aspect拦截类_接⼝⽅法上的注解⽆法被@Aspect声明的切⾯
springboot aop拦截的原因分析
前⾔
在Spring中使⽤MyBatis的Mapper接⼝⾃动⽣成时,⽤⼀个⾃定义的注解标记在Mapper接⼝的⽅法中,再利⽤@Aspect定义⼀个切⾯,
拦截这个注解以记录⽇志或者执⾏时长。但是惊奇的发现这样做之后,在Spring Boot 1.X(Spring Framework 4.x)中,并不能⽣效,⽽
在Spring Boot 2.X(Spring Framework 5.X)中却能⽣效。
这究竟是为什么呢?Spring做了哪些更新产⽣了这样的变化?此⽂将带领你探索这个秘密。
案例
核⼼代码
@SpringBootApplicationpublic class Starter { public static void main(String[] args) { SpringApplication.r
un(DynamicApplication.class, args); }}@Servicepubl
测试类
@RunWith(SpringRunner.class) @SpringBootTest(classes = Starter.class)public class BaseTest { @Autowired DemoService demoService; @Test public v
在Spring Boot 1.X中,@Aspect⾥的两个println都没有正常打印,⽽在Spring Boot 2.X中,都打印了出来。
调试研究
已知@Aspect注解声明的,会⾃动切⼊符合其拦截条件的Bean。这个功能是通过@EnableAspectJAutoProxy注解来启⽤和配置的
(默认是启⽤的,通过AopAutoConfiguration),由@EnableAspectJAutoProxy中的@Import(AspectJAutoProxyRegistrar.class)可
知,@Aspect相关注解⾃动切⼊的依赖是AnnotationAwareAspectJAutoProxyCreator这个BeanPostProcessor。在这个类的postProcessAfterInitialization⽅法中打上条件断点:beanName.equals("demoMapper")
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean != null) { // 缓存中尝试获取,没有则尝试包装Object c 在wrapIfNecessary⽅法中,有⾃动包装Proxy的逻辑:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 如果是声明的需要原始Bean,则直接返回if (beanName != null && this.targ
调试发现,Spring Boot 1.X中specificInterceptors返回为空,⽽Spring Boot 2.X中则不是空,那么这⾥就是问题的核⼼点了,查看源
码:
protected Object[] getAdvicesAndAdvisorsForBean(Class> beanClass, String beanName, TargetSource targetSource) {List advisors = findEligibleAdvisors(
这⾥的核⼼问题在于AopUtils.findAdvisorsThatCanApply⽅法,这⾥的返回在两个版本是不⼀样的,由于这⾥代码过多就不贴上来了,
说明下核⼼问题代码是这段:
/
/ AopProxyUtils.javapublic static List findAdvisorsThatCanApply(List candidateAdvisors, Class> clazz) { // ... 省略 for (Advisor candidate : candidateAdvisors) { if (c 基本定位了问题点,看下最终调⽤的canApply⽅法,Spring Boot 1.X与2.X这⾥的代码是不⼀样的
1. Spring Boot 1.X中源码,即Spring AOP 4.X中源码
/** * targetClass是com.sun.proxy.$Proxy??即JDK动态代理⽣成的类 * hasIntroductions是false,先不管 */public static boolean canApply(Pointcut pc, Class> targetC
1. Spring Boot
2.X中源码,即Spring AOP 5.X中源码
public static boolean canApply(Pointcut pc, Class> targetClass, boolean hasIntroductions) {Null(pc, "Pointcut must not be null");if (!pc.getClassFi
调试信息图
上⾯的代码执⾏结果不同,但是区别只是少个动态代理⽣成的类进⾏遍历,为什么少⼀个遍历内容结果却是true呢?肯定是introductionAwareMethodMatcher或者methodMatcher的逻辑有改动,其中methodMatcher和
introductionAwareMethodMatcher是同⼀个对象,两个⽅法逻辑相同。看代码:
/** AspectJExpressionPointcut.java * method是上⾯接⼝中遍历的⽅法,targetClass是⽬标class,即⽣成的动态代理class */public boolean matches(Method method
这段代码在Spring Boot 1.X和2.X中基本是相同的,但是在MostSpecificMethod(method, targetClass);这⼀句的执⾏结
果上,两者是不同的,1.X返回的是动态代理⽣成的Class中重写的接⼝中的⽅法,2.X返回的是原始接⼝中的⽅法。
⽽在动态代理⽣成的Class中重写的接⼝⽅法⾥,是不会包含接⼝中的注解信息的,所以Aspect中条件使⽤注解在这⾥是拿不到匹配信息
的,所以返回了false。
⽽在2.X中,因为返回的是原始接⼝的⽅法,故可以成功匹配。
问题就在于MostSpecificMethod(method, targetClass)的逻辑:
// 1.Xpublic static Method getMostSpecificMethod(Method method, Class> targetClass) { // 这⾥返回了targetClass上的重写的method⽅法。Method resolvedMetho ⾄此原因已经完全明了,Spring在AOP的5.X版本修复了这个问题。
影响范围
原因已经查明,那么根据原因我们推算⼀下影响范围
1. Bean是接⼝动态代理对象时,且该动态代理对象不是Spring体系⽣成的,接⼝中的切⾯注解⽆法被拦截
2. Bean是CGLIB动态代理对象时,该动态代理对象不是Spring体系⽣成的,原始类⽅法上的切⾯注解⽆法被拦截。
3. 可能也影响基于类名和⽅法名的拦截体系,因为⽣成的动态代理类路径和类名是不同的。
如果是Spring体系⽣成的,之前拿到的都是真实类或者接⼝,只有在⽣成动态代理后,才是新的类。所以在创建动态代理时,获取的是真实
的类。
接⼝动态代理多见于ORM框架的Mapper、RPC框架的SPI等,所以在这两种情况下使⽤注解要尤为⼩⼼。
有些同学⽐较关⼼@Cacheable注解,放在Mapper中是否⽣效。答案是⽣效,因为@Cacheable注解中使⽤的不是@Aspect的
PointCut,⽽是CacheOperationSourcePointcut,其中虽然也使⽤了getMostSpecificMethod来获取method,但是最终其实⼜从原
始⽅法上尝试获取了注解:
// AbstractFallbackCacheOperationSourceputeCacheOperationsif (specificMethod != method) {// Fallback is to look at the original methodopDef = find 看似不受影响,其实是做了兼容。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。