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

背景

  • spring 版本:3.2.8.RELEASE
  • JDK版本:1.8
  • 本地是正常,线上环境是有问题的 应用从云下迁移到云上的过程中出现了一个应用部分aop 通知失效的问题,场景如下:
  • node1 节点上的category 是失效的,element是正常的
  • node2 节点上aop都是正常 从上面我们判断是节点导致的,然后我们给节点添加亲和性标签;所有的pod都部署到这个节点上,pod全部部署到这个节点上后spring aop竟然都正常了。 难道是节点的问题? spring 是Java层面的东西,中间还隔了一层JVM用来屏蔽不同操作系统的影响;我判断肯定是应用代码中有什么配置影响了spring aop,毕竟是07年的老项目了。

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

spring aop流程

spring xml配置

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

XML 复制代码
 
    <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功能的实现依赖于代理对象的创建,代理类实现了接口所以使用的是JDK的动态代理。 对应JDK的动态代理来说最重要的是InvocationHandler接口。

  1. 创建代理对象
java 复制代码
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的实现
java 复制代码
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的动态代理生成的对象。

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

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

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

bash 复制代码
watch org.springframework.aop.framework.JdkDynamicAopProxy invoke -x 2
  • 正常的bean

  • 异常的bean

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

源码分析

getInterceptorsAndDynamicInterceptionAdvice

java 复制代码
	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

java 复制代码
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;
	}

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

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

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

java 复制代码
	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

java 复制代码
	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

java 复制代码
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

java 复制代码
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

java 复制代码
	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 没有添加特殊配置的前提,存在上述依赖是会报

java 复制代码
信息: 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$1.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来解决多个代理对象之间的循环依赖。

  • aop 生效场景下的输出。
  • aop 失效场景下的输出:

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

解决方案

  • 通过配置延迟初始化bean来打断依赖创建对象。
  • 修改应用代码不应该存在这种依赖关系。(推荐方案)
相关推荐
luoluoal2 分钟前
java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
java·vue.js·spring boot
ChinaRainbowSea8 分钟前
十三,Spring Boot 中注入 Servlet,Filter,Listener
java·spring boot·spring·servlet·web
小游鱼KF11 分钟前
Spring学习前置知识
java·学习·spring
扎克begod15 分钟前
JAVA并发编程系列(9)CyclicBarrier循环屏障原理分析
java·开发语言·python
青灯文案116 分钟前
SpringBoot 项目统一 API 响应结果封装示例
java·spring boot·后端
我就是程序猿26 分钟前
tomcat的配置
java·tomcat
阳光阿盖尔32 分钟前
EasyExcel的基本使用——Java导入Excel数据
java·开发语言·excel
二十雨辰33 分钟前
[苍穹外卖]-12Apache POI入门与实战
java·spring boot·mybatis
程序员皮皮林34 分钟前
开源PDF工具 Apache PDFBox 认识及使用(知识点+案例)
java·pdf·开源·apache
蔚一34 分钟前
Java设计模式—面向对象设计原则(三) -----> 依赖倒转原则DIP(完整详解,附有代码+案例)
java·开发语言·设计模式·intellij-idea·依赖倒置原则