由LifecycleBeanPostProcessor引起spring缓存失效说起

文章来源原创   作者:许秋冬   发布时间:2020-08-20   阅读:1709   标签:源码,事务

[toc]

由LifecycleBeanPostProcessor引起spring缓存失效说起

由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

方式二

方式三:

  • 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 方法初始化加载顺序:结果如下
测试过程如下:(代码很简单,略)

新建bean1 实现InitializingBean, DisposableBean,SmartInitializingSingleton重写相关方法,构造方法等

新建bean2 实现BeanPostProcessor,实现接口方法

新增(@Configuration)bean3 配置@bean1(指定初始化/销毁方法),@bean2

run&close springbootApplication

非常重要的结论关于bean初始化和销毁过程中调用的方法的顺序!!!!!!!!!!!!!!!!!!!!!!!!!
  1. 1. 构造方法
  2. 1.1 BeanPostProcessor#postProcessBeforeInitialization
  3. 2. @PostConstruct修饰的方法
  4. 3. InitializingBean#afterPropertiesSet
  5. 4. 指定的init函数
  6. 4.1 BeanPostProcessor#postProcessAfterInitialization
  7. 5. SmartInitializingSingleton#afterSingletonsInstantiated
  8. 6. DisposableBean#destroy
  9. 7 指定的销毁方法

四 shiro的LifecycleBeanPostProcessor到底做了什么?

realm的集成结构

AuthorizingRealm extends AuthenticatingRealm
implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware

LifecycleBeanPostProcessor前置处理方法如下(截取部分代码)

  1. public class LifecycleBeanPostProcessor implements DestructionAwareBeanPostProcessor, PriorityOrdered {
  2. //把本后置处理器在PriorityOrdered级别中优先级置为最低
  3. public LifecycleBeanPostProcessor() {
  4. this(LOWEST_PRECEDENCE);
  5. }
  6. //前置处理逻辑
  7. public Object postProcessBeforeInitialization(Object object, String name) throws BeansException {
  8. if (object instanceof Initializable) {
  9. ((Initializable) object).init();
  10. }
  11. return object;
  12. }
  13. }
  1. 判断对象是否是Initializable的实例,是的话调用init()方法。
  2. 由于Realm符合这个条件,因此会调用realm的init方法,从而触发realm的实例化。
  3. 由于自动一relm中注入了授权相关的service(如UserService), 从而导致相关service初始化。

结论:LifecycleBeanPostProcessor会导致realm中注入的service提前初始化。

另外:LifecycleBeanPostProcessor实现了PriorityOrdered,并把order置为最低,表示在所有后置处理器中优先级很高(而且高于同级别的实现PriorityOrdered的处理器)

BeanPostProcessor的处理时机

再次回顾下bean初始化和销毁的执行顺序

BeanPostProcessor本身也是一个Bean, 那么它的初始化时机是什么呢?

AbstractApplicationContext#refresh()——>registerBeanPostProcessors(beanFactory)方法会注册BeanPostProcessors:

