spring三级缓存不会还不懂吧?

流程图总结


1、概念和误区

	/** Cache of singleton objects: bean name to bean instance. */
    // spring容器里面的完整的单例池对象,但是与解决循环依赖无直接关系
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
    // 缓存单例的工厂,大家常叫的第三级缓存,其实叫第三级缓存并不合理
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	// 这个所谓的二级缓存才是打破循环依赖的一个关键点
	// 缓存早期暴露的bean,这里的bean和singletonObjects里的bean是同一个
	// 只是它没有执行属性注入和初始化等其他操作。
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

如果想透彻理解在这三个缓存里面,要去掉一级缓存 singletonObjects 的概念?
其实大家都叫他第一级缓存本身是不合适的,因为它不是解决循环依赖的缓存。
因为 singletonObjects 是在一个bean走完所有流程之后,才会放进单例map中,正常发生循环依赖的bean压根走不到这一步。所以去理解singletonFactories和earlySingletonObjects就行了。
其实第二级缓存和第三级缓存,它不是俩个概念,他俩是相互依赖的。
说到这里可能还是很绕,下面继续分析。

2、普通对象属性注入循环依赖流程:

注入过程

假设A和B是俩个bean,且相互依赖它的注入过程如下:
1、A实例化(createBeanInstance)
2、A加入earlySingletonObjects暴露实例化的对象
3、A属性填充(populateBean)
4、A注入B(inject)
4.2、查询B(getSingleton)
4.3、查询不到执行B实例化(createBeanInstance)
4.4、B加入earlySingletonObjects暴露实例化的对象
4.5、B属性填充(populateBean)
4.6、B注入A(inject)
4.7、查询A(getSingleton)此时A在第二部时已经放入缓存中,此时可以查到不会再次创建A
4.8、B初始化(initializeBean)
4.9、B创建完成(addSingleton)
5、A初始化(initializeBean)
6、A创建完成(addSingleton)

分析结论:

从这里可以分析到,其实用不到俩层缓存,一层缓存就能解决问题,这层缓存就是earlySingletonObjects

3、第三级缓存存在意义

像spring注解如@Transactional、 @Async都是基于动态代理实现的,还有我们自己实现@Log注解来打印日志等等,都是在spring bean注入时生成代理对象的。
这里想要了解第三级缓存存在的意义需要了解代理对象生成的过程:

普通对象:实例化 -> 属性填充 -> 初始化 -> 创建完成
代理对象: 实例化 -> 属性填充 -> 初始化 -> 生成代理对象 -> 创建完成

矛盾点:如果存在代理对象,那么最终注入的是代理对象,而不是普通对象
回到刚刚这个流程

假设A和B是俩个bean,且相互依赖它的注入过程如下:
1、A实例化(createBeanInstance)
2、A加入earlySingletonObjects暴露实例化的对象
3、A属性填充(populateBean)
4、A注入B(inject)
4.2、查询B(getSingleton)
4.3、查询不到执行B实例化(createBeanInstance)
4.4、B加入earlySingletonObjects暴露实例化的对象
4.5、B属性填充(populateBean)
4.6、B注入A(inject)
4.7、查询A(getSingleton)从早期对象中查到A的普通对象
4.8、B初始化(initializeBean)
4.9、B创建完成(addSingleton)
5、A初始化(initializeBean)
6、A生成代理对象 这个时候生成的是代理对象
7、A创建完成(addSingleton)

所以来看,如何没有第三级缓存,最终B对象注入的A对象是普通对象而不是代理对象。

4、AOP代理对象属性注入循环依赖流程::

注入过程

