媒介
在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);
}
}