Spring AOP调用本类方法没有生效的问题

背景

首先请思考一下如下代码执行的结果:html

  • LogAop.java
//声明一个AOP拦截service包下的全部方法
@Aspect
public class LogAop {

  @Around("execution(* com.demo.service.*.*(..))")
  public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
      try {
          MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
          Method method = methodSignature.getMethod();
          Object ret = joinPoint.proceed();
          //执行完目标方法以后打印
          System.out.println("after execute method:"+method.getName());
          return ret;
      } catch (Throwable throwable) {
          throw throwable;
      }
  }
}
  • UserService.java
@Service
public class UserService{

  public User save(User user){
    //省略代码
  }

  public void sendEmail(User user){
    //省略代码
  }

  //注册
  public void register(User user){
    //保存用户
    this.save(user);
    //发送邮件给用户
    this.sendEmail(user);
  }
}
  • UserServiceTest.java
@SpringBootTest
public class UserServiceTest{

  @Autowired
  private UserService userService;

  @Test
  public void save(){
    userService.save(new User());
  }

  @Test
  public void sendEmail(){
    userService.sendEmail(new User());
  }

  @Test
  public void register(){
    UserService.register(new User());
  }
}

在执行save方法后,控制台输出为:java

after execute method:save

在执行sendEmail方法后,控制台输出为:git

after execute method:sendEmail

请问在执行register()方法后会打印出什么内容?github

反直觉

这个时候可能不少人都会和我以前想的同样,在register方法里调用了savesendEmail,那 AOP 会处理savesendEmail,输出:spring

after execute method:save
after execute method:sendEmail
after execute method:register

然而事实并非这样的,而是输出:jvm

after execute method:register

在这种认知的状况下,极可能就会写出有bug的代码,例如:ide

@Service
public class UserService{
  //用户下单一个商品
  public void order(User user,String orderId){
    Order order = findOrder(orderId);
    pay(user,order);
  }

  @Transactional
  public void pay(User user,Order order){
    //扣款
    user.setMoney(user.getMoney()-order.getPrice());
    save(user);
    //...其它处理
  }
}

当用户下单时调用的order方法,在该方法里面调用了@Transactional注解修饰的pay方法,这个时候pay方法的事务管理已经不生效了,在发生异常时就会出现问题。this

理解 AOP

咱们知道 Spring AOP 默认是基于动态代理来实现的,那么先化繁为简,只要搞懂最基本的动态代理天然就明白以前的缘由了,这里直接以 JDK 动态代理为例来演示一下上面的状况。代理

因为 JDK 动态代理必定须要接口类,因此首先声明一个IUserService接口日志

  • IUserService.java
public interface IUserService{
  User save(User user);
  void sendEmail(User user);
  User register(User user);
}

编写实现类

  • UserService.java
public class UserService implements IUserService{

  @Override
  public User save(User user){
    //省略代码
  }

  @Override
  public void sendEmail(User user){
    //省略代码
  }

  //注册
  @Override
  public void register(User user){
    //保存用户
    this.save(user);
    //发送邮件给用户
    this.sendEmail(user);
  }
}

编写日志处理动态代理实现

  • ServiceLogProxy.java
public static class ServiceLogProxy {
    public static Object getProxy(Class<?> clazz, Object target) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{clazz}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object ret = method.invoke(target, args);
                    System.out.println("after execute method:" + method.getName());
                    return ret;
                }
            });
    }
}

运行代码

  • Main.java
public class Main{
    public static void main(String[] args) {
        //获取代理类
        IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService());
        userService.save(new User());
        userService.sendEmail(new User());
        userService.register(new User());
    }
}

结果以下:

after execute method:save
after execute method:sendEmail
after execute method:register

能够发现和以前 Spring AOP 的状况同样,register方法中调用的savesendEmail方法一样的没有被动态代理拦截到,这是为何呢,接下来就看看下动态代理的底层实现。

动态代理原理

其实动态代理就是在运行期间动态的生成了一个class在 jvm 中,而后经过这个class的实例调用真正的实现类的方法,伪代码以下:

public class $Proxy0 implements IUserService{

  //这个类就是以前动态代理里的new InvocationHandler(){}对象
  private InvocationHandler h;
  //从接口中拿到的register Method
  private Method registerMethod;

  @Override
  public void register(User user){
    //执行前面ServiceLogProxy编写好的invoke方法,实现代理功能
    h.invoke(this,registerMethod,new Object[]{user})
  }
}

回到刚刚的main方法,那个userService变量的实例类型其实就是动态生成的类,能够把它的 class 打印出来看看:

IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService());
System.out.println(userService.getClass());

输出结果为:

xxx.xxx.$Proxy0

在了解这个原理以后,再接着解答以前的疑问,能够看到经过代理类的实例执行的方法才会进入到拦截处理中,而在InvocationHandler#invoke()方法中,是这样执行目标方法的:

//注意这个target是new UserService()实例对象
Object ret = method.invoke(target, args);
System.out.println("after execute method:" + method.getName());

register方法中调用this.savethis.sendEmail方法时,this是指向自己new UserService()实例,因此本质上就是:

User user = new User();
UserService userService = new UserService();
userService.save(user);
userService.sendEmail(user);

不是动态代理生成的类去执行目标方法,那必然不会进行动态代理的拦截处理中,明白这个以后原理以后,就能够改造下以前的方法,让方法内调用本类方法也能使动态代理生效,就是用动态代理生成的类去调用方法就行了,改造以下:

  • UserService.java
public class UserService implements IUserService{

  //注册
  @Override
  public void register(User user){
    //获取到代理类
    IUserService self = (IUserService) ServiceLogProxy.getProxy(IUserService.class, this);
    //经过代理类保存用户
    self.save(user);
    //经过代理类发送邮件给用户
    self.sendEmail(user);
  }
}

运行main方法,结果以下:

after execute method:save
after execute method:sendEmail
after execute method:save
after execute method:sendEmail
after execute method:register

能够看到已经达到预期效果了。

Spring AOP 中方法调用本类方法的解决方案

一样的,只要使用代理类来执行目标方法就行,而不是用this引用,修改后以下:

@Service
public class UserService{

  //拿到代理类
  @Autowired
  private UserService self;

  //注册
  public void register(User user){
    //经过代理类保存用户
    self.save(user);
    //经过代理类发送邮件给用户
    self.sendEmail(user);
  }
}

好了,问题到此就解决了,可是须要注意的是Spring官方是不提倡这样的作法的,官方提倡的是使用一个新的类来解决此类问题,例如建立一个UserRegisterService类:

@Service
public class UserRegisterService{
  @Autowired
  private UserService userService;

  //注册
  public void register(User user){
    //经过代理类保存用户
    userService.save(user);
    //经过代理类发送邮件给用户
    userService.sendEmail(user);
  }
}

附录

从JVM中拿到动态代理生成的class文件
aop-understanding-aop-proxies

相关文章
相关标签/搜索