假设A和B是俩个bean,且相互依赖它的注入过程如下:
1、A实例化(createBeanInstance)
2、A加入singletonFactories 暴露一个A自己的单例工厂
3、A属性填充(populateBean)
4、A注入B(inject)
4.2、查询B(getSingleton)
4.3、查询不到执行B实例化(createBeanInstance)
4.4、B加入earlySingletonObjects暴露实例化的对象
4.5、B属性填充(populateBean)
4.6、B注入A(inject)
4.7、查询A(getSingleton)
执行代理对象判断之前:earlyProxyReferences.put(A, bean);这里备份代理之前的对象
从wrapIfNecessary判断A是否需要动态代理,不需要就返回A的普通对象,需要就返回A的代理对象,并且打标记:
4.8、B初始化(initializeBean)
4.9、B创建完成(addSingleton)
5、A初始化(initializeBean)
6、A生成代理对象(AbstractAutoProxyCreator#postProcessAfterInitialization)
** 若earlyProxyReferences存在A普通对象,表示已经动态过了,已经执行过wrapIfNecessary**
不再生成代理对象若earlyProxyReferences不存在,表示尚未进行aop判断
7、A创建完成(addSingleton)

分析结论

其实可以分析到其实真正起作用的打破循环依赖的不是第三级缓存,第三级缓存

5、思考&拙见

能不能直接用二级缓存来实现aop

这里斗胆揣摩一下spring开发者思路
最早的开发是不支持循环依赖的,因为发生循环依赖是我们用户自己的设计问题。
而现在spring为了少数不合理的需求,也做了适配,也就是如果出现循环依赖,就在实例化之后,填充属性之前,提前aop。
所谓的【第三级缓存+第二级缓存】,如果改造成一层缓存也能够的。

思路:

A实例化
…直接判断有没有代理对象,有的话就塞入二级缓存中,并暴露出来**(执行二级和三级的操作)**
A属性填充
A注入B
查询B(getSingleton)
B实例化
B属性填充
…从二级缓存中取出代理对象或者普通对象,这一步不在创建
B初始化
B创建完成
A初始化
A创建完成

这样理论是可行的,但是与spring设计的理念相悖,因为spring创建代理对象的时机是初始化之后,这样的话无论是否发生循环依赖都会在实例化之后提前创建一个代理对象,
再强调一点,本身出现循环依赖就是我们的程序设计问题,spring只是为了解决这种不合理的情况,加了一层三级缓存来适配本身spring合理的bean初始化设计逻辑。

Spring三级缓存Spring框架内部的缓存机制,与Spring缓存机制并不直接相关。但是,Spring缓存机制可以用于对Bean的缓存,以提高应用程序的性能。下面介绍如何在Spring三级缓存中使用Spring缓存机制。 1. 配置缓存管理器:在Spring配置文件中配置缓存管理器。例如,使用Ehcache作为缓存管理器,可以在配置文件中添加以下内容: ``` <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcache"/> </bean> <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml"/> </bean> ``` 2. 在需要缓存的方法上添加缓存注解:在需要缓存的方法上添加缓存注解,例如@Cacheable注解。例如: ``` @Service public class UserServiceImpl implements UserService { @Override @Cacheable(value = "userCache", key = "#userId") public User getUserById(String userId) { // 从数据库中获取用户信息 User user = userDao.getUserById(userId); return user; } } ``` 3. 注入缓存管理器:在需要使用缓存的类中注入缓存管理器。例如: ``` @Service public class UserServiceImpl implements UserService { @Autowired private CacheManager cacheManager; ... } ``` 4. 获取缓存对象:在需要使用缓存的方法中,通过缓存管理器获取缓存对象,然后从缓存对象中获取缓存数据。例如: ``` @Service public class UserServiceImpl implements UserService { @Autowired private CacheManager cacheManager; @Override public User getUserById(String userId) { Cache userCache = cacheManager.getCache("userCache"); ValueWrapper valueWrapper = userCache.get(userId); if (valueWrapper != null) { User user = (User) valueWrapper.get(); return user; } else { User user = userDao.getUserById(userId); userCache.put(userId, user); return user; } } } ``` 在使用Spring缓存机制时,需要注意缓存的key值的生成方式,可以使用SpEL表达式来指定。此外,需要根据具体的业务需求来决定缓存的策略,例如缓存的有效时间、缓存的清除策略等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值