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

30 篇文章 3 订阅

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);
    }
}

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));
    }
}

3.Controller类:

@Controller
public class MyController {
    @Autowired
    private UserService userService;

    @GetMapping("/hello")
    @ResponseBody
    public String hello() throws InterruptedException {
        userService.login();
        return "Hello";
    }
}

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

@SpringBootApplication
@EnableAspectJAutoProxy
public class Main8080 {
    public static void main(String[] args) {
        SpringApplication.run(Main8080.class, args);
    }
}

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

1.2 原理分析

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

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

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

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

public void login() throws InterruptedException {
    System.out.println("Login!");
    this.getUserName();
}

可是事实与预想的却不太一样。我们知道,如果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);
	}
}

其中,这段代码显得尤为重要: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);
			}
		}
	}
}

我们主要关注这段代码:

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

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

1.3 解决

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

@EnableAspectJAutoProxy(exposeProxy = true)

然后代码login改成:

public void login() throws InterruptedException {
    System.out.println("Login!");
    UserService userService = (UserService) AopContext.currentProxy();
    userService.getUserName();
}

结果如下:
在这里插入图片描述
我们可以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. 首先,不要在使用AOP的过程中,在增强方法的内部或者是业务代码上调用增强方法上,使用this指针。
  2. 一旦有this.增强方法()代码的出现,你就要注意了,因为这会导致AOP功能失效。
  3. 因为AOP的本质就是创建代理对象,而代理对象里面存储了原始对象的引用。
  4. 被代理对象只负责执行那些新加入的逻辑(增强方法),而原函数的调用则是通过原始对象来执行的。因此在执行AOP过程中,执行原函数的时候,this指针指的是原始对象,而非被代理对象。
  5. this.method()相当于 原始对象.method()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值