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

编程入门 行业动态 更新时间:2024-10-19 22:26:46

匪夷所思,<a href=https://www.elefans.com/category/jswz/34/1769862.html style=spring aop这么写竟然会失效!!"/>

匪夷所思,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对象来生产代理对象。

 <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. 创建代理对象
public Object getProxy(ClassLoader classLoader) {if (logger.isDebugEnabled()) {logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());}Class[] proxiedInterfaces = AopProxyUtilspleteProxiedInterfaces(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的动态代理生成的对象。

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

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

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

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

  • 异常的bean

通过上述分析可以发现异常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;}


结合上面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 没有添加特殊配置的前提,存在上述依赖是会报

信息: 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来打断依赖创建对象。
  • 修改应用代码不应该存在这种依赖关系。(推荐方案)

更多推荐

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

本文发布于:2023-11-16 22:13:44,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1634004.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:匪夷所思   spring   aop

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!