Java动态代理和Spring AOP原理浅析

一直对Spring的AOP功能敬而远之,总是用,却从没想过去了解它。想学习下JDK的动态代理,顺便的了解了下AOP,发现其实不算很难,但是设计的是真的妙啊。
Spring-AOP-Based-System.jpg

References:

文章篇幅可能有点长。

首先从代理模式说起吧,先来简单复习一下代理模式:
18202945_lHIa.png

代理模式是一种设计模式,提供了对目标对象额外的访问方式,代理类和目标对象实现相同的接口,然后通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。
动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。目标对象必须实现接口,否则不能使用动态代理。下面看一下例子:
Spring AOP.png

首先是业务的接口(Person)和实现类(Child):

public interface Person {
    void play(String thing);
}
public class Child implements Person {
    @Override
    public void play(String thing) {
        if (thing == null || "".equals(thing)) {
            throw new IllegalArgumentException();
        }
        
        System.out.println("child play: " + thing + ". feel happy.");
    }
}

然后实现InvocationHandler接口:

public class ProxyHandler implements InvocationHandler {
    private Object target;
    
    public ProxyHandler(Object target) {
        this.target = target;
    }
    
    public static Object newInstance(Object obj) {
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new ProxyHandler(obj));
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before play, first wash hands.");
        
        // 对目标对象的调用
        Object result = method.invoke(this.target, args);
        
        System.out.println("after play, need to sleep.");
        return result;
    }
}

这里为了方便使用,直接把获取代理的方法也放在Hanlder的实现类中。然后直接调用就可以看到效果了:

public class TestProxy {
    public static void main(String[] args) {
        Person proxy = (Person) ProxyHandler.newInstance(new Child());
        proxy.play("video games");
    }
}
// Output:
before play, first wash hands.
child play: video games. feel happy.
after play, need to sleep.

在main方法中,我们实际调用的是child的play()方法,而before和after是增强功能。那么是什么时候帮我们做了增强的操作呢?
之所以天天叫JDK动态代理,是因为这个代理class是由JDK在运行时动态帮我们生成。在解释代理生成过程前,我们先把-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true这个参数加入到JVM 启动参数中,它的作用是帮我们把JDK动态生成的proxy class 的字节码保存到硬盘中,帮助我们查看具体生成proxy的内容。
Eclipse中操作如下,在Run Configuration中添加VM参数:
Snipaste_2019-04-28_18-28-01.png

运行后在工程目录下生成如下的文件$Proxy0.class,就是运行时生成的代理类:
Snipaste_2019-04-28_18-26-04.png

反编译之后代码如下:

package com.sun.proxy;

import java.lang.reflect.*;

public final class $Proxy0 extends Proxy implements Person
{
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    
    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy0.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final void play(final String s) {
        try {
            super.h.invoke(this, $Proxy0.m3, new Object[] { s });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy0.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    static {
        try {
            $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
            $Proxy0.m3 = Class.forName("Person").getMethod("play", Class.forName("java.lang.String"));
            $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
        }
        catch (NoSuchMethodException ex) {
            throw new NoSuchMethodError(ex.getMessage());
        }
        catch (ClassNotFoundException ex2) {
            throw new NoClassDefFoundError(ex2.getMessage());
        }
    }
}

这个代理类继承自Proxy,并且实现了我们的Person接口。通过Proxy.newProxyInstance()方法将给定的handler设置给这个代理类。重点就是其中的play()方法,play()调用了handler的invoke()方法,这样就完成了我们需要的增强功能。然后handler中的invoke()方法在增强后重新调用目标对象的方法。这样整个代理过程就算结束了。关于生成动态代理类这部分更详细可以参考上面链接中的“JDK动态代理详解”一文。

对于JDK的动态代理就到此,下面看看Spring的代理机制,和JDK动态代理有什么关系?

Spring官网的代理图解:
aop-proxy-call.png

Spring AOP使用JDK创建动态代理或CGLIB给目标对象创建代理对象。

  • 如果被代理的目标对象实现了至少一个接口,那么Spring就会使用JDK动态代理,目标对象实现接口的方法都会被代理,需要注意的是JDK动态代理类无法强制转换为原始目标类,因为它只是一个简单的动态代理,恰好和目标对象实现了相同的接口。这就需要面向接口编程,因为代理通常会通过这些接口调用。
  • 如果目标对象没有实现接口,那么就会用CGLIB创建代理,CGLIB会通过继承的方式创建目标对象的子类作为代理,并重写所有的方法(不只是实现接口的方法),需要注意的是final类和方法不能被代理,因为final类不能被继承,final方法不能被重写。

其实不管是动态代理还是CGLIB,都是创建了一个代理对象,然后通过代理增强目标的功能。概念差不多了,下面通过一个简单的例子模拟一下Spring的AOP。

实例的结构如下,我保留的原始的类名并稍微改变了原本的功能,使其更容易体现出动态代理的结构。
Spring AOP Simulation.png

目标接口和目标类依然是上面的Person和Child,就不重复了,看下AopProxy和JdkDynamicAopProxy:

public interface AopProxy {
    Object getProxy();
}

JdkDynamicAopProxy实现了InvocationHandler接口并提供了Proxy.newProxyInstance(...)的封装方法。

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    private final Object target;
    private final List<Interceptor> advised;
    
    public JdkDynamicAopProxy(Object target, List<Interceptor> advised) {
        this.target = target;
        this.advised = advised;
    }
    
    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (advised == null) {
            return method.invoke(target, args);
        } else {
            ReflectiveMethodInvocation invocation = new ReflectiveMethodInvocation(target, method, args, advised);
            
            return invocation.proceed();
        }
    }
}