源码略,摘录部分注释如下:

  1. 注册BeanPostProcessorChecker,它在以下情况下记录信息消息:
  2. bean是在BeanPostProcessor实例化期间创建的,即
  3. bean不适合由所有beanPostProcessor处理。
  4. 检查可在当前Bean上起作用的BeanPostProcessor个数与总的BeanPostProcessor个数,
  5. 如果起作用的个数少于总数打印:
  6. xxx is not eligible for getting processed by all BeanPostProcessors
  7. (for example: not eligible for //auto-proxying)
  8. 根据是否实现 PriorityOrdered,Ordered, and the rest来区分BeanPostProcessors
  9. 1,注册实现 PriorityOrdered BeanPostProcessors
  10. PriorityOrdered类型的BeanPostProcessor会预初始化
  11. 2,注册实现 Ordered BeanPostProcessors
  12. 3 注册所有无序(没有实现Ordered/ PriorityOrdered) BeanPostProcessors.
  13. 4, 注册所有内部(MergedBeanDefinitionPostProcessor) BeanPostProcessors.

结论:BeanPostProcessor注册顺序如下:

  1. 实现了PriorityOrdered接口的BeanPostProcessor()
  2. 实现了Ordered接口的BeanPostProcessor
  3. 注册无实现任何接口的BeanPostProcessor
  4. 实现了MergedBeanDefinitionPostProcessor接口的BeanPostProcessor

关于PriorityOrdered:

实现了PriorityOrdered的BeanPostProcessor先于其他BeanPostProcessor,并会影响到其他BeanPostProcessor的autowiring behavior(参见PriorityOrdered接口上的注释)

  1. public interface PriorityOrdered extends Ordered {
  2. }
  • Ordered 接口用于spring中相同接口实现类的的排序,
  • Ordered 有多个实现类,PriorityOrdered类型的实现类,优先级更高;

六 关于springcache是如何初始化的?

6.1 以springmvc的配置为开始:如何开启springcache

  1. 配置<cache:annotation-driven />
  2. 配置一个cacheManager

6.2 从<cache:annotation-driven />标签的解析器开始

spring标签的解析器全都实现了NamespaceHandlerSupport(来自spring-beans包)

NamespaceHandlerSupport实现类

NamespaceHandlerSupport实现类

其中 CacheNamespaceHandler就是用于解析<cache:annotation-driven />标签

  1. public class CacheNamespaceHandler extends NamespaceHandlerSupport {
  2. @Override
  3. public void init() {
  4. registerBeanDefinitionParser("annotation-driven", new
  5. AnnotationDrivenCacheBeanDefinitionParser());
  6. registerBeanDefinitionParser("advice", new CacheAdviceParser());
  7. }
  8. }

根据源码,可以看出将下列重要类注册到容器中:

  • InfrastructureAdvisorAutoProxyCreator
  • AnnotationCacheOperationSource
  • CacheInterceptor(方法的拦截器,缓存的逻辑实现)
  • BeanFactoryCacheOperationSourceAdvisor(重点关注这个,这个类在创建代理的时候被使用)

6.3 InfrastructureAdvisorAutoProxyCreator:cache逻辑的后置处理器

InfrastructureAdvisorAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor接口(此接口集成了BeanPostProcessor接口)

回顾下前文的类生命周期执行顺序

InfrastructureAdvisorAutoProxyCreator部分源码摘录:

  1. //执行顺序对应上文的 4.1 BeanPostProcessor#postProcessAfterInitialization, 在初始化其他bean的时候织入此逻辑
  2. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  3. if (bean != null) {
  4. Object cacheKey = getCacheKey(bean.getClass(), beanName);
  5. if (!this.earlyProxyReferences.contains(cacheKey)) {
  6. return wrapIfNecessary(bean, beanName, cacheKey);
  7. }
  8. }
  9. return bean;
  10. }
  11. protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  12. ...
  13. // 1 获取当前类的所有切面拦截类
  14. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  15. //2 如果拦截类不为空,则需要创建当前类的代理类
  16. if (specificInterceptors != DO_NOT_PROXY) {
  17. this.advisedBeans.put(cacheKey, Boolean.TRUE);
  18. //3.创建代理类
  19. Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
  20. this.proxyTypes.put(cacheKey, proxy.getClass());
  21. return proxy;
  22. }
  23. this.advisedBeans.put(cacheKey, Boolean.FALSE);
  24. return bean;
  25. }
  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)

  1. public class TxNamespaceHandler extends NamespaceHandlerSupport {
  2. @Override
  3. public void init() {
  4. registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
  5. registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
  6. registerBeanDefinitionParser("jta-transaction-manager", new
  7. JtaTransactionManagerBeanDefinitionParser());
  8. }
  9. }
  • 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事务入口及核心类)。

九 总结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 吗?

这是一篇没有完成的文章,我还有很多疑问待校验

主要的原因是对很多原理性的东西不熟悉, 我会在后续的时间里,慢慢的一点点的补充吧,


本文参考:


发表评论

目录