前言
在去年研发XX项目时,需要一种字节码增强机制,用于增强HSF、Tair、TDDL等相关类,用于信息采集。当时考虑了好几种方案,也踩到了一些坑,特别是关于Spring AOP代理机制的一个缺陷,让我最后决定放弃使用Spring AOP,而采用了基于JVM-Sandbox的方案。写此文特地记录下这个坑,避免后人重复入坑(当然这个问题在5.0.5后应该是已经修复了)
问题表现
当混用BeanNameAutoProxyCreator(或者其他类似的基于JDK proxy的,例如Sentinel中自己实现了BeanNameAutoProxyCreator)和使用AnnotationAwareAspectJAutoProxyCreator(或者其他基于AspectJ+注解识别的)代理同一个Spring Bean的时候,会出现一个诡异的问题。AnnotationAwareAspectJAutoProxyCreator偶尔会代理不成功(注意是偶现,所以从严格意义上来说这应该是Spring的一个设计bug)。
Spring AOP原理
简单来讲,Spring AOP是通过BeanPostProcessor实现的,BeanPostProcessor是在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)中进行遍历调用,典型的用于AOP代理的BeanPostProcessor是AbstractAutoProxyCreator及其子类,其中最重要的就是这两个方法:
@Override
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;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
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;
}
protected Object createProxy(
Class> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
protected abstract Object[] getAdvicesAndAdvisorsForBean(
Class> beanClass, String beanName, TargetSource customTargetSource) throws BeansException;
其中getAdvicesAndAdvisorsForBean是个抽象方法,从代码中可以看出getAdvicesAndAdvisorsForBean这个方法很重要,既决定了是否要进行代理,也决定了用于代理的特定的interceptors。
BeanNameAutoProxyCreator
在BeanNameAutoProxyCreator进行代理的时候,getAdvicesAndAdvisorsForBean是简单通过beanName匹配的,如果匹配上就会进行代码,当然可能会使用JDK proxy,也可能使用 CGLIB proxy,但请记住不论是哪种方式,都会生成新的class,并且这个class的方法上并不会继承target class上的注解信息(targe class方法上的注解信息丢了)!
AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator识别是否要进行的代理的代码路径如下:
protected List findEligibleAdvisors(Class> beanClass, String beanName) {
List candidateAdvisors = findCandidateAdvisors();
List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
protected List findAdvisorsThatCanApply(
List candidateAdvisors, Class> beanClass, String beanName) {
ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
}
}
public static List findAdvisorsThatCanApply(List candidateAdvisors, Class> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List eligibleAdvisors = new LinkedList();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
public static boolean canApply(Advisor advisor, Class> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
public static boolean canApply(Pointcut pc, Class> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set> classes = new LinkedHashSet>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass);
for (Class> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if ((introductionAwareMethodMatcher != null &&
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
@Override
public boolean matches(Method method, Class> targetClass) {
if (method.isAnnotationPresent(this.annotationType)) {
return true;
}
// The method may be on an interface, so let's check on the target class as well.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
return (specificMethod != method && specificMethod.isAnnotationPresent(this.annotationType));
}
public static Method getMostSpecificMethod(Method method, Class> targetClass) {
if (method != null && isOverridable(method, targetClass) &&
targetClass != null && targetClass != method.getDeclaringClass()) {
try {
if (Modifier.isPublic(method.getModifiers())) {
try {
return targetClass.getMethod(method.getName(), method.getParameterTypes());
}
catch (NoSuchMethodException ex) {
return method;
}
}
else {
Method specificMethod =
ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes());
return (specificMethod != null ? specificMethod : method);
}
} catch (SecurityException ex) {
// Security settings are disallowing reflective access; fall back to 'method' below.
}
}
return method;
}
当使用Annotation进行AOP识别的时候,4.0版本的Spring是通过在当前的beanClass(也就是AopUtils.getMostSpecificMethod的targetClass)的最特定方法上寻找对应注解;结合上节的分析,当class被BeanNameAutoProxyCreator代理过后,原class方法上的注解已经丢了,所以当bean被AnnotationAwareAspectJAutoProxyCreator处理的时候,会被跳过,不会进行代理
原因定位
经过对于Spring AOP基本原理的简单分析,其实原因已经很明显了:如果某个BeanClass(考虑是类不是接口,并且注解标识在类方法上)同时被BeanNameAutoProxyCreator和AnnotationAwareAspectJAutoProxyCreator代理时(单独使用其中一个都一定会生效),如果BeanClass被BeanNameAutoProxyCreator先处理了,后被AnnotationAwareAspectJAutoProxyCreator处理,则AnnotationAwareAspectJAutoProxyCreator的代码不会成功(因为注解信息丢失了);如果BeanClass先被AnnotationAwareAspectJAutoProxyCreator处理,然后被BeanNameAutoProxyCreator处理,则两个代理都会成效(因为BeanName没有变)。但由此其实引申出了另外一个问题,那到底哪个BeanPostProcessor先执行呢?
public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
// Register BeanPostProcessorChecker that logs an info message when
// a bean is created during BeanPostProcessor instantiation, i.e. when
// a bean is not eligible for getting processed by all BeanPostProcessors.
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
// Separate between BeanPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List priorityOrderedPostProcessors = new ArrayList();
List internalPostProcessors = new ArrayList();
List orderedPostProcessorNames = new ArrayList();
List nonOrderedPostProcessorNames = new ArrayList();
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
// First, register the BeanPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
// Next, register the BeanPostProcessors that implement Ordered.
List orderedPostProcessors = new ArrayList();
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors);
// Now, register all regular BeanPostProcessors.
List nonOrderedPostProcessors = new ArrayList();
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
// Finally, re-register all internal BeanPostProcessors.
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors);
// Re-register post-processor for detecting inner beans as ApplicationListeners,
// moving it to the end of the processor chain (for picking up proxies etc).
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}
从上述代码可以看出来,在排除处理上spring会先分成三个等级,一个是PriorityOrdered代表最高等级,一个Ordered的代表第二级,一个是NonOrdered代表没有级别,各个等级的分别进行排序,PriorityOrdered和Ordered会根据getOrder的返回值大小排序,当然如果同个等级中order大小一样的话,那两者的顺序就随缘了...当BeanNameAutoProxyCreator和AnnotationAwareAspectJAutoProxyCreator的Order一样大的时候,两者的排序顺序随缘,所以就有可能会出现文首提到的问题,在碰到这个问题后给Spring官方提了一个issue,该问题在Spring5.0.5版本及以后应该已经被修复
总结
- Spring BeanNameAutoProxyCreator代理后会丢失target bean方法上的注解
- 代理通过BeanPostProcessors进行,多个BeanPostProcessors的执行顺序可能存在随机性
- 最好不要混用多个BeanPostProcessor对同个bean进行代理(这个确实不好做好,因为有可能其他人在框架或者二方包中进行了代理)