可以看到,其核心的invoke方法包含了增强处理,处理包含在Interceptor中,这是个List,表明可以有多个且是链式调用的方式,虽然我这里没有体现出来,但其实是个责任链模式。如果advised不为空,就会构造一个ReflectiveMethodInvocation实例并调用其proceed()方法。

public class ReflectiveMethodInvocation implements MethodInvocation {
    private final Object target;
    private final Method method;
    private final Object[] args;
    private final List<Interceptor> advised;
    private int currentInterceptorIndex = -1;
    
    public ReflectiveMethodInvocation(Object target, Method method, Object[] args, List<Interceptor> advised) {
        this.target = target;
        this.advised = advised;
        this.method = method;
        this.args = args;
    }

    @Override
    public Object proceed() throws Throwable {
        if (currentInterceptorIndex < advised.size() - 1) {
            Interceptor interceptor = advised.get(++currentInterceptorIndex);
            
            return interceptor.invoke(this);
        } else {
            return method.invoke(target, args);
        }
    }
}

ReflectiveMethodInvocation包含了反射和增强通知(advised)的全部参数,所以proceed方法调用了增强通知的invoke方法,并将当前的参数继续传递过去,方便增强通知处理完之后回调proceed方法,继续将责任链进行下去。

public class MethodBeforeAdviceInterceptor implements Interceptor {

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        System.out.println("do sth. before...");
        
        return mi.proceed();
    }
}
public class MethodReturningAdviceInterceptor implements Interceptor {

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        
        System.out.println("do sth. after returning...");
        
        return retVal;
    }
}
public class MethodThrowingAdviceInterceptor implements Interceptor { 

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        } catch (Throwable ex) {
            System.out.println("do sth. after throwing...");
            
            throw ex;
        }
    }
}

以上三个常见的通知拦截器简单表示了增强通知是怎么处理的。可以看到,处理完自己的增强事情后,都回调了MethodInvocation的proceed方法,这个例子中就是ReflectiveMethodInvocation的proceed,然后ReflectiveMethodInvocation继续责任链的下一个增强通知处理,依次处理直到责任链结束。

public class App {
    public static void main(String[] args) {
        List<Interceptor> interceptorList = new ArrayList<>();
        interceptorList.add(new MethodBeforeAdviceInterceptor());
        interceptorList.add(new MethodReturningAdviceInterceptor());
        interceptorList.add(new MethodThrowingAdviceInterceptor());
        
        AopProxy aopProxy = new JdkDynamicAopProxy(new Child(), interceptorList);
        Person childProxy = (Person) aopProxy.getProxy();
        childProxy.play("video game"); // Output1
//        childProxy.play(null); // Output2
    }
}
// Output1:
do sth. before...
child play: video game. feel happy.
do sth. after returning...

// Output2:
do sth. before...
do sth. after throwing...
Exception in thread "main" ...
Caused by: java.lang.IllegalArgumentException
    at spring.aop.simulation.service.Child.play(Child.java:8)
    ... 14 more

示例代码点击下载
main方法中构造了一个通知List模拟增强通知链,例子看起来简单,但实际跑一遍debug走走才能体会到含义。至此就能理解Spring是怎样通过JDK动态代理搞出AOP这么个东西了,并没有什么高深的技术,但这其中的思想真的是令人受益匪浅。至于CGLIB,和动态代理差不多,只不过生成代理类的方式和条件不一样而且,但在AOP方面和动态代理就大同小异了。

最后补充一些关于AspectJ的概念:
一提起AspectJ,其实我感觉绝大多数人都会联想到Spring。毕竟,大多数人都是通过spring才接触到了AspectJ。但AspectJ其实是eclipse基金会的一个项目,官网就在eclipse官网里。

