1、描述
某次Debug的时候,发现项目中某个Bean只要使用了AOP代理,就会出现两次代理的情况。
2、测试
-
首先创建一个测试用的service,并加上使用AOP的注解(Transactional、Async、caching相关的注解都可以)
-
- 在上图测试类中:
- class2代表最终的代理类:
TestService$$EnhancerBySpringCGLIB$$5e29e70b
- class1代表class2的目标类,因为是多次代理,所以目标类还是一个代理类:
TestService$$EnhancerBySpringCGLIB$$8cdcc677
- class0才是最初代理的类,也就是
TestService
类。
- class2代表最终的代理类:
- 在上图测试类中:
-
- 其实debug testService bean 也能看出,有两个cglib的代理类。
3、原因
-
经过排查,发现在Shiro中配置了一个ProxyCreator[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
-
spirng aop config中,肯定有自己的ProxyCreator,但是项目中自己配置了Creator,并不会让spring自己的Creator失效,而是会出现两个ProxyCreator,所以一个Bean才会创建两次。
-
Spring自己的代理创建器经过各种委托,最终代码的实现在AopConfigUtils
@Nullable private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); // 如果有内部的 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } return null; } RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }
- DefaultAdvisorAutoProxyCreator是配置Shiro时添加的代理创建器
- AspectJAwareAdvisorAutoProxyCreator时spring自动添加的内部创建器
4、修复
原因找到了,那我们直接干掉Shiro中配置的代理创建器不就可以了。
删除Shiro的DefaultAdvisorAutoProxyCreator配置
此时启动项目,却报错了
goodsService无法作为GoodsService注入,因为goodsService是一个jdk动态代理类实现了:空?
给的建议是让我们强制使用cglib代理。
但是根据建议无论如何设置,都无效。依旧会报错。
新问题的原因
这时候另一个坑出现了,因为项目使用的是Shrio starter,在ShiroAnnotationProcessorAutoConfiguration
自动配置类里,又发现了熟悉的创建器,DefaultAdvisorAutoProxyCreator
可以看到,super类直接new了DefaultAdvisorAutoProxyCreator
。而且我们已经删除了自己配置的代理创建器,所以@ConditionalOnMissingBean注解就会生效,将DefaultAdvisorAutoProxyCreator放到容器中。
- 既然跟之前情况一样,有两个代理创建器,那为什么项目没有启来?
- 第一次代理,使用的cglib(TestService并没有实现接口),cglib是通过代理类去继承目标类,然后重写其中目标类的方法,但是也会实现某些接口,比如Factory。
- 第二次代理,代理的是第一次代理的代理类,发现第一次代理的代理类有接口,然后代理创建器就会用JDK的动态代理来实现,JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了。但是此时的接口是cglib创建出来的,并没有我们想要的方法。所以此次的代理类会实现Factory,与目标类没有啥关系。但是这并不是注入失败的原因,失败的原因是JDK动态代理的类,相当于和目标类是 兄弟类,也就是代理类和目标类实现了共同的接口,所以将代理类注入给目标类肯定无法注入。
- 相当于A类和B类共同实现了C类,此时我要A a = new B(),当然是不行的
5、最终解决
此时我们再排除Shiro的autoConfiguration就可以了
在启动类的@SpringBootApplication
注解中添加 exclude = {ShiroAnnotationProcessorAutoConfiguration.class}
6、备注
感觉对于AOP和Spring源码类理解的还是不到位。如有问题,请随时联系!