由LifecycleBeanPostProcessor引起spring缓存失效说起
文章来源:原创
作者:许秋冬
发布时间:2020-08-20
阅读:1172
标签:源码,事务
许秋冬偷偷隐藏起来的markdown原文,测试对蜘蛛是否友好
[toc]
## [由LifecycleBeanPostProcessor引起spring缓存失效说起](https://crazyblitz.github.io/2019/08/24/BeanPostProcessor加载次序及其对Bean造成的影响)
> 由LifecycleBeanPostProcessor引起spring缓存失效说起
>
> 1. springBean 加载时候各方法执行顺序
> 2. shiro引起的种种坑
> 3. springcache初始化过程
> 4. spring事务初始化过程
### 一 问题场景:
#### 1 shiro 造成springcache失效
> spring4.1.6 + springmvc + spring-shiro1.4.2
#### shiro 造成注入配置文件失效
> springboot2.1.7 + spring-shiro1.4.2
#### shiro造成事务失效?
> 本人没有关注是否存在这个问题, 但是事务的生效机制和cache理论上差不多
### 二 解决方案
#### 方式一
* springmvc中把LifecycleBeanPostProcessor移到到其他xml(和mvc级别的xml一起执行)
* springboot中把LifecycleBeanPostProcessor移到到其他@Configuration
#### 方式二
* [reml中注入的bean设置为延迟加载@Lazy](https://stackoverflow.com/questions/21512791/spring-service-with-cacheable-methods-gets-initialized-without-cache-when-autowi)
#### 方式三:
- springBoot中注册LifecycleBeanPostProcessor的方法(@Bean)设置为静态方法
### 三 前置条件
* 了解BeanPostProcessor的工作原理
* 了解shiro的LifecycleBeanPostProcessor的处理过程
* 了解spring的cache初始化流程
* 综合,如何差生交叉影响
#### bean的加载顺序
1. 在web.xml中,ContextLoaderListener和DispatcherServlet的书写顺序不会影响相应的xml文件加载顺序。ContextLoaderListener中的xml先加载,DispatcherServlet中的xml后加载。
2. ContextLoaderListener中如果contextConfigLocation通过模糊匹配到多个xml文件时,xml按照文件命名顺序加载。但是如果contextConfigLocation逐个指定了具体加载某个xml,则会按照其指定顺序加载。
3. 同一个spring的xml文件中,bean的加载顺序按照书写顺序加载
4. 通过component-scan扫描的方式加载bean,在扫描范围内按照class的命名顺序加载
5. **如果bean之间的创建存在依赖关系,则被依赖的bean会被优先创建**
##### [springBean 方法初始化加载顺序](https://blog.csdn.net/varyall/article/details/82257202):结果如下
##### 测试过程如下:(代码很简单,略)
> 新建bean1 实现InitializingBean, DisposableBean,SmartInitializingSingleton重写相关方法,构造方法等
>
> 新建bean2 实现BeanPostProcessor,实现接口方法
>
> 新增(@Configuration)bean3 配置@bean1(指定初始化/销毁方法),@bean2
>
> run&close springbootApplication
##### 非常重要的结论关于bean初始化和销毁过程中调用的方法的顺序!!!!!!!!!!!!!!!!!!!!!!!!!
```
1. 构造方法
1.1 BeanPostProcessor#postProcessBeforeInitialization
2. @PostConstruct修饰的方法
3. InitializingBean#afterPropertiesSet
4. 指定的init函数
4.1 BeanPostProcessor#postProcessAfterInitialization
5. SmartInitializingSingleton#afterSingletonsInstantiated
6. DisposableBean#destroy
7 指定的销毁方法
```
### 四 shiro的LifecycleBeanPostProcessor到底做了什么?
#### realm的集成结构
> AuthorizingRealm extends AuthenticatingRealm
> implements Authorizer, **Initializable**, PermissionResolverAware, RolePermissionResolverAware
#### LifecycleBeanPostProcessor前置处理方法如下(截取部分代码)
```
public class LifecycleBeanPostProcessor implements DestructionAwareBeanPostProcessor, PriorityOrdered {
//把本后置处理器在PriorityOrdered级别中优先级置为最低
public LifecycleBeanPostProcessor() {
this(LOWEST_PRECEDENCE);
}
//前置处理逻辑
public Object postProcessBeforeInitialization(Object object, String name) throws BeansException {
if (object instanceof Initializable) {
((Initializable) object).init();
}
return object;
}
}
```
1. 判断对象是否是Initializable的实例,是的话调用init()方法。
2. 由于Realm符合这个条件,因此会调用realm的init方法,从而触发realm的实例化。
3. 由于自动一relm中注入了授权相关的service(如UserService), 从而导致相关service初始化。
**结论:LifecycleBeanPostProcessor会导致realm中注入的service提前初始化。**
**另外:LifecycleBeanPostProcessor实现了PriorityOrdered,并把order置为最低,表示在所有后置处理器中优先级很高(而且高于同级别的实现PriorityOrdered的处理器)**
### 五 [BeanPostProcessor的处理时机](https://chinalhr.github.io/post/springcache_shrio_bug)
> **再次回顾下bean初始化和销毁的执行顺序**
BeanPostProcessor本身也是一个Bean, 那么它的初始化时机是什么呢?
> AbstractApplicationContext#refresh()——>registerBeanPostProcessors(beanFactory)方法会注册BeanPostProcessors:
源码略,摘录部分注释如下:
```js
注册BeanPostProcessorChecker,它在以下情况下记录信息消息:
bean是在BeanPostProcessor实例化期间创建的,即
bean不适合由所有beanPostProcessor处理。
检查可在当前Bean上起作用的BeanPostProcessor个数与总的BeanPostProcessor个数,
如果起作用的个数少于总数打印:
xxx is not eligible for getting processed by all BeanPostProcessors
(for example: not eligible for //auto-proxying)
根据是否实现 PriorityOrdered,Ordered, and the rest来区分BeanPostProcessors
1,注册实现 PriorityOrdered BeanPostProcessors
PriorityOrdered类型的BeanPostProcessor会预初始化
2,注册实现 Ordered BeanPostProcessors
3 注册所有无序(没有实现Ordered/ PriorityOrdered) BeanPostProcessors.
4, 注册所有内部(MergedBeanDefinitionPostProcessor) BeanPostProcessors.
```
**结论:BeanPostProcessor注册顺序如下:**
1. 实现了PriorityOrdered接口的BeanPostProcessor()
2. 实现了Ordered接口的BeanPostProcessor
3. 注册无实现任何接口的BeanPostProcessor
4. 实现了MergedBeanDefinitionPostProcessor接口的BeanPostProcessor
#### 关于PriorityOrdered:
> 实现了PriorityOrdered的BeanPostProcessor先于其他BeanPostProcessor,并会影响到其他BeanPostProcessor的autowiring behavior(参见PriorityOrdered接口上的注释)
```JAVA
public interface PriorityOrdered extends Ordered {
}
```
- Ordered 接口用于spring中相同接口实现类的的排序,
- Ordered 有多个实现类,PriorityOrdered类型的实现类,优先级更高;
### 六 关于springcache是如何初始化的?
#### 6.1 以springmvc的配置为开始:如何开启springcache
1. 配置<cache:annotation-driven />
2. 配置一个cacheManager
#### 6.2 从<cache:annotation-driven />标签的解析器开始
spring标签的解析器全都实现了`NamespaceHandlerSupport`(来自spring-beans包)


其中 CacheNamespaceHandler就是用于解析<cache:annotation-driven />标签
```java
public class CacheNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("annotation-driven", new
AnnotationDrivenCacheBeanDefinitionParser());
registerBeanDefinitionParser("advice", new CacheAdviceParser());
}
}
```
根据源码,可以看出将下列重要类注册到容器中:
* **InfrastructureAdvisorAutoProxyCreator**
* **AnnotationCacheOperationSource**
* **CacheInterceptor(方法的拦截器,缓存的逻辑实现)**
* **BeanFactoryCacheOperationSourceAdvisor(重点关注这个,这个类在创建代理的时候被使用)**
#### 6.3 InfrastructureAdvisorAutoProxyCreator:cache逻辑的后置处理器
InfrastructureAdvisorAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor接口(此接口集成了BeanPostProcessor接口)
> 回顾下前文的类生命周期执行顺序
**InfrastructureAdvisorAutoProxyCreator**部分源码摘录:
```java
//执行顺序对应上文的 4.1 BeanPostProcessor#postProcessAfterInitialization, 在初始化其他bean的时候织入此逻辑
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
...
// 1 获取当前类的所有切面拦截类
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//2 如果拦截类不为空,则需要创建当前类的代理类
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//3.创建代理类
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
```
1. 在获取当前类的所有切面拦截器的时候会获取所有的**Advisor**,然后过滤出符合当前场景的,也即`BeanFactoryCacheOperationSourceAdvisor`
2. 创建的代理类,在执行缓存功能的时候,调用代理类的invoke()方法,在invoke()方法中调用CacheInterceptor拦截器的execute()方法,拦截器会使用缓存器(本例中的SimpleCacheManager)来进行具体方法实现。
##### 跳过代码细节,给出的结论是
> **1)解析<cache:annotation-driven />,将InfrastructureAdvisorAutoProxyCreator注入到Spring容器中,该类的作用是在Spring创建bean实例的时候,会执行其postProcessAfterInitialization()方法,生成bean实例的代理类**
>
> **2)解析<cache:annotation-driven />,将BeanFactoryCacheOperationSourceAdvisor类注入到Spring容器中,该类的主要作用是作为一个Advisor添加到上述代理类中**
>
> **3)BeanFactoryCacheOperationSourceAdvisor类拥有对CacheInterceptor的依赖,CacheInterceptor作为一个方法拦截器,负责对缓存方法的拦截,**
>
>
>
> **4)当前类方法调用被拦截到CacheInterceptor后,CacheInterceptor会调用我们在配置文件中配置的CacheManager实现(也就是本例中的SimpleCacheManager),来真正实现缓存功能**
### 七 springboot的缓存原理类似springmvc
> 从@EnableCaching开始,
>
> 引入了@Import({CachingConfigurationSelector.class})
>
> 这个类添加了AutoProxyRegistrar.java,ProxyCachingConfiguration.java两个类
过程略,参见文末的“本文参考”。
### 八 spring(mvc) 事务的启动原理
了解了springcache的原理之后,事务的原理就呼之欲出了,二者基本都是一个理念都是一致的。
#### 8.1 TxNamespaceHandler为入口(tx:annotation-driven)
```java
public class TxNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("jta-transaction-manager", new
JtaTransactionManagerBeanDefinitionParser());
}
}
```
* AnnotationDrivenBeanDefinitionParser有个静态内部类AopAutoProxyConfigurer (对应的model 为proxy模式)
* AopAutoProxyConfigurer 注册了四个类
* InfrastructureAdvisorAutoProxyCreator:是不是很眼熟,和springcache那里是一样的
* AnnotationTransactionAttributeSource:解析事务类,得到事务配置相关信息
* TransactionInterceptor:事务拦截器,实现了 Advice、MethodInterceptor 接口。
* BeanFactoryTransactionAttributeSourceAdvisor::实现了 PointcutAdvisor 接口,依赖 TransactionInterceptor 和 TransactionAttributeSourcePointcut。
* 通过spring生成的代理类,在执行事务方法其实调用的是TransactionInterceptor的invoke()方法,类似**CacheInterceptor**
* 而在TransactionInterceptor的invoke()方法中就是大家熟悉的事务管理器PlatformTransactionManager等相关bean了,参见我些的另外一篇关于spring事务的源码的文章([spring事务入口及核心类](http://www.xuqiudong.cn/detail/7))。
### 九 总结shiro造成springcache和spring事务失效的原因
1. shiro配置的LifecycleBeanPostProcessor实现了BeanPostProcessor,并且在前置处理(#postProcessBeforeInitialization)逻辑中调用了实现Initializable(shiro的)接口的javaBean的init方法,Realm刚好实现了这个接口,因此,造成Realm的提前初始化,同时造成注入Realm中的bean的提前初始化。
* 这个顺序对应上文中的顺序号为**1.1**(非常的提前了,仅次于构造函数)
2. **springcache和spring事务的代码织入时机在InfrastructureAdvisorAutoProxyCreator类的后置处理(#postProcessAfterInitialization)逻辑中产生**
1. #wrapIfNecessary();
2. 这个顺序对应上文中的顺序号为**4.1**
3. 其中LifecycleBeanPostProcessor实现了PriorityOrdered,InfrastructureAdvisorAutoProxyCreator实现了Ordered,(PriorityOrdered的优先级大于Ordered),不过这么用不到,因为shiro的是前置处理器,cache和tx是后置处理器。
3. 因此,LifecycleBeanPostProcessor的前置处理器先执行,造成Realm中的注入的service提前处理化,没有经过cache和tx的后置处理器,因而会导致缓存和事务失效。
4. 所以,在注入Realm中的service上加上**@Lazy**注解,让它延迟加载是一个不错的处理办法;
### TODO
被BeanPostProcessor 提前初始化的bean还会进入其他BeanPostProcessor 吗?
### 这是一篇没有完成的文章,我还有很多疑问待校验
> 主要的原因是对很多原理性的东西不熟悉, 我会在后续的时间里,慢慢的一点点的补充吧,
----
### 本文参考:
* [Spring中Bean的生命周期以及三级缓存介绍](https://blog.csdn.net/qq_33808244/article/details/102453052)
* [聊一聊 Spring 中的扩展机制(二) - NamespaceHandler](https://blog.csdn.net/weixin_33735077/article/details/87978434)
* [springcache源码深度分析](https://juejin.im/entry/6844903776860536840)
* [spring AOP功能源码解析](https://blog.csdn.net/qq_26323323/article/details/81012855)
* [spring aop —— 深入理解advisor](https://www.jianshu.com/p/2250b24a3f7d)
* [BeanPostProcessor启动阶段对其依赖的Bean造成的影响](https://chinalhr.github.io/post/springcache_shrio_bug)
* [Spring Boot缓存源码分析](https://segmentfault.com/a/1190000017006525)