当前位置: 首页>后端>正文

匪夷所思,spring aop这么写竟然会失效!!

背景

  • spring 版本:3.2.8.RELEASE
  • JDK版本:1.8
  • 本地是正常,线上环境是有问题的
    应用从云下迁移到云上的过程中出现了一个应用部分aop 通知失效的问题,场景如下:


    匪夷所思,spring aop这么写竟然会失效!!,第1张
  • node1 节点上的category 是失效的,element是正常的
  • node2 节点上aop都是正常
    从上面我们判断是节点导致的,然后我们给节点添加亲和性标签;所有的pod都部署到这个节点上,pod全部部署到这个节点上后spring aop竟然都正常了。
    难道是节点的问题?
    spring 是Java层面的东西,中间还隔了一层JVM用来屏蔽不同操作系统的影响;我判断肯定是应用代码中有什么配置影响了spring aop,毕竟是07年的老项目了。


    匪夷所思,spring aop这么写竟然会失效!!,第2张

我们首先分析一下 spring aop的流程。

spring aop流程

spring xml配置

下面是spring aop的配置,依赖于org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator对象来生产代理对象。

 
    <bean id="myAfterAdvisor" class="com.xbin.aop.MyAfterAdvisor" lazy-init="false" >
        <property name="category" ref="category"></property>
    </bean>



    <bean id="nameMatchMethodPointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" >
        <property name="mappedNames">
            <list>
                <value>test</value>
                <value>insert</value>
                <value>update</value>
            </list>
        </property>

        <property name="order" value="99"></property>

        <property name="advice">
            <ref bean="myAfterAdvisor"></ref>
<!--            <ref ="myAfterAdvisor"></ref>-->
        </property>
    </bean>


    <bean id="beanNameAutoProxyCreator"
          class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="interceptorNames">
            <list>
                <value>nameMatchMethodPointcutAdvisor</value>

            </list>
        </property>
        <property name="order" value="99"></property>
        <property name="beanNames">
            <list>
                <value>element</value>
                <value>category</value>
            </list>
        </property>
    </bean>

spring 代理对象创建的流程

匪夷所思,spring aop这么写竟然会失效!!,第3张

我们了解到spring aop功能的实现依赖于代理对象的创建,代理类实现了接口所以使用的是JDK的动态代理。
对应JDK的动态代理来说最重要的是InvocationHandler接口。


匪夷所思,spring aop这么写竟然会失效!!,第4张
  1. 创建代理对象
public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
  1. InvocationHandler的实现

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodInvocation invocation;
        Object oldProxy = null;
        boolean setProxyContext = false;

        TargetSource targetSource = this.advised.targetSource;
        Class targetClass = null;
        Object target = null;

        try {
            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
                // The target does not implement the equals(Object) method itself.
                return equals(args[0]);
            }
            if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                // The target does not implement the hashCode() method itself.
                return hashCode();
            }
            if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                    method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                // Service invocations on ProxyConfig with the proxy config...
                return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
            }

            Object retVal;

            if (this.advised.exposeProxy) {
                // Make invocation available if necessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }

            // May be null. Get as late as possible to minimize the time we "own" the target,
            // in case it comes from a pool.
            target = targetSource.getTarget();
            if (target != null) {
                targetClass = target.getClass();
            }

            // Get the interception chain for this method.
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            // Check whether we have any advice. If we don't, we can fallback on direct
            // reflective invocation of the target, and avoid creating a MethodInvocation.
            if (chain.isEmpty()) {
                // We can skip creating a MethodInvocation: just invoke the target directly
                // Note that the final invoker must be an InvokerInterceptor so we know it does
                // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
            }
            else {
                // We need to create a method invocation...
                invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                // Proceed to the joinpoint through the interceptor chain.
                retVal = invocation.proceed();
            }

            // Massage return value if necessary.
            Class<?> returnType = method.getReturnType();
            if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
                    !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                // Special case: it returned "this" and the return type of the method
                // is type-compatible. Note that we can't help if the target sets
                // a reference to itself in another returned object.
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
            }
            return retVal;
        }
        finally {
            if (target != null && !targetSource.isStatic()) {
                // Must have come from TargetSource.
                targetSource.releaseTarget(target);
            }
            if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }

大体流程如上,至于 spring aop 中前置通知、后置通知、环绕通知等是如果调用的这边就不进行分析了;我们回归解决问题的本质上。

问题分析

代理对象是否生成

我们都知道spring aop的能力都是通过代理对象来实现了,我们直接输出日志查看对象信息。发现类名有Proxy说明是生成是由JDK的动态代理生成的对象。

代理对象中通知是否可以获取到

代理对象已经生成,接下来看看调用代理对象时候相关的通知是否可以获取到。


匪夷所思,spring aop这么写竟然会失效!!,第5张

JdkDynamicAopProxy的invoke方法里面需要获取到通知处理的 chain。
线上环境这里我们借助阿里的arthas线上调试工具进行排查。
arthas地址:https://arthas.aliyun.com/doc/
我们来监控:org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice 这个方法。

watch org.springframework.aop.framework.JdkDynamicAopProxy invoke -x 2
  • 正常的bean


    匪夷所思,spring aop这么写竟然会失效!!,第6张
  • 异常的bean


    匪夷所思,spring aop这么写竟然会失效!!,第7张

通过上述分析可以发现异常bean没有获取通知相关的拦截器。

源码分析

