去年写过Spring AOP原理和源码的文章:
Spring AOP从原理到源码(一)
Spring AOP从原理到源码(二)
Spring AOP从原理到源码(三)
Spring AOP从原理到源码(四)
时至今日,我可能已经不记得具体每一行源码是怎样的,但是Spring AOP的原理和流程还记得清清楚楚。这不,最近写了一个纯AspectJ的项目,打包成了jar包,放到一个webflux工程里发现jar包里定义的切面类压根不起作用。
本着对AOP原理比较熟,而且借助调试技巧十多分钟就定位出问题所在了。
问题原因
先说一下问题定位的结果:因为jar包被AspectJ项目的ajc编译器编译过,所以spring判断这个类是否为一个切面时判了死刑,认为它不能成为一个切面类。大概就是这样一段代码。
// AbstractAspectJAdvisorFactory#isAspect
@Override
public boolean isAspect(Class<?> clazz) {
// 成为切面的条件,有@AspectJ注解,同时不能是ajc编译出来的类
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
问题定位记录
下面记录一下问题定位过程。
从@EnableAspectJAutoProxy入手
@EnableAspectJAutoProxy
本质就是引入了AspectJAutoProxyRegistrar
,注入了一个AnnotationAwareAspectJAutoProxyCreator
(一个BeanPostProcessor
)。
所以跟踪AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization
方法即可。
跟到
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
发现返回来的拦截器没有那个被@AspectJ注解的类,这就找到问题原因了。
继续跟下去。
跟到BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
方法时就发现spring在第一次创建代理对象时就将所有切面类保存起来了。
而判断一个bean是否能成为切面类的代码就在BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
里。
// AbstractAspectJAdvisorFactory#isAspect
@Override
public boolean isAspect(Class<?> clazz) {
// 成为切面的条件,有@AspectJ注解,同时不能是ajc编译出来的类
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
private boolean compiledByAjc(Class<?> clazz) {
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
// an implementation detail of the AspectJ compiler.
for (Field field : clazz.getDeclaredFields()) {
// 通过反射拿到类中的属性,里面有ajc$开头的属性就代表被ajc编译器编译过。
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
通过jd-gui查看一下jar中的代码,发现确实属性里有ajc$开头的。
所以打包的时候别用ajc编译器就好了。