概述

每次调用单例类的实例对象时,都获得同一个对象;每次调用多例类的实例对象时,会获取到不同的对象(类会自建新的对象)。

为什么用单例多例:

之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存可以保证系统中一个类只有一个实例而且该实例和外界通信,解约资源,便于维护;当前需要频繁访问一个对象,可以用单例,避免创建过多的垃圾

之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理

用单例和多例的标准只有一个: 当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例

单例模式

单例模式的类实现方法有饿汉式和懒汉式

Spring的bean默认注入是单例的,它在Spring容器初始化的时候创建对象;

多例模式

实现多例的方式

  1. xml配置
<bean id="xxx" class="全类名" scope="prototype"></bean>
  1. 注解
@Component
@Scope("prototype")

注意:

  1. 多例模式在进行注入时,不能使用 @Autowired,否则注入的还是单例模式实现多例模式需要使用工厂模式
  • @Autowired 在项目启动时已经将bean实例注入到引用类的成员变量中,因此在调用时是直接引用了已被初始化的成员变量,不会再从bean工厂中获取,因此没办法实现多例。也就是说多例模式,必须从bean工厂获取bean才能够实现。
  1. @Autowired+@Qualifier("xxx") 与@Resource作用一致,当获取对象时,并不能稳定获 取到多例,在使用时需要注意
  2. @Resource注入的是对象不稳定,有时能获取相同对象,有时获取到的是不同对象; 与@Autowired+@Qualifier("student") 类似
  • 这里的区别是因为@Autowired和@Resource注解的处理逻辑不同:
  • 对于@Resource注解会根据要注入属性的name,从单实例池中找到类,获取bean,完成属性注入
  • spring在解析@Autowired注解的时候,并不是直接从单实例池中查找bean,而是先从beanDefinitionMap中,根据type查找如果找到多个,再根据name从单实例池中查找,只有找到确定要注入哪个类,才会从单实例池中找到对应的bean,完成注入

解决Bean链中某个Bean需要多例的问题

@Component
public TPromotionSeckill getKill(){
    TPromotionSeckill seckill = new TPromotionSeckill();
    seckill.setPsCount(10);
    return seckill;
}

方式1:@Scope

将@Scope改为:

@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS

注解@Scope("prototype")(这个注解实际上也可以写成@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,使用常量比手打字符串不容易出错)还有很多用法。

  1. value就分为四类:
  • ConfigurableBeanFactory.SCOPE_PROTOTYPE,即“prototype”
  • ConfigurableBeanFactory.SCOPE_SINGLETON,即“singleton”
  • WebApplicationContext.SCOPE_REQUEST,即“request”
  • WebApplicationContext.SCOPE_SESSION,即“session”

他们的含义是:

  1. singleton代表单例
  2. prototype代表多例
  3. request表示请求,即在一次http请求中,被注解的Bean都是同一个Bean,不同的请求是不同的Bean
  4. session表示会话,即在同一个会话中,被注解的Bean都是使用的同一个Bean,不同的会话使用不同的Bean

使用session和request产生了一个新问题,生成controller的时候需要service作为controller的成员,但是service只在收到请求(可能是request也可能是session)时才会被实例化,controller拿不到service实例。

为了解决问题,@Scope注解添加了一个proxyMode的属性,有两个值

  1. ScopedProxyMode.INTERFACES:表示Service是一个接口
  2. ScopedProxyMode.TARGET_CLASS:表示Service是一个类

解决Bean链中某个Bean需要多例的问题@Scope注解改成@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)

参考博客:

缺点:但是若业务内存在异步操作,且请求相关的http丢失,则会报错。报错日志与下面的类似

Error creating bean with name 'xxxx': Scope 'request' is not active for the current thread; 
consider definin g a scoped proxy for this bean if you intend to refer to it from a singleton; 
nested exception is java.lang.IllegalStateException: No thread-bound reque st found:
 Are you referring to request attributes outside of an actual web request,
  or processing a request outside of the originally receiving thread? 
   If you are actually operating within a web request and still receive this message,
    your code is probably running outside of DispatcherServlet: In this c ase, 
    use RequestContextListener or RequestContextFilter to expose the current request.

方式2:BeanFactory

注入BeanFactory对象,调用其getBean方法即可

@Autowired
private BeanFactory factory;
// 此时得到的对象即为多例
TPromotionSeckill seckill = factory.getBean(TPromotionSeckill.class);

缺点:获取bean对象,若对象为prototype,则会按照Bean的生成策略,生成多例对象

方式3:ObjectFactory(推荐使用)

使用下面的方式

@Autowired
ObjectFactory<TPromotionSeckill> TPromotionSeckillFactory

然后想要使用,直接使用下面的方法

TPromotionSeckill TPromotionSeckill = TPromotionSeckillFactory.getObject();