記錄下多個BeanPostProcessor代理同個Bean的問題

前言

在去年研發(fā)XX項目時,需要一種字節(jié)碼增強機制,用于增強HSF、Tair、TDDL等相關(guān)類,用于信息采集。當(dāng)時考慮了好幾種方案,也踩到了一些坑,特別是關(guān)于Spring AOP代理機制的一個缺陷,讓我最后決定放棄使用Spring AOP,而采用了基于JVM-Sandbox的方案。寫此文特地記錄下這個坑,避免后人重復(fù)入坑(當(dāng)然這個問題在5.0.5后應(yīng)該是已經(jīng)修復(fù)了)

問題表現(xiàn)

當(dāng)混用BeanNameAutoProxyCreator(或者其他類似的基于JDK proxy的,例如Sentinel中自己實現(xiàn)了BeanNameAutoProxyCreator)和使用AnnotationAwareAspectJAutoProxyCreator(或者其他基于AspectJ+注解識別的)代理同一個Spring Bean的時候,會出現(xiàn)一個詭異的問題。AnnotationAwareAspectJAutoProxyCreator偶爾會代理不成功(注意是偶現(xiàn),所以從嚴格意義上來說這應(yīng)該是Spring的一個設(shè)計bug)。

Spring AOP原理

簡單來講,Spring AOP是通過BeanPostProcessor實現(xiàn)的,BeanPostProcessor是在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)中進行遍歷調(diào)用,典型的用于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;
            

其中g(shù)etAdvicesAndAdvisorsForBean是個抽象方法,從代碼中可以看出getAdvicesAndAdvisorsForBean這個方法很重要,既決定了是否要進行代理,也決定了用于代理的特定的interceptors。

initBean的過程.jpg
pointcut不匹配.jpg
annotation型的列表.jpg
processors列表.jpg

BeanNameAutoProxyCreator

在BeanNameAutoProxyCreator進行代理的時候,getAdvicesAndAdvisorsForBean是簡單通過beanName匹配的,如果匹配上就會進行代碼,當(dāng)然可能會使用JDK proxy,也可能使用 CGLIB proxy,但請記住不論是哪種方式,都會生成新的class,并且這個class的方法上并不會繼承target class上的注解信息(targe class方法上的注解信息丟了)!

AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator識別是否要進行的代理的代碼路徑如下:

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            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);
        }
    }
    
    public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
        if (candidateAdvisors.isEmpty()) {
            return candidateAdvisors;
        }
        List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
        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<Class<?>> classes = new LinkedHashSet<Class<?>>(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;
    }
    

當(dāng)使用Annotation進行AOP識別的時候,4.0版本的Spring是通過在當(dāng)前的beanClass(也就是AopUtils.getMostSpecificMethod的targetClass)的最特定方法上尋找對應(yīng)注解;結(jié)合上節(jié)的分析,當(dāng)class被BeanNameAutoProxyCreator代理過后,原class方法上的注解已經(jīng)丟了,所以當(dāng)bean被AnnotationAwareAspectJAutoProxyCreator處理的時候,會被跳過,不會進行代理

原因定位

經(jīng)過對于Spring AOP基本原理的簡單分析,其實原因已經(jīng)很明顯了:如果某個BeanClass(考慮是類不是接口,并且注解標(biāo)識在類方法上)同時被BeanNameAutoProxyCreator和AnnotationAwareAspectJAutoProxyCreator代理時(單獨使用其中一個都一定會生效),如果BeanClass被BeanNameAutoProxyCreator先處理了,后被AnnotationAwareAspectJAutoProxyCreator處理,則AnnotationAwareAspectJAutoProxyCreator的代碼不會成功(因為注解信息丟失了);如果BeanClass先被AnnotationAwareAspectJAutoProxyCreator處理,然后被BeanNameAutoProxyCreator處理,則兩個代理都會成效(因為BeanName沒有變)。但由此其實引申出了另外一個問題,那到底哪個BeanPostProcessor先執(zhí)行呢?

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<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
        List<BeanPostProcessor> internalPostProcessors = new ArrayList<BeanPostProcessor>();
        List<String> orderedPostProcessorNames = new ArrayList<String>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
        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<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>();
        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<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
        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會根據(jù)getOrder的返回值大小排序,當(dāng)然如果同個等級中order大小一樣的話,那兩者的順序就隨緣了...當(dāng)BeanNameAutoProxyCreator和AnnotationAwareAspectJAutoProxyCreator的Order一樣大的時候,兩者的排序順序隨緣,所以就有可能會出現(xiàn)文首提到的問題,在碰到這個問題后給Spring官方提了一個issue,該問題在Spring5.0.5版本及以后應(yīng)該已經(jīng)被修復(fù)

總結(jié)

  • Spring BeanNameAutoProxyCreator代理后會丟失target bean方法上的注解
  • 代理通過BeanPostProcessors進行,多個BeanPostProcessors的執(zhí)行順序可能存在隨機性
  • 最好不要混用多個BeanPostProcessor對同個bean進行代理(這個確實不好做好,因為有可能其他人在框架或者二方包中進行了代理)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容