我读[不懂]源码

由LifecycleBeanPostProcessor引起spr

2020-08-20  本文已影响0人  临窗旋墨

[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理论上差不多

二 解决方案

方式一

方式二

方式三:

三 前置条件

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.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的处理时机

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

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

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

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

注册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接口上的注释)

public interface PriorityOrdered extends Ordered {
}

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

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

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

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

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

NamespaceHandlerSupport实现类

[图片上传失败...(image-3ec3a-1597890905331)]

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

public class CacheNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("annotation-driven", new 
                                     AnnotationDrivenCacheBeanDefinitionParser());
        registerBeanDefinitionParser("advice", new CacheAdviceParser());
    }
}

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

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

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

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

InfrastructureAdvisorAutoProxyCreator部分源码摘录:

//执行顺序对应上文的 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)

public class TxNamespaceHandler extends NamespaceHandlerSupport {


    @Override
    public void init() {
        registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("jta-transaction-manager", new 
                                     JtaTransactionManagerBeanDefinitionParser());
    }

}

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

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

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


本文参考:

文章来源:临窗旋墨的博客

上一篇下一篇

猜你喜欢

热点阅读