getInterceptorsAndDynamicInterceptionAdvice

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
        MethodCacheKey cacheKey = new MethodCacheKey(method);
        List<Object> cached = this.methodCache.get(cacheKey);
        if (cached == null) {
            cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                    this, method, targetClass);
            this.methodCache.put(cacheKey, cached);
        }
        return cached;
    }

getInterceptorsAndDynamicInterceptionAdvice


public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method method, Class targetClass) {

        // This is somewhat tricky... we have to process introductions first,
        // but we need to preserve order in the ultimate list.
        List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
        boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
        // 如果config.getAdvisors获取的对象是空的,那么获取的 list也是空的
        for (Advisor advisor : config.getAdvisors()) {
            if (advisor instanceof PointcutAdvisor) {
                // Add it conditionally.
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
                        if (mm.isRuntime()) {
                            // Creating a new object instance in the getInterceptors() method
                            // isn't a problem as we normally cache created chains.
                            for (MethodInterceptor interceptor : interceptors) {
                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                            }
                        }
                        else {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
            }
            else if (advisor instanceof IntroductionAdvisor) {
                IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
                if (config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
                    Interceptor[] interceptors = registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            }
            else {
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        return interceptorList;
    }
匪夷所思,spring aop这么写竟然会失效!!,第8张
匪夷所思,spring aop这么写竟然会失效!!,第9张

结合上面2中图,可以分析得出 advisorArray中的内容是空的,那么接下来我们需要分析这个数组为啥是空。

这个对象是在spring aop 生成代理对象的时候进行赋值的。

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.containsKey(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (beanName != null && this.targetSourcedBeans.containsKey(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;
    }

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy

protected Object createProxy(
            Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

        ProxyFactory proxyFactory = new ProxyFactory();
        // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
        proxyFactory.copyFrom(this);

        if (!shouldProxyTargetClass(beanClass, beanName)) {
            // Must allow for introductions; can't just set interfaces to
            // the target's interfaces only.
            Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader);
            for (Class<?> targetInterface : targetInterfaces) {
                proxyFactory.addInterface(targetInterface);
            }
        }
       //  如果这里没有获取到 advisors ,那么advisorArray数组就是空的了
        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        for (Advisor advisor : advisors) {
            proxyFactory.addAdvisor(advisor);
        }

        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);

        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }

        return proxyFactory.getProxy(this.proxyClassLoader);
    }

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#buildAdvisors

protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
        // Handle prototypes correctly...
         // resolveInterceptorNames 方法非常关键
        Advisor[] commonInterceptors = resolveInterceptorNames();

        List<Object> allInterceptors = new ArrayList<Object>();
        if (specificInterceptors != null) {
            allInterceptors.addAll(Arrays.asList(specificInterceptors));
            if (commonInterceptors != null) {
                if (this.applyCommonInterceptorsFirst) {
                    allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
                }
                else {
                    allInterceptors.addAll(Arrays.asList(commonInterceptors));
                }
            }
        }
        if (logger.isDebugEnabled()) {
            int nrOfCommonInterceptors = (commonInterceptors != null commonInterceptors.length : 0);
            int nrOfSpecificInterceptors = (specificInterceptors != null specificInterceptors.length : 0);
            logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
                    " common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
        }

        Advisor[] advisors = new Advisor[allInterceptors.size()];
        for (int i = 0; i < allInterceptors.size(); i++) {
            advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
        }
        return advisors;
    }

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#resolveInterceptorNames

    private Advisor[] resolveInterceptorNames() {
        ConfigurableBeanFactory cbf = (this.beanFactory instanceof ConfigurableBeanFactory) ?
                (ConfigurableBeanFactory) this.beanFactory : null;
        List<Advisor> advisors = new ArrayList<Advisor>();
        for (String beanName : this.interceptorNames) {
           // 这里有一个逻辑非常关键, interceptorName如果正在创建中是不会添加到 advisors里面的,
         //  spring认为这不是一个完整的对象,直接对外使用会出现问题。
            if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) {
                Object next = this.beanFactory.getBean(beanName);
                advisors.add(this.advisorAdapterRegistry.wrap(next));
            }
        }
        return advisors.toArray(new Advisor[advisors.size()]);
    }

总结

原因总结

通过分析上述源码,那么什么场景下会出现这种问题?


匪夷所思,spring aop这么写竟然会失效!!,第10张
匪夷所思,spring aop这么写竟然会失效!!,第11张

spring 没有添加特殊配置的前提,存在上述依赖是会报

信息: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3796751b: defining beans [category,element,myAfterAdvisor,nameMatchMethodPointcutAdvisor,beanNameAutoProxyCreator]; root of factory hierarchy
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'category': Bean with name 'category' has been injected into other beans [myAfterAdvisor] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:548)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:296)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:628)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at com.xbin.TestAopMain.main(TestAopMain.java:15)

Process finished with exit code 1


需要添加 lazy-init来解决多个代理对象之间的循环依赖。


匪夷所思,spring aop这么写竟然会失效!!,第12张
  • aop 生效场景下的输出。


    匪夷所思,spring aop这么写竟然会失效!!,第13张
  • aop 失效场景下的输出:
匪夷所思,spring aop这么写竟然会失效!!,第14张
匪夷所思,spring aop这么写竟然会失效!!,第15张

就只改了myAfterAdvisor延迟初始化的就会导致 aop失效。

匪夷所思,spring aop这么写竟然会失效!!,第16张

解决方案

  • 通过配置延迟初始化bean来打断依赖创建对象。
  • 修改应用代码不应该存在这种依赖关系。(推荐方案)

https://www.xamrdz.com/backend/3cj1941505.html

相关文章: