0%

Spring单例和多例

在面试的时候经常被问到Spring的单例、多例之类的问题,实际上是Bean的作用域问题。当在Spring中声明一个bean时,需要声明bean的作用域。默认是singleton。这篇文章主要讨论Spring 单例和多例。

Spring bean 的作用域(Spring Bean scopes)

作用域 描述
singleton 根据Spring IoC容器将单个bean定义范围限定为单个对象实例。
prototype 将单个bean定义范围限定为任意数量的对象实例。
request 将单个bean定义范围限定为单个HTTP请求的生命周期;也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。仅在Web-aware的 Spring ApplicationContext的上下文中有效。
session 将单个bean定义范围限定为HTTP会话的生命周期。仅在Web-aware的Spring ApplicationContext的上下文中有效。
global-session 将单个bean定义范围限定为全局HTTP会话的生命周期。通常仅在portlet上下文中使用时有效。仅在Web-aware 的Spring ApplicationContext的上下文中有效。
> 具体可以查看Spring官方文档 Spring Doc,有更详细的说明。
####The singleton scope(单例作用域)
默认作用域是始终是 singleton,但是当仅仅需要 bean 的一个实例时,你可以在 bean 的配置文件中设置作用域的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
import lombok.Data;

@Data
public class TestBean {

private String name;

public TestBean(String name) {
this.name = name;
}
}


可以在Application中进行注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);

}

@Bean
public TestBean getBean() {
return new TestBean("Hello,singleton!");
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTest {

@Autowired
private TestBean testBean;

@Test
public void getBean() throws Exception {
System.out.println(testBean.getName());
}
}

还可以使用 @Component 的方式,可以自行实践。

The prototype scope(多例作用域)

The non-singleton, prototype scope of bean deployment results in the creation of a new bean instance every time a request for that specific bean is made. That is, the bean is injected into another bean or you request it through a getBean() method call on the container. As a rule, use the prototype scope for all stateful beans and the singleton scope for stateless beans.

描述了什么时候使用单例、什么时候使用多例。
@Resource@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。

@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。

@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。

最常用的是一个接口有多个实现。

1
2
3
4
5
6
7
8
9
10
public interface IPrintService {

/**
* 打印字符串
*
* @return
*/
void print();
}

打印数字

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.stereotype.Service;

@Service
public class NumberPrintServiceImpl implements IPrintService {

@Override
public void print() {
System.out.println(1);

}
}

打印字符串

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.stereotype.Service;

@Service
public class StringPrintServiceImpl implements IPrintService {

@Override
public void print() {
System.out.println("String");

}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

@Test
public void contextLoads() {
}

@Resource(name = "numberPrintServiceImpl")
private IPrintService numPrintService;

@Resource(name = "stringPrintServiceImpl")
private IPrintService strPrintService;


@Test
public void print() throws Exception {
numPrintService.print();
strPrintService.print();
}

}

通过name来区分IPrintService,进行注入。
面试的时候还会考察单例模式的实现,最常见的是懒汉式式、饿汉模式和双重锁等。