Spring的Ioc容器创建bean的时候,如果不做特殊指定(不管是通过xml配置文件、或者是注解方式),则Spring默认会创建单例bean。
与单例bean对应的,Spring还可以提供原型bean,原型bean需要用户在配置bean的时候特殊指定。
单例bean和原型bean的主要区别
Spring的单例bean指的是,Spring容器中只保存该bean的一个实例,用户在应用中获取bean的时候,即使在不同的模块、或不同的线程中,获取到的其实是同一个bean。
单例bean在容器初始化的时候就会创建并完成初始化,并存储在容器中,应用在使用的时候直接从容器中获取。
原型bean是针对单例bean而言的,原型bean在容器初始化的时候并不会初始化,只有在应用向容器获取bean的时候,才会创建、初始化并返回给应用。
应用即使是在同一模块中、同一段代码中,两次获取原型bean,返回的也是不同的bean实例。
为什么要有单例bean
或者说,Spring为什么默认提供的是单例bean,而不是原型bean。Spring框架考虑的其实是性能问题,容器初始化的时候就创建并初始化好bean,应用使用的时候直接从容器中获取,就会节约bean实例创建及初始化过程中的开销,极大提高应用的性能。
那为什么还会需要原型bean
其实,任何事情都会有正反两面,单例bean在提高性能的同时,也存在线程安全的隐患。
尤其是你的业务bean中存在成员变量,由于单例bean在整个容器生命周期过程中只存在一个bean实例,该成员变量非常容易导致多线程环境下的安全问题。
这种情况下,除了可以选择对bean成员变量进行线程安全的保护之外,Spring还给你提供了另外一种选择就是原型bean。
由以上原型bean与单例bean的区别部分,非常容易的能知道,原型bean不存在以上的线程安全问题。
单例bean就表示容器中只能有该类的一个实例对象吗?
这个问题的答案大家都知道:不一定,可以有多个。
原因是大家都知道Spring的单例和设计模式中的单例模式实际不是一回事,Spring的单例并不是单例模式的实现。
进一步解释,就会看到有很多人这样说:因为Spring可以有多个容器,同一个类的单例bean注册到不同的容器中,那么,在多个容器中就会存在该类的多个实例。
个人觉得这个解释不合格,因为这样的解释并没有真正说清楚Spring容器中单例bean的含义。
我们回忆一下,同一个类,其实是可以在Spring容器中注册多个实例对象的。否则就不会存在by type/by name这样的说法了。
同一个类,我们可以用不同的名字,注入该类的多个对象到Spring容器中。
然后我们可以通过不同的方式,获取到你想要的实例bean:
- 通过@Qualifier注解
- 通过@primary注解
- 通过名字(bean id)获取
单例bean举例
比如:我们创建一个userService类:
@Component
public class userService{
//默认是单例bean,在容器初始化的时候就会被创建、初始化
public userService(){
System.out.println("userService constructed。。。");
}
然后,在配置类中定义多个userService的实例:
@Configuration
public class commonConfig {
@Bean
public userService userService3(){
return new userService();
}
@Bean
public userService userService4(){
return new userService();
}
}
编写启动类:
@Slf4j
public class testApp
{
public static void main( String[] args )
{
log.info("program is running Context will be created");
ApplicationContext ctx=new AnnotationConfigApplicationContext(commonConfig.class);
System.out.println("Context created over。。。");
userService u3=ctx.getBean("userService3",userService.class);
userService u4=ctx.getBean("userService4",userService.class);
System.out.println("u3 here:" + u3);
System.out.println("u4 here:" + u4);
userService u5=ctx.getBean("userService3",userService.class);
userService u6=ctx.getBean("userService4",userService.class);
System.out.println("u5 here:" + u5);
System.out.println("u6 here:" + u6);
运行启动类,控制台查看运行结果:
u3 here:org.example.service.userService@35d019a3
u4 here:org.example.service.userService@18078bef
u5 here:org.example.service.userService@35d019a3
u6 here:org.example.service.userService@18078bef
说明:
- u3/u5是在程序运行的不同位置向Spring容器获取的名字为userService3的userService对象,从运行结果看,获取到的是同一个对象。
- 第一步的运行结果说明,由于userService3是单例对象,所以在Spring容器的整个声明周期中的不同位置获取到的是同一个对象。
- 尝试在不同的线程中获取userService3,仍然是同一个对象。
- 以上实验可以说明,Spring容器中创建并存储的名字为userService3的userService对象是单例的、只有一个。
- 获取名字为userService3、userService4的userService对象,得到的是不同的对象。
- 说明Spring容器可以创建并保存类型为userService、作用域为单例的多个对象。
结论:
Spring容器中可以创建并存储多个名称不同的某一类型的单例对象,这是Spring的单例bean与单例模式的最本质的区别
原型bean举例
将以上userService定义为原型bean:
@Component
@Scope("prototype")
public class userService{
//通过注解定义为原型bean,容器初始化的时候,不会创建userService的实例
public userService(){
System.out.println("userService constructed。。。");
}
重跑以上案例,可以发现,多次获取的userService3为不同的bean实例对象。
多线程的案例以后补充。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。