Spring AOP有一个自我调用的问题,我们在使用Spring AOP的时候一定要注意,否则容易掉坑。
我们先来解释一下什么叫自我调用问题。
我们还是使用上一篇文章用过的例子,UserService类中稍加改造,让addUser方法调用deleteUser方法:
@Override
public boolean addUser(String userName) {
System.out.println("add user in userService:"+userName);
deleteUser();
return true;
}
@Override
public boolean deleteUser() {
System.out.println("delete user in userService");
return false;
}
修改切面类LogManagement,增加对deleteUser方法的Pointcut:
@Pointcut("execution(* springAop.UserService.delete*())")
public void deleteUserPointcut(){}
@Before(value="deleteUserPointcut()")
public void beforeLog(JoinPoint jp){
System.out.println("before "+jp+" advice...");
}
启动程序中仅保留deleteUsers方法的调用,验证一下针对deleteUser的Advice:
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
IUserService us = (IUserService) applicationContext.getBean("userService");
// UserService us = (UserService) applicationContext.getBean("userService");
System.out.println(us.getClass());
// us.addUser("Zhang San");
us.deleteUser();
}
执行结果:
class com.sun.proxy.$Proxy34
before execution(boolean springAop.IUserService.deleteUser()) advice...
delete user in userService
说明针对deleteUser的Advice已经生效。
修改启动类,执行UserService的addUser方法,测试addUser方法中对deleteUser方法的调用情况:
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
IUserService us = (IUserService) applicationContext.getBean("userService");
// UserService us = (UserService) applicationContext.getBean("userService");
System.out.println(us.getClass());
us.addUser("Zhang San");
}
执行启动类:
class com.sun.proxy.$Proxy34
This is around execution(boolean springAop.IUserService.addUser(String)) ,before process...
before execution(boolean springAop.IUserService.addUser(String)) advice... and userName is :Zhang San
add user in userService:Zhang San
delete user in userService
the return value after proceed is :true
This is aroundexecution(boolean springAop.IUserService.addUser(String)) ,after process...
after execution(boolean springAop.IUserService.addUser(String)) advice......
This is after returningexecution(boolean springAop.IUserService.addUser(String)) ...
return :true
从执行结果看,addUser调用deleteUser方法后,并没有触发deleteUser的切面处理。
这就是Spring AOP的自我调用问题:目标类的连接点方法A,调用本类的连接点方法B,方法B不会被切面处理!
之所有说自我调用的以上现象是一个问题,是因为自我调用可能无法避免,但是自我调用导致的切面失效可能会导致严重的应用问题:比如@Transactional事务处理,会使得本应该包含在一个事务中的方法被遗漏,最终可能导致数据不一致问题。
Spring AOP底层是基于动态代理的,所以,目标类的JointPoint方法最终是通过代理类发起调用的。
调用方法A的时候,实际调用的是代理类的方法A,代理类中执行完相关的Before Advices之后才会调用目标类的方法A,才会执行到目标类的方法A中的逻辑。
然后自我调用发生了,方法A调用了方法B,这种情况下方法B的调用是目标类直接调用,不会用到代理类了。因此方法B就不会被增强了。
Spring官网给出的解决方案,首推“业务上尽量避免自我调用”。
实在无法避免的情况下(好多情况下实际确实无法避免),官网给出的解决方案:
Okay, so what is to be done about this? The best approach (the term “best” is used loosely here) is to refactor your code such that the self-invocation does not happen. This does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and we hesitate to point it out, precisely because it is so horrendous. You can (painful as it is to us) totally tie the logic within your class to Spring AOP, as the following example shows:
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
这个方案的原理很简单,其实就是在调用方法B的时候再向Spring Ioc容器获取一次当前对象,因为Spring IoC容器中存储的其实是当前对象的代理对象,所以就相当于在目标类中又获取到了自己的代理对象,通过代理对象调用方法B、从而使方法B获得增强。
顺着这个思路,基于这个原理,其实还有另外的方式:
@Autowired
IUserService userService;
@Override
public boolean addUser(String userName) {
System.out.println("add user in userService:"+userName);
userService.deleteUser();
return true;
}
运行启动类,看一下效果。
class com.sun.proxy.$Proxy34
This is around execution(boolean springAop.IUserService.addUser(String)) ,before process...
before execution(boolean springAop.IUserService.addUser(String)) advice... and userName is :Zhang San
add user in userService:Zhang San
before execution(boolean springAop.IUserService.deleteUser()) advice...
delete user in userService
the return value after proceed is :true
This is aroundexecution(boolean springAop.IUserService.addUser(String)) ,after process...
after execution(boolean springAop.IUserService.addUser(String)) advice......
This is after returningexecution(boolean springAop.IUserService.addUser(String)) ...
return :true
Process finished with exit code 0
其实和Spring官网给出的方案的底层逻辑一样、运行效果也一样。
上一篇 Spring FrameWork从入门到NB - Spring AOP(实战)