1.Bean的作用域(Spring 5.2.7.RELEASE)
Scope | Description |
---|---|
singleton(单例) | (默认的)在Spring IoC 容器中,只会有唯一的一个bean 实例。Spring 采用的是双重校验锁来实现的单例模式。 |
prototype(原型) | 每次从Spring IoC 容器中调用bean 时,都返回新的实例,即调用getBean()时,都相当于执行new XxxBean()。 |
request(请求) | 一个bean 定义对应于单个HTTP 请求的生命周期。也就是说,每个HTTP 请求都有一个bean 实例,且该实例仅在这个HTTP 请求的生命周期里有效。该作用域仅适用于WebApplicationContext 环境。 |
session(会话) | 一个bean 定义对应于单个HTTP Session 的生命周期,也就是说,每个HTTP Session 都有一个bean 实例,且该实例仅在这个HTTP Session 的生命周期里有效。该作用域仅适用于WebApplicationContext 环境。 |
application(应用) | 一个bean 定义对应于单个ServletContext 的生命周期。该作用域仅适用于WebApplicationContext 环境。 |
websocket(网络套接字) | 一个bean 定义对应于单个websocket 的生命周期。该作用域仅适用于WebApplicationContext 环境。 |
2.常用的作用域对比
2.1singleton(单例):
- 对tomcat来说,每一个进来的请求(request)都需要一个线程,直到该请求结束。当Bean的作用域定义为singleton时,多个线程共用一个Bean实例。即减少了生成新实例对性能的消耗,也减少了垃圾回收的次数。
- 当Bean被设置为单例非懒加载时,在spring容器创建时Bean就已经初始化。
- 当Bean被设置为单例懒加载时,在第一次访问的时候初始化(如果该Bean被其他需要实例化的Bean引用到,Spring会忽略延迟实例化的设置)
- 单例Bean在第一次生成实例时会将实例存放到ConcurrentHashMap对象中,之后每次用到该实例都会去map缓存里查找,所以获取速度很快。
- 单例Bean的缺点是当Bean为有状态对象时(含属性的对象),无法做到线程安全。所以我们在日常开发中Controller,Service,Dao都只会定义方法,不会定义属性。
2.2prototype(原型)
- prototype原型作用域下,每一个进来的请求(request)就实例化一个新的bean,没有缓存以及从缓存中查询的方法。
- 当Bean的作用域定义为prototype原型时,默认采用懒加载的方式,即每次调用getBean()方法时才实例化。
3单例Bean中依赖原型Bean存在的问题
3.1问题复现
3.1.1作用域为原型的Dao
3.1.2作用域为单例的Service
3.1.3采用注解方式配置Spring容器
3.1.4测试类
3.1.5测试结果
3.2原因分析
单例(懒加载除外) Bean在 Spring容器初始化的时候就已经实例化完成,接下来只会从缓存中读取,而在单例 Bean 中,不管是依赖了单例 Bean还是原型 Bean,都是在 Spring 容器启动时实例化好了的,所以,我们看到两次打印 hashCode 的结果是一模一样的。
3.3解决方案
3.3.1放弃某些控制反转
通过实现ApplicationContextAware接口,作用是为了方便获得ApplicationContext中的所有bean。在单例Bean A每次需要原型Bean B实例时,对容器发出getBean(“B”)调用来让Bean A每次注入不同的Bean B,也就是说将Bean B的控制权重新交由我们自己来管理,而不是通过IoC自动注入。
3.3.2通过@Lookup
注解实现方法注入
AutowiredAnnotationBeanPostProcessor 会检查类里面是否有方法添加了 @Lookup 这个注解,然后通过CGLib动态代理的方式,实现了getTestDao()抽象方法, 每次调用getTestDao()方法时都会调用getBean()方法去Spring容器重新获取Bean对象