Java - 静态代理和动态代理

Java - 静态代理和动态代理

Scroll Down

由于后面要写的文章需要用到代理的知识,所以先写这篇文章为后面的知识做铺垫。

代理就是定义一个行为和某类的对象相似,而又潜在地表示了所有继承自该类的对象的东西。

image.png

在说动态代理之前,必须先聊聊什么是静态代理。

静态代理

假设现在有这么一个需求:在某个类的方法前后打印日志。

那么要如何做到在不修改源代码前提下,来实现这个功能呢?

首先想到的就是静态代理,具体做法是:

  1. 为这个类编写一个对应的代理类,并让它实现与目标类相同的接口。
    image.png

  2. 在创建代理对象是,通过构造函数传入一个实现了该接口的目标对象,然后在代理对象内部调用目标对象的同名方法,并在调用之前后打印日志。也就是说,代理对象 = 目标对象 + 增强代码。而有了代理对象后,就不需要原对象了。
    image.png

编写代码进行测试

  1. 首先先新建一个接口A
public interface A {
    int add();
}

2.创建一个对应的实现类 AImpl

public class AImpl implements A {
    @Override
    public int add() {
        return 1 + 2;
    }
}
  1. 接下来建立一个目标对象的代理类 AProxy
public class AProxy {
    private final A target;

    public AProxy(A target) {
        this.target = target;
    }

    public int add() {
        System.out.println("方法执行之前...");
        System.out.println(target.add());
        System.out.println("方法执行之后...");
        return 0;
    }
}
  1. 运行 代理类中的 add 方法进行测试
    image.png

结果在我们意料之中,这样就实现了一个简单的静态代理。

静态代理的缺陷

当在类少的时候还好,但是当你的项目中有成千上万个类时,你需要为每个类都写一个对应的代理类,这工作量太大了。

我们能不能少写或者不写代理类,却能完成代理功能呢?接下来我们来介绍动态代理。

动态代理

Java的动态代理可以动态的创建代理并动态的处理对所代理方法的调用。动态代理有两种实现方法,一种是使用JDK自带的,一种是使用Cglib实现。在这里就先聊聊JDK的动态代理是怎么实现的。

JDK中提供了 invocationHandler 接口和 Proxy 类,借助这两个工具可以达到我们想要的效果。

InvocationHandler 接口

InvocationHandler 接口是 proxy 代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

Proxy 类

Proxy 类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance 方法。

下面我们先来看看代码

public class ProxyTest {
    public static void main(String[] args) {
        ClassLoader classLoader = ProxyTest.class.getClassLoader();
        Class<?>[] interfaces = {A.class};
        A aImpl = new AImpl();
        A aProxy = (A) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                System.out.println("方法执行之前");
                result = method.invoke(aImpl, args);
                System.out.println(result);
                System.out.println("方法执行之后");
                return result;
            }
        });
        aProxy.add();
    }
}

上面这段代码是什么意思呢?

  1. 首先我们可以调用 Proxy 类的 newProxyInstance 方法获取代理对象。查看源码可以看到该方法需要接收三个参数
    image.png

    • ClassLoader loader,类加载器, 随便找个类的加载器就可以
    • Class<?>[] interfaces,目标对象实现的接口
    • InvocationHandler h,传入一个实现了该接口的实现类
  2. 获取 Proxy.newProxyInstance(...) 创建的代理对象,调用其方法

来看结果:

image.png

完美!

我们来分析一下整个流程:

  1. 通过 Proxy.newProxyInstance(...) 获得代理对象
  2. 代理对象调用 add 方法
  3. JVM通过将方法导向致 invoke 方法
  4. 执行invoke方法,若有返回值则返回

image.png

总结

  1. 静态代理实现较简单,代理类在编译期生成,效率高。缺点是会生成大量的代理类。
  2. JDK动态代理不要求代理类和委托类实现同一个接口,但是委托类需要实现接口,代理类需要实现InvocationHandler接口。
  3. 动态代理要求代理类InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
支付宝 微信