由于整合shiro导致的BeanNotOfRequiredTypeException:Caused by: ****but was actually of type 'com.sun.proxy.$Proxy**
问题发现
前几天接到一个任务,分析后设计了一个模版类A,子类AA、AB等,A及其子类未实现任何接口。A类通用方法上添加事务注解@Transactional;通过在子类上添加@Component注解将其注入Spring容器,引用时通过父类A接收。运行项目时抛出如下异常:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'aA' is expected to be of type 'A' but was actually of type 'com.sun.proxy.$Proxy**。
意思是$Proxy**代理类不能转换为实体类型A,当时我就奇怪了,spring生成动态代理的基本原则为类实现接口则用jdk动态代理,未实现则用cglib动态代理。我这A及其子类一个接口未实现咋还生成了jdk代理类了呢?
查找根源
根据本人经验,代理类生成的方式为后处理器PostProcessor通过postProcessAfterInitialization(Object bean, String beanName)方法生成。于是我在后处理器调用处(调用处不止这一处,为了解决循环引用问题,beanfactory还在getEarlyBeanReferences方法处调用了后处理器):AbstractAutowireCapableBeanFactory的applyBeanPostProcesssorAfterInitialization(Object existingBean, String beanName)处打断点查看如下:
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
进入getBeanPostProcessors()方法查看:
好家伙,一共13个后处理器,其中编号5、6、7为代理相关。于是我一个个的查看,发现经过编号为5的后处理器处理后生成了cglib代理类,但是在通过编号7后处理器处理后就生成了jdk代理类!
原来如此,类A及其子类AA、AB等未实现接口,因而在通过后处理器InfrastructureAdvisorAutoProxyCreator时生成了cglib代理类,这里称为enhance1,而通过AppConfig$1@7288时,则是对enhance1再次加强。cglib代理类enhance1则是实现了三个接口(我这里忽略接口描述),自然而然的就生成了jdk代理类$Proxy**。$Proxy**当然不能转换为A了,因为它和A及其实现类并无直接关系。
通过查询代码发现:
编号5的后处理器为开启事务注解@EnableTransactionManagement添加。编号6为开启spring异步执行功能注解@EnableAsync添加(应该是只@Async这类注解生效,所以这里排除它的作案可能)。编号7为自己创建的AppConfig类中注入的DefaultAdvisorAutoProxyCreator,注入这个类的原因是需要将shiro和spring进行整合,开启shiro的注解功能。
哦,原来是因为整合shiro时添加了DefaultAdvisorAutoProxyCreator这个bean,导致为没实现接口的bean生成了jdk代理类(或许有的人会说shiro根本不需要手动注入DefaultAdvisorAutoProxyCreator,shiro-spring.jar的config文件夹下某个配置类中已经注入了这个bean。我想说的是:那也得看版本啊大哥,我这是shiro-spring-1.3.2.jar,根本没有你们说的配置类,需要自己手动添加)。
解决方案
问题已经找到了,那么就寻找解决方案(ps:我个人觉得寻找解决问题的方法时,可以从时间(或程序流先后步骤)的角度将这个问题看作一条时间线,在这条时间线中从各个可能的时间节点去寻找突破口,时间节点寻找因人而异)。比如这里,时间开始节:万恶之源为添加了DefaultAdvisorAutoProxyCreator这个bean,最硬核的方式就是将DefaultAdvisorAutoProxyCreator这个bean去掉,那么就正常了,不过这不太好吧阿sir。。。A及其子类AA等的实例生成的时间节点:这里我们可以在A的子类上添加@Scope(ProxyMode=ScopeProxyMode.TARGET_CLASS)强制其代理方式为cglib(@Scope不能继承,不然我就添加在A类上了,一步到位),或者可以通过javaassist的方式动态的给A的子类添加@Scope注解。代理生成的时间节点:我们可以在代理生成的时候通过判断被代理类的类型来决定是否代理,或代理方式即可。
方案分析:
1.去掉DefaultAdvisorAutoProxyCreator
不可取,如果不用shiro的注解可行。
2.A类及子类添加@Scope(ProxyMode=ScopeProxyMode.TARGET_CLASS)
可行,但是创建新子类的时候可能忘了添加该注解。
3.在代理生成的时候判断
可行,自定义DefaultAdvisorAutoProxyCreator,可以在DefaultAdvisorAutoProxyCreator的wrapIfNecessary方法处判断是否需要代理,或者在customizeProxyFactory方法处判断是否setTargetClass为true即可(为true则强制为cglib代理,false则按照基本规则来代理)。缺点是代理有需要生成cglib代理的类需要手动添加。
这里我采取了方案3,代码如下:
@Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { return new DefaultAdvisorAutoProxyCreator() { @Override protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//因为得到的bean可能是factorybean或者经过代理后的bean,不好手动判断其类型,所以通过beanFactory自带的isTypeMatch方法来判断,万无一失 if(getBeanFactory().isTypeMatch(beanName, TimerTask.class)) return bean; return super.wrapIfNecessary(bean, beanName, cacheKey); } }; }