spring中的单例和多例中的坑

此处仅是问题的简单描述,并没有按照规范整理,请大家谅解,这是我在遇到这样问题时,随手按照自己的想法记录下了自己的心得,看着有些乱,不过全是干货,希望谅解;

 

//在springboot 整合rabbitmq下  rabbitTemplate 默认是单例形式
如果仅是发送队列和接受队列消息 该单例模式就足够使用了
如果想要 对于 发布端进行消息推送确认,那么单例模式是无法满足的
如果我们有多个队列,并需要对每个队列发送是否成功的消息进行确认
这种情况下,如果是单例模式,那么整个项目中,仅有个一confirm 和 returncallback 实现接口
对于所有的队列消息的确认也只能在这一个接口中得到回复,这样就很难辨别确认的消息响应是具体哪个队列的。

所以这样的情况下,单例是无法满足的,因此需要使用多例模式


scope
  prototype 多例模式也会出现一个问题
  
  问题描述
  1  在非指直接请求层  如 server层 ServerImp 使用多例 @Scope("prototype")
    如果在 A 和 B 中分别引用了
    
    A{
      @Autowired
      ServerImp sA;
    }
    
    B{
      @Autowired
      ServerImp sB;
    }
    
  2 此时A和B 都是默认的单例形式作为controller层
    对于A在spring容器启动的时候,会创建一个A的实例,此时也会创建一个ServerImp的实例 sA
    对于B在spring容器启动的时候,会创建一个B的实例,此时也会创建一个ServerImp的实例 sB
    
    sA != sB
    但由于a和b仅有一个实例,所以,每次请求使用它们的时候,它们并不会去创建一个新的实例
    那么此时在a和b中注入的 sA和sB也只是在不同的地方具有不同的实例,但是相对于a和b而言,它们只有一分
    所以,如果在sA和sB中创建了共享变量,那么这时候就会出现非线程安全问题。
    
    
    单例模式
    1  controller server ...等等 在spring容器启动的时候就会初始化一个单一实例
    
    
  3 scope("prototype") 的时候位置不同,那么他被初始化和使用的时候也不一样
   1  使用在controller层的情况下,他是在每次请求到该controller的时候,进行一个实例化,就是创建一个新的controller对象
      在该请求结束后,该controller的实例就会被销毁,下次再有新的请求进来,就会再次创建一个新的实例
     //使用该注解,标识,在类被实例化后(即调用构造方法后),调用下面的方法
     @PostConstruct
     public void init(){
        //此处每次有新的请求进来的时候,都会实例化一个新的controller对象
        System.out.println("ProductListController-->init:,this:"+this);
     }
     
  
    
   2  server层使用多例的注解,这个地方会出现很多的坑,这个也是我们需要特别注意的
    首先我们说在默认的单例情况下
    2.1  在controller层 使用@Autowired 来注入一个server层的实例
    2.1  此时spring容器为我们怎么处理的?
       2.1.1  在创建了一个server单实例后,在引入了该controller的地方,setter到该server的属性上
         此时如果有3个或者n个controller同时 @Autowired Server 该server,那么这n个controller同时拥有同一个server对象
       
    此时我们说下多例模式下
   1  controller层是单例
      server层使用多例模式
   2  此时在3个controller中 
       //创建三个controller
       controller_01{
         @Autowired  
         Server server;//  01中的server对象的实例 地址 [email protected]
       }
       controller_02{
         @Autowired  
         Server server  //  02中的server对象的实例 地址 [email protected]
       }
       controller_03{
         @Autowired  
         Server server  //  03中的server对象的实例 地址 [email protected]
       }
       .....
       
       //server层
       @Service
       @Scope("prototype")   
       Server{
       }
       
       我们会发现,在三个controller层注入的server的实例是不一样的。也就是体现了多例
       
       但是我们需要注意:server的多例是相对于每个不同引入的地方而言的
         其实我们会发现,在spring容器初始化的时候,他会为每一个controller中的server实例化一个对象
         这个实例化对象,就被绑定到了这个controller上了,意思就是(如论你有n多个请求,都请求到同一个controller上
         ,此时这n个请求使用的server对象是同一个。),但是在不同的controller里的server是不一样的。
       同理,如果我们的controller是prototype多例的,server也是多例的,这时候的情况和上面也是类似的
       controller多例 是在每次请求的时候创建一个新的实例,
       但是server不一样,如果在controller里面,使用@Autowired server进行了注入,该server的实例并不会在每次请求到
       该位置的时候才进行实例化的,仍然是在spring容器初始化的时候就一样完成了对每个controller的注入
       这里可能有点绕,下面给出伪代码
       //controller——01类  多例
       //在每次新的请求时创建一个新的实例
       @Scope("prototype")
       class controller_01{  
         
         @Autowired
         Server server01  //spring容器初始化的时候为其注入了一个 [email protected]
       }
       
       //controller——02类  多例
       //在每次新的请求时创建一个新的实例
       @Scope("prototype")
       class controller_02{  
         @Autowired
         Server server02;   //spring容器初始化的时候为其注入了一个 [email protected]
       }
       
       //在spring容器初始化的时候,就为存在@Autowired server注解的类注入了一个新的实例
       @Scope("prototype")
       class Server{
       
       }
       
       -------------
       此时有3个请求进入了controller_01中,那么内存中将会创建三个新的实例
       [email protected]
       [email protected]
       [email protected]
       这三个新的实例他们共享server01中的地址[email protected],也就是他们持有同一个server01,
       因为,该server01在spring容器初始话的时候就已经完成了实例化
       
       同理,其他也是这样
       
       
    探讨一个问题
    此时我们就可以利用上面的原理,来解决一些非线程安全变量共享问题
    描述:
      有些时候,我们会在server层的类中定义一些私有属性,这些属性会被作为该类的全局变量来使用,而且该属性是一个动态赋值
    如果向上面那样,虽然server在被多例后,在不同的controller类下具有不同的实例,但是,对于同一个controller类而言,他们
    持有同一个server对象,也就是该属性是共享的,随时可能被另外的请求给修改。
    这时候我们的server中的该属性是非线程安全的。这也是我们平时很少用的情况,严格来说也不符合spring的初衷
    如果非要这样时候怎么办,那就不要使用@Autowired 这种方式去获取server的实例,这种获取其实也是单例的。
    所以我们要想办法,在每次请求的时候,都要获取一个新的server实例,这样才可以保证变量线程安全
    
    @Autowired
    ApplicationContent applicationContent;
    我们可以通过上下文容器在需要的时候主动去获取,
    Server ser = applicationContent.getBean(Server.class);
    这样每次获取到的ser都是一个新的实例。
    
    这样就解决了多实例的问题