• Spring AOP调用本类方法为什么没有生效


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

    • 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方法后,控制台输出为:

    after execute method:save

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

    after execute method:sendEmail

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

    反直觉

     

    这个时候可能很多人都会和我之前想的一样,在register方法里调用了save和sendEmail,那 AOP 会处理save和sendEmail,输出:

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

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

    after execute method:register

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

    @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方法的事务管理已经不生效了,在发生异常时就会出现问题。

    理解 AOP

     

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

    关注公众号程序员小乐回复关键字“Java”获取Java面试题和答案。

    由于 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方法中调用的save和sendEmail方法同样的没有被动态代理拦截到,这是为什么呢,接下来就看看下动态代理的底层实现。

    关注公众号程序员小乐回复关键字“offer”获取算法面试题和答案。

    动态代理原理

     

    其实动态代理就是在运行期间动态的生成了一个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.save和this.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);
    }
    }
  • 相关阅读:
    Java的常用API之System类简介
    Java的常用API之Date类简介
    Java的常用API之Object类简介
    数据库知识总结(全)
    学习:浏览器访问网站的总流程
    学习:TCP/UDP协议分析(TCP四次挥手)
    学习:TCP/UDP协议分析(TCP三次握手)
    学习:ICMP协议
    实现:ARP探测存活主机
    学习:ARP协议/数据包分析
  • 原文地址:https://www.cnblogs.com/fengyun2050/p/13411624.html
Copyright © 2020-2023  润新知