我们知道面向切面编程(Aspect Oriented Programming)有诸多好处,但是在使用AspectJ之前我们一般是怎么编写切面的呢?我想一般 来说应该是三种吧:静态代理,jdk动态代理,cglib。

  • 静态代理的重用性太差,一个代理不能同时代理多种类;
  • 动态代理可以做到代理的重用,但调用起来还是比较麻烦,除了写切面代码以外,还需要将代理类耦合进被代理类的调用阶段,在创建被代理类的时候都要先创建代理类,再用代理类去创建被代理类,
    这就稍微有点麻烦了。比如我们想在现有的某个项目里统一新加入一些切面,这时候就需要创建切面并且侵入原有代码,在创建对象的时候添加代理,还是挺麻烦的。
    说到底,这种麻烦出现的本质原因是,代理模式并没有做到切面与业务代码的解耦。虽然将切面的逻辑独立进了代理类,但是决定是否使用切面的权利仍然在业务代码中。这才导致了上面这种问题。(当然,话不能说的这么绝对,如果有那种类似Spring的IoC容器,将类的创建都统一托管起来,我们只需要将切面用配置文件进行注册,容器会根据注册信息在创建bean的时候自动加上代理,这也是比较方便的。不过并不是所有框架都提供IoC机制的吧。。。)

既然代理模式这么麻烦,那么AspectJ又是通过什么方式来避免这个麻烦的呢?

我总结AspectJ提供了两套强大的机制:

  • 第一套是切面语法。就是网上到处都是的那种所谓”AspectJ使用方法”,这套东西做到了将决定是否使用切面的权利还给了切面。在写切面的时候就可以决定哪些类的哪些方法会被代理,从而从逻辑上不需要侵入业务代码。由于这套语法实在是太有名,导致很多人都误以为AspectJ等于切面语法,其实不然。
  • 第二套是织入工具。刚才讲到切面语法能够让切面从逻辑上与业务代码解耦,但是从操作上来讲,当JVM运行业务代码的时候,他甚至无从得知旁边还有个类想横插一刀。。。这个问题大概有两种解决思路,一种就是提供注册机制,通过额外的配置文件指明哪些类受到切面的影响,不过这还是需要干涉对象创建的过程;另外一种解决思路就是在编译期(或者类加载期)我们优先考虑一下切面代码,并将切面代码通过某种形式插入到业务代码中,这样业务代码不就知道自己被“切”了么?这种思路的一个实现就是aspectjweaver,就是这里的织入工具。

Spring AOP旨在提供跨Spring IoC的简单AOP实现,以解决程序员面临的最常见问题。 它不是一个完整的AOP解决方案 - 它只能应用于由Spring容器管理的bean。

另一方面,AspectJ是最初的AOP技术,旨在提供完整的AOP解决方案。 它比Spring AOP更强大但也更复杂。 值得注意的是,AspectJ可以应用于所有域对象。

我们知道Spring里有很多基于动态代理的设计,而我们知道动态代理也可以被用作面向切面的编程,但是Spring AOP本身却支持AspectJ的切面语法,而且spring-aop这个包也引用了AspectJ,我们知道AspectJ是通过织入的方式来实现AOP的。那么Spring AOP究竟是通过织入还是代理来实现aop的呢?

其实spring aop还是通过动态代理来实现aop的,即使不去看他的源码,我们也可以通过简单的实验来得到这个结论。
根据aspectj的使用方式,我们知道,如果要向代码中织入切面,那么我们要么采用ajc编译,要么使用aspectjweaver的agent代理。但是spring既没有依赖任何aspectjtools的相关jar包,虽然依赖了aspectjweaver这个包,但是并没有添加agent代理。当然,也存在一种可能就是spring利用aspectjweaver这个包自己实现了动态织入,但是从可复用的角度讲,spring真的会自己重新造轮子?如果真的重新造了那为啥不脱离aspectj彻底重新造,而是用一半造一半呢?
而且,我们知道用织入和用动态代理有一个很大的区别,如果使用织入的话,那么调业务对象的getClass()方法获得的类名就是这个类本身实现的类名;但是如果使用动态代理的话,调用getClass()方法获得的类名就是动态代理类的类名了。做一个简单的实验我们就可以发现,如果我们使用spring aop来对某一个service进行切面处理,那么调用getClass()方法获得的结果就是:

com.mythsman.test.Myservice$$EnhancerBySpringCGLIB$$3afc9148

显然,虽然spring aop采用了aspectj语法来定义切面,但是在实现切面逻辑的时候还是采用CGLIB生成代理的方法。

以上介绍了Aspect是为了更好的了解Spring AOP的使用,希望能有点用。

这一段看起来可能有些难受,因为我只是截取了引用链接文章中一部分,具体可以常看参考链接中的文章。

提前祝5.1快乐~~!

标签: none

添加新评论

ali-01.gifali-58.gifali-09.gifali-23.gifali-04.gifali-46.gifali-57.gifali-22.gifali-38.gifali-13.gifali-10.gifali-34.gifali-06.gifali-37.gifali-42.gifali-35.gifali-12.gifali-30.gifali-16.gifali-54.gifali-55.gifali-59.gif

加载中……