原文出处:
光闪
前言
在Spring中使用MyBatis的Mapper接口自动生成时,用一个自定义的注解标记在Mapper接口的方法中,再利用@Aspect定义一个切面,拦截这个注解以记录日志或者执行时长。但是惊奇的发现这样做之后,在Spring Boot 1.X(Spring Framework 4.x)中,并不能生效,而在Spring Boot 2.X(Spring Framework 5.X)中却能生效。
这究竟是为什么呢?Spring做了哪些更新产生了这样的变化?此文将带领你探索这个秘密。
案例
核心代码
@SpringBootApplication public class Starter { public static void main(String[] args) { SpringApplication.run(DynamicApplication.class, args); } } @Service public class DemoService { @Autowired DemoMapper demoMapper; public List<Map<String, Object>> selectAll() { return demoMapper.selectAll(); } } /** * mapper类 */ @Mapper public interface DemoMapper { @Select("SELECT * FROM demo") @Demo List<Map<String, Object>> selectAll(); } /** * 切入的注解 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Demo { String value() default ""; } /** * aspect切面,用于测试是否成功切入 */ @Aspect @Order(-10) @Component public class DemoAspect { @Before("@annotation(demo)") public void beforeDemo(JoinPoint point, Demo demo) { System.out.println("before demo"); } @AfterDemo("@annotation(demo)") public void afterDemo(JoinPoint point, Demo demo) { System.out.println("after demo"); } }
测试类
@RunWith(SpringRunner.class) @SpringBootTest(classes = Starter.class) public class BaseTest { @Autowired DemoService demoService; @Test public void testDemo() { demoService.selectAll(); } }
在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 cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
在wrapIfNecessary方法中,有自动包装Proxy的逻辑:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 如果是声明的需要原始Bean,则直接返回 if (beanName != null && this.targetSourcedBeans.contains(beanName)) { return bean; } // 如果不需要代理,则直接返回 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } // 如果是Proxy的基础组件如Advice、Pointcut、Advisor、AopInfrastructureBean则跳过 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // Create proxy if we have advice. // 根据相关条件,查找interceptor,包括@Aspect生成的相关Interceptor。 // 这里是问题的关键点,Spring Boot 1.X中这里返回为空,而Spring Boot 2.X中,则不是空 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { /