1.Bean的作用域(Spring 5.2.7.RELEASE)
ScopeDescription
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对象
4.参考资料
面试题?一个单例 Bean 依赖一个原型 Bean,这个原型 Bean 是单例还是原型?面试题:Spring为什么默认bean为单例?Spring Framework 官方文档