集成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中使用切面。轻喷。。

原文地址:https://www.cnblogs.com/guyaoblog/p/13043261.html