• Spring常见问题解决 - this指针造成AOP失效


    一. this指针造成AOP失效问题

    1.1 案例

    1.自定义一个UserService:(本文的关键先生)

    @Service
    public class UserService {
        public void login() throws InterruptedException {
            System.out.println("Login!");
            this.getUserName();
        }
    
        public void getUserName() throws InterruptedException {
            System.out.println("My Name is Test");
            Thread.sleep(1000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.定义AOP,希望计算getUserName方法消耗了多长时间。

    @Aspect
    @Service
    public class UserAop {
    	// 这里写你的UserService的路径名.增强的方法名
        @Around("execution(* com.service.UserService.getUserName()) ")
        public void check(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            joinPoint.proceed();
            long end = System.currentTimeMillis();
            System.out.println("getUserName method time cost(ms): " + (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.Controller类:

    @Controller
    public class MyController {
        @Autowired
        private UserService userService;
    
        @GetMapping("/hello")
        @ResponseBody
        public String hello() throws InterruptedException {
            userService.login();
            return "Hello";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.启动类:@EnableAspectJAutoProxy开启AOP功能。

    @SpringBootApplication
    @EnableAspectJAutoProxy
    public class Main8080 {
        public static void main(String[] args) {
            SpringApplication.run(Main8080.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.访问对应路径,结果如下:
    在这里插入图片描述
    可见,我们虽然通过@Around织入了我们自定义的增强逻辑,但是结果却并没有运行到相关代码,即AOP失效了。

    1.2 原理分析

    我们首先来说下SpringAOP总的来说就是通过Cglib或者JDK动态代理的方式实现在运行期对方法的动态增强。本质上也就是一个代理模式。

    那么针对本文的案例,我们对getUserName方法做了增强逻辑:

    @Around("execution(* com.service.UserService.getUserName()) ")
    
    • 1

    那么,在UserService类的login方法中,我们希望调用的这个getUserName是一个增强后的方法:

    public void login() throws InterruptedException {
        System.out.println("Login!");
        this.getUserName();
    }
    
    • 1
    • 2
    • 3
    • 4

    可是事实与预想的却不太一样。我们知道,如果AOP功能实现成功的话,本质上是调用了对应方法的一个代理。那么我们先debug看下,调用getUserName的对象是什么:
    在这里插入图片描述
    没啥特别的地方,就是一个普通的UserService对象。我们再来看下Controller中的userService是什么:
    在这里插入图片描述
    是一个通过Cglib增强后的对象实例。很明显两者不是同一个。那么就容易看出问题出在这了。我写过一篇文章:Spring源码系列: AOP实现里面对于AOP的实现原理讲的很详细了。这里面仅仅贴出跟本文息息相关的一些代码:

    AOP的最后一步就是创建代理对象:

    // 5.创建代理
    Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    
    public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
    		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    	protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
    			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    		}
    		// 1.创建代理工厂类,并且复制当前类的属性
    		ProxyFactory proxyFactory = new ProxyFactory();
    		proxyFactory.copyFrom(this);
    		// 2.校验:给定的bean使用目标类还是其接口代理
    		if (!proxyFactory.isProxyTargetClass()) {
    			if (shouldProxyTargetClass(beanClass, beanName)) {
    				proxyFactory.setProxyTargetClass(true);
    			}
    			else {
    				// 添加代理接口
    				evaluateProxyInterfaces(beanClass, proxyFactory);
    			}
    		}
    		// 3.设置代理工厂的相关属性,将增强数组和目标对象加入其中
    		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    		proxyFactory.addAdvisors(advisors);
    		proxyFactory.setTargetSource(targetSource);
    		customizeProxyFactory(proxyFactory);
    		// 4.用来控制代理工厂被配置之后,是否允许修改通知
    		proxyFactory.setFrozen(this.freezeProxy);
    		if (advisorsPreFiltered()) {
    			proxyFactory.setPreFiltered(true);
    		}
    
    		ClassLoader classLoader = getProxyClassLoader();
    		if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
    			classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
    		}
    		// 5.主要还是委托ProxyFactory去创建代理类。
    		return proxyFactory.getProxy(classLoader);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    其中,这段代码显得尤为重要:proxyFactory.setTargetSource(targetSource); 也就是说,代理对象它里面依旧包含了源对象的引用。即AOP的实现依旧离不开源对象。

    1. 因为AOP本质上,只不过是重写了被代理对象的方法,而当真正调用被代理对象的方法时,是通过原始对象来进行调用的,而不是被代理对象。
    2. 因此如果在被代理方法的内部使用了this指针,那么这个this指的就是原始对象,而不是被代理对象。

    那么我们需要看下Cglib对于代理的实现,来验证上面的说法。Spring源码系列: AOP实现这里我讲了Cglib对于方法的拦截是通过将自定义的拦截器加入到Callback中并调用代理时,通过激活intercept方法来实现。

    而源码中我们可以发现,Spring将拦截器都加入到了DynamicAdvisedInterceptor这个类中,而该类又是MethodInterceptor的实现类。因此具体的Cglib方式的AOP代理必然在其中实现:

    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
    	@Override
    	@Nullable
    	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    		Object oldProxy = null;
    		boolean setProxyContext = false;
    		Object target = null;
    		TargetSource targetSource = this.advised.getTargetSource();
    		try {
    			// 同JDK代理,处理一些自调用的特殊情况,暴露对象
    			if (this.advised.exposeProxy) {
    				oldProxy = AopContext.setCurrentProxy(proxy);
    				setProxyContext = true;
    			}
    			target = targetSource.getTarget();
    			Class<?> targetClass = (target != null ? target.getClass() : null);
    			// 1.获取拦截器链
    			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    			Object retVal;
    			// 2.若拦截器为空,且方法是可以公共访问的。直接调用源方法
    			if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
    				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
    				retVal = methodProxy.invoke(target, argsToUse);
    			}
    			else {
    				// 3.进入链中,和jdk 动态代理实现是类似的,只是MethodInvocation实现类不同而已
    				retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
    			}
    			retVal = processReturnType(proxy, target, method, retVal);
    			return retVal;
    		}
    		finally {
    			if (target != null && !targetSource.isStatic()) {
    				targetSource.releaseTarget(target);
    			}
    			if (setProxyContext) {
    				// Restore old proxy.
    				AopContext.setCurrentProxy(oldProxy);
    			}
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    我们主要关注这段代码:

    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
    
    • 1

    那么它的taget是啥呢?我们看下外层的传参target = targetSource.getTarget();,和我们在创建代理对象时proxyFactory.setTargetSource(targetSource);set 进去的对象是同一个,都指向了原始对象。

    Spring中,如果某个实例是需要通过Cglib方式来完成代理的,那么最终的实现在于CglibMethodInvocation 这个类中。我们可以确定,这里执行invoke的时候,使用的也就是原始对象this.target

    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
    	@Override
    	protected Object invokeJoinpoint() throws Throwable {
    		if (this.methodProxy != null) {
    			return this.methodProxy.invoke(this.target, this.arguments);
    		}
    		else {
    			return super.invokeJoinpoint();
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里可以得出结论:

    1. 在一个被动态代理的对象执行完 AOP 所有的增强逻辑之后,最终都会使用被代理对象作为实例调用真实的方法,即相当于调用了:target.method () 方法。
    2. AOP动态代理方法的时候,实际上会通过原始对象进行调用。因此这里的this指的是原始对象。而不是代理对象本身。
    3. 而只有代理对象才拥有增强的逻辑。

    1.3 解决

    我们可以通过AopContext来获取一个代理对象。首先在启动类的注解中,添加属性exposeProxy

    @EnableAspectJAutoProxy(exposeProxy = true)
    
    • 1

    然后代码login改成:

    public void login() throws InterruptedException {
        System.out.println("Login!");
        UserService userService = (UserService) AopContext.currentProxy();
        userService.getUserName();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结果如下:
    在这里插入图片描述
    我们可以deubg看下,此时的UserService对象实例是咋样的:
    在这里插入图片描述
    可见这里不再是一个普通的没有被增强过的UserService实例了。另外做个补充,无论你的this用在哪里,只要是AOP的调用链中引用的,那么this指的就是原始对象本身。如果程序里面不通过this指针来调用被增强的方法,那么问题倒是没有的。 证明如下:
    在这里插入图片描述

    另一种方式(不推荐):可以通过@Autowired注解引入自己:若报错循环依赖,可以加个@Lazy注解。

    @Service
    public class UserService {
        @Autowired
        private UserService userService;
    
        public void login() throws InterruptedException {
            System.out.println("Login!");
            userService.getUserName();
        }
    
        public void getUserName() throws InterruptedException {
            System.out.println("My Name is Test");
            Thread.sleep(1000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    二. 总结

    1. 首先,不要在使用AOP的过程中,在增强方法的内部或者是业务代码上调用增强方法上,使用this指针。
    2. 一旦有this.增强方法()代码的出现,你就要注意了,因为这会导致AOP功能失效。
    3. 因为AOP的本质就是创建代理对象,而代理对象里面存储了原始对象的引用。
    4. 被代理对象只负责执行那些新加入的逻辑(增强方法),而原函数的调用则是通过原始对象来执行的。因此在执行AOP过程中,执行原函数的时候,this指针指的是原始对象,而非被代理对象。
    5. this.method()相当于 原始对象.method()
  • 相关阅读:
    不可重复读和幻读详解(必看!!!)
    C++ 17 filesystem
    使用Python进行页面开发——模板层
    深度学习输入数据的归一化
    第11讲:Python列表对象中元素修改操作
    CG-05 角度传感器工作介绍
    C语言 - 位段
    非肿瘤纯生信拿下7+,多种机器学习算法,搭配WGCNA。
    计算机毕业设计ssm电子书店管理系统ya226系统+程序+源码+lw+远程部署
    王道书P149 T3(二叉树链式存储实现)
  • 原文地址:https://blog.csdn.net/Zong_0915/article/details/126468545