Bean Scopes,Spring Bean的作用域,也就是Spring创建的Bean实例的生效范围。
Spring提供了如下6中不同的作用域:
- singleton: 单例Bean,每一个Spring IoC容器中只有一个Bean实例,这是Bean的默认作用域。
- prototype:原型Bean,每请求一次就生成一个新的Bean实例
- request:仅对 web-aware Spring ApplicationContext生效,每一次HTTP请求生成一个Bean实例。
- session: 仅对 web-aware Spring ApplicationContext生效,每一个HTTP session对应一个Bean 实例。
- application: 仅对 web-aware Spring ApplicationContext生效,ServletContext生命周期对应一个Bean实例。
- websocket:仅对 web-aware Spring ApplicationContext生效,websocket请求对应一个Bean实例。
其中有4个是和web-aware Spring ApplicationContext相关的,也就是和web应用相关,最常用的是singleton和prototype两种。
单例Bean
singleton,单例Bean,是Spring默认的Bean作用域,我们在配置Bean的时候如果没有指定作用域,那么他的作用域就是singleton。
Spring官网的一张图一目了然的讲清楚了单例Bean的含义:
定义了一个id为accountDao的Bean,被其他3个Bean引用。如图,我们在定义accountDao的时候没有指定作用域,默认的,他的作用域就是singleton,单例Bean。那么,在整个Spring IoC容器中就只创建一个accountDao的Bean实例,3个引用类引用的其实是同一个accountDao实例。
这种情况系按我们一定要注意accountDao的线程安全问题!使用单例Bean的时候你一定要注意线程安全问题,尽可能的避免在单例Bean中使用属性对象,或者你非常明确的知道该属性不需要关注线程安全性!
接下来思考一个问题:定义为单例Bena就一定表示整个Spring IoC容器中只有该类的一个对象吗?
答案显示是No,你当然可以在Spring IoC容器中为某一个类创建多个单例对象。
原型Bean
原型Bean指的是Spring IoC容器中会有多个Bena实例,应用没请求一次,Spring IoC容器就会生成一个新的Bean实例返回。
如图,accountDao定义为原型Bean,被其他3个Bean引用,不管其他3个Bean是单例还是原型Bean,Spring IoC注入的accountDao都会是3个不同的对象。
原型Bean在声明的时候需要显式指定scope:
也通过注解方式指定scope:
@Component
@Scope("prototype")
public class DependencyB implements IDependencyB{
@Autowired
//@Qualifier(value="dependencyA3")
private IDependencyA dependencyA;
public static void main(String[] args) {
System.out.println("I am a new comer...hello world ");
}
@Override
public void testB(){
System.out.print("This is DependencyB's test and ...");
dependencyA.test();
}
启动类加测试代码:
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("Now start to getbean...");
DependencyB dependencyB = applicationContext.getBean(DependencyB.class);
dependencyB.testB();
System.out.println(dependencyB);
dependencyB=applicationContext.getBean(DependencyB.class);
System.out.println(dependencyB);
}
}
执行启动类:
Now start to getbean...
This is DependencyB's test and ...I am DependencyA test...
springTest.DependencyB@53ca01a2
springTest.DependencyB@358c99f5
可以看到两次获取DependencyB,得到的是不同的对象。
原型Bean的特殊之处还在于:创建的时候交给了Spring IoC,但是Spring IoC并不负责销毁。但是好消息是,由于原型Bean的特殊性,所以,Spring IoC容器也没有必要在每次创建之后缓存该实例,Spring IoC容器只需要new出一个对象(其实不是New的,是通过反射机制或者CGLIB创建的)直接返回给应用就OK,所以,也就不会导致内存泄漏,
单例Bena和原型Bean的依赖
如果一个原型Bean要依赖一个单例Bena,其实是没有任何问题的,因为依赖注入是发生在Bean实例化之后的属性赋值阶段,原型Bean在每次请求的时候都会进行一次属性赋值、都会执行一次依赖注入,单例Bean depencyA会注入到原型Bean dependencyB中,不会存在任何问题。
其实我们上面的例子,原型Bean dependencyB就是依赖单例Bean dependencyA的,我们把DependencyB的test方法稍加改造、打印一下依赖对象dependencyA:
@Override
public void testB(){
System.out.print("This is DependencyB's test and ..."+dependencyA);
dependencyA.test();
}
执行启动类:
Now start to getbean...
This is DependencyB's test and ...springTest.DependencyA@53ca01a2I am DependencyA test...
springTest.DependencyB@358c99f5
This is DependencyB's test and ...springTest.DependencyA@53ca01a2I am DependencyA test...
springTest.DependencyB@3ee0fea4
我们会发现两次获取到的虽然是不同的dependencyB对象,但是他们的依赖对象dependencyA却是相同的对象,因为dependencyA是单例Bean。
但是反过来就不是这么回事了:如果一个单例Bean依赖一个原型Bean会怎么样?
为此,我们把上例中的DependencyB修改为单例Bean,把DependencyA修改为原型Bean,做一下测试。
只需要把@Scope("prototype")注解从DependencyB类转移到DependencyA上即可。
运行启动类:
Now start to getbean...
This is DependencyB's test and ...springTest.DependencyA@53ca01a2I am DependencyA test...
springTest.DependencyB@358c99f5
This is DependencyB's test and ...springTest.DependencyA@53ca01a2I am DependencyA test...
springTest.DependencyB@358c99f5
我们发现虽然定义DependencyA为原型Bean,但是两次获取到的DependencyB的依赖对象DependencyA是同一个对象。
这也不难理解,Bena的注入是在Bean实例化之后的属性赋值阶段解决的,单例Bean只实例化一次、属性负责也只发生一次,所以,依赖对象也只能被注入一次,虽然属性是原型Bean,由于只能被注入一次所以也就只能是同一个对象了。
其实建议最好不要在单例Bean中引用原型Bean,如果非要这么做的话,Spring也提供了一种解决方法:Method Injection,可以采用xml配置或注解@Lookup实现,个人感觉应用很少,暂时忽略。