媒介
在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) { // 返回不是null,则需要署理 this.advisedBeans.put(cacheKey, Boolean.TRUE); // 放入缓存 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); // 自动生成署理实例 this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
调试发明,Spring Boot 1.X中specificInterceptors返回为空,而Spring Boot 2.X中则不是空,那么这里就是问题的焦点点了,昆山软件开发,查察源码:
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) { List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName); if (advisors.isEmpty()) { // 假如是空,则不署理 return DO_NOT_PROXY; } return advisors.toArray(); } protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { // 找到当前BeanFactory中的Advisor List<Advisor> candidateAdvisors = findCandidateAdvisors(); // 遍历Advisor,按照Advisor中的PointCut判定,返回所有符合的Advisor List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); // 扩展advisor列表,这里会默认插手一个ExposeInvocationInterceptor用于袒露动态署理工具,之前文章有表明过 extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { // 按照@Order可能接口Ordered排序 eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; } protected List<Advisor> findAdvisorsThatCanApply( List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) { ProxyCreationContext.setCurrentProxiedBeanName(beanName); try { // 真正的查找要领 return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); } finally { ProxyCreationContext.setCurrentProxiedBeanName(null); } }