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 实例对象。
多线程的案例当前补充。