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:

  1. 通过@Qualifier注解
  2. 通过@primary注解
  3. 通过名字(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

说明:

  1. u3/u5是在程序运行的不同位置向Spring容器获取的名字为userService3的userService对象,从运行结果看,获取到的是同一个对象。
  2. 第一步的运行结果说明,由于userService3是单例对象,所以在Spring容器的整个声明周期中的不同位置获取到的是同一个对象。
  3. 尝试在不同的线程中获取userService3,仍然是同一个对象。
  4. 以上实验可以说明,Spring容器中创建并存储的名字为userService3的userService对象是单例的、只有一个。
  5. 获取名字为userService3、userService4的userService对象,得到的是不同的对象。
  6. 说明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实例对象。

多线程的案例以后补充。


45 声望18 粉丝