zoukankan      html  css  js  c++  java
  • 集成Shiro导致部分bean代理失效问题记录以及解决

    问题:

      最近因为业务需要,在项目中获取用户信息时需要做特殊处理,于是本人想到了使用AOP的方式来实现。但是在实现的过程中发现@Before注解对UserDao失效,对于其它Bean则能正常使用(before1不生效,before2生效)。注解如下:

    集成shiro的代码:

     shiroRealmManager依赖了userDao:

    解决过程:

      首先,既然对其它bean做aop有效,那么配置就不存在问题。

      其次,面向切面编程是使用动态代理来实现的,因此在调用userDao时使用的应该是代理对象,但是通过调试发现事实并非如此。

      

      因此将问题锁定在了UserDao的初始化上。

    补充一点:本人使用@EnableAspectJAutoProxy注解的方式启用@AspectJ支持,而@EnableAspectJAutoProxy注解会注入AspectJAutoProxyRegistrar,AspecttJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,

    查看ImportBeanDefinitionRegistrar的注释:Interface to be implemented by types that register additional bean definitions(注册额外的bean定义使用),了解到该接口用于需要注册额外bean 定义的实现类使用,AspecttJAutoProxyRegistrar实现如下:

    跟踪到底,这里注册了一个名为org.springframework.aop.config.internalAutoProxyCreator的bean定义,bean定义的类为AnnotationAwareAspectJAutoProxyCreator,该类实现了BeanPostProcessor

     补充结束。既然是bean的生成过程出了问题,那么就去创建bean的源码里面找。通过断点调试发现在创建创建bean之后,AbstractAutowireCapableBeanFactory中会有这么一段代码:

    其它能够面向切面编程的bean生成代理对象的地方就在这里,而UserDao在这里并没有生成代理对象。那么问题就出在这里(曾经粗略看过spring源码,知道bean的大致生成过程,不然能不能调试到这里还是个问题。。心累)!并且日志输出

    Bean 'userDaoImpl' of type ... is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

    重点就在于initializeBean这个方法,见名思义,这个方法做的事情大致就是初始化创建好的Bean,进入该方法,里面有两个重要的过程applyBeanPostProcessorsBeforeInitialization(bean前置处理过程)和applyBeanPostProcessorsAfterInitialization(bean后置处理过程),在AOP中,这两个方法就是将生成的Bean包装为代理对象的方法:

    进入上面两个方法任一个发现正常情况下(能够生成代理)beanPostProcessors有14个,而处理UserDao时只有9个。

    这里就涉及到context初始化的过程了,在应用程序启动时,AbstractApplicationContext的refresh方法会被调用(本项目依赖于Servlet3.0的SPI机制启动:),这里重点在于解决aop失效问题,就不深入应用程序启动过程了,以后有空再写),注意,在这个过程中会向BeanFactory注册BeanPostProcessor。代码如下:

    这里的注册过程分为多步,如下:

    首先,从容器中获取BeanPostProcessor类型的bean名称:

     注册BeanPostProcessorChecker,上面UserDao后处理时打印的...not eligible for auto-proxying就是通过BeanPostProcessorChecker打印的。注释也说明了它的功能。

    然后,将实现了PriorityOrdered、实现了Ordered和其它的PostBeanProcessor或它的名称分别放入三个容器中

     接下来注册三个容器中的BeanPostProcessor(BPP):

    先注册实现了PriorityOrdered的BPP,注意,真正注册BPP的方法是registerBeanPostProcessors。

    再注册实现了Ordered的BPP

     最后注册其它BPP

    以上的BPP都在BeanFactory中注册,在bean初始化过程中被initializeBean方法调用。而通过断点调试发现在注册实现Ordered的BPP 前BeanFactory中注册的BPP刚好为9个(和初始化UserDao时BeanFactory中BPP的个数一样),而注册之后就变为13个了。

    实现Ordered的BPP注册前:

    实现Ordered的注册后: 

     并且,上面补充的使@AspectJ生效的BPP(internalAutoProxyCreator)就实现了Ordered接口,同时也是在这一步注册的。

    那么问题必然出现在这两个时间点之间。通过断点调试的方式证明确实如此,UserDao实例化的时间点就在这之间,而且是在创建一个名为org.springframework.context.annotation.internalAsyncAnnotationProcessor的bean时被依赖到的。

    在BeanFactory中,org.springframework.context.annotation.internalAsyncAnnotationProcessor对应的mbd(bean定义)中使用的时ProxyAsyncConfiguration,在该类的父类AbstractAsyncConfiguration中依赖了AsyncConfigurer,如图:

     在创建ProxyAsyncConfiguration时就会根据类型去获取AsyncConfigurer,关键点就在这里:

     以上代码根据类型在BeanFactory中获取对应的Bean,这里遍历了beanFactory中所有注册的bean名称(上图for 循环),并调用了isTypeMatch方法,进入isTypeMatch方法中查看,有这么一段代码

     这里如果遍历的bean是FactoryBean类型就查看该factoryBean创建的是什么类型,并调用getTypeForFactoryBean方法校验(集成shiro时正好注册了一个FactoryBean类型的ShiroFilterFactoryBean)该方法执行了getSingletonFactoryBeanForTypeCheck,在getSingletonFactoryBeanForTypeCheck中,会先尝试能否通过该factoryBean获取生成的对象类型,如果找不到且创建该FactoryBean的bean工厂(我这里是带有@Configuration的配置类mvcconfig)没有初始化就执行创建factoryBean对象的方法

     此处就创建了名为shiroFilter的ShiroFilterFactoryBean对象。好了,到这里已经有足够的证据找出真凶,就不往下走代码了。

    让我们理一理目前得到的信息:

    1.bean初始化会执行BeanFactory中注册的BPP;

    2.BPP会分为3个阶段注册:注册实现了PriorityOrdered的BPP、注册实现Ordered的BPP和注册其它BPP,第一个阶段注册的BPP能在第二阶段使用,第二阶段注册的BPP能在第三阶段使用,第三阶段注册的BPP能给还未生成的 Bean对象使用;

    3.注册BPP时会创建BPP对象以及相关依赖,在根据类型注入依赖时会判断注册的bean是否实现了FactoryBean,如果实现了并且无法通过现有条件找到该FactoryBean返回的对象类型,就会创建该bean。

    综上所诉,在注册实现Ordered的BPP时会创建名称为org.springframework.context.annotation.internalAsyncAnnotationProcessor(ProxyAsyncConfiguration)的BPP,而创建ProxyAsyncConfiguration时依赖AsyncConfigurar,就通过类型在BeanFactory中查找实现了AsyncConfigurar接口的类型,在验证到shiroFilter时,shiroFilter为FactoryBean且通过bean定义无法识别该FactoryBean对象返回的类型,因此创建了该对象,而shiroFilter间接依赖了本项目中实现了AuthorizingRealm的ShiroRealmManager,而ShiroRealmManager又依赖了UserDao,因此,UserDao在这个时间被提前初始化了。导致实现了Ordered和其它类型的BPP无法处理UserDao,最终使得AOP功能失败。

    解决:

      既然问题已经发现了,那么解决起来就好办了,通过反向查找,ProxyAsyncConfiguration是通过@EnableAsync注入的,因此去掉该注解就不会提前去加载UserDao了(但是这种方法治标不治本,有可能其它实现了Ordered的BPP会通过类型在BeanFactory中查找),或者在ShiroRealmManager中使用@Autowired依赖UserDao时加上@Lazy注解(推荐),lazy注解在autowired时做判断,该判断在DefaultListableBeanFactory的resolveDependency方法中:

      

    总结:

       1.shiro和spring不太兼容,还是springsecurity好用;2.开发的过程中要注意启动日志里面的warn信息,比如这次打印的Bean 'userDaoImpl' of type ... is not eligible for getting processed by all BeanPostProcessors就被我忽略了(不然解决这个问题也不会这么麻烦);3.多看看Spring文档,Spring文档中就有相关警示(虽然不是此次问题所在):

     大意是SpringAop就是通过BeanPostProcessor实现的,BeanPostProcessor和其间接引用的bean都不适用于自动代理,因此,别在这些bean中使用切面。轻喷。。

  • 相关阅读:
    yii2增删改查及AR的理解
    yii2中关联查询
    yii2常用的migrate命令
    有线电视网
    选课
    没有上司的舞会
    [ZJOI2008]骑士
    【模板】树链剖分
    [ZJOI2008]树的统计
    [NOI2015]软件包管理器
  • 原文地址:https://www.cnblogs.com/guyaoblog/p/13043261.html
Copyright © 2011-2022 走看看