什么是动态代理

181 阅读4分钟

在上一篇文章中,详细介绍了 Java 中的反射机制,反射为 Java 提供了运行时修改程序的能力。

与之相对的,Java 中有另外一个技术也经常被提及,动态代理

这个技术在 Java 中同样在很多框架中同样得到了大量的应用。

那么动态代理到底是什么,和反射又有什么关系?

💡本文基于 OpenJDK11

1. 代理模式

在讲动态代理之前,我们需要先了解一下代理模式。

假设现在有一个 RPC 接口,需要统计每个 RPC 接口的调用时间,但是这些统计执行时间的代码于业务逻辑没有关系,这些代码适合独立出来。

public interface Hello {
    void sayHello(String name);
}

public class HelloImpl implements Hello {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

使用代理模式就是一个比较好的办法,代理模式通常用于为现有的类添加额外的功能,而且不用修改现有的代码。

代理模式可以分成静态代理动态代理

需要注意,如果使用动态代理,最好现有的类是基于接口来实现的。如果不是基于接口,那么这个类只能实现静态代理,而无法实现基于反射的动态代理。

2. 静态代理实现

静态代理的实现比较简单,核心就是实现这个接口,然后就可以在这个实现中调用目标对象的方法,并且可以做一些额外的事情。

public class HelloStaticProxy implements Hello {

    private Hello helloImpl;

    public HelloStaticProxy(Hello helloImpl) {
        this.helloImpl = helloImpl;
    }

    @Override
    public void sayHello(String name) {
        long begin = System.currentTimeMillis();
        helloImpl.sayHello(name);
        System.out.println("Invoke time: " + (System.currentTimeMillis() - begin) + " ms");
    }
}

上面的代码就可以统计出 sayHello 这个 rpc 接口的调用时间。

但是静态代理有一个很大的问题,假设有 n 个 rpc 接口,就需要把上面的逻辑重复实现很多遍。这对于大型系统或者通用框架中肯定是不能接受的。

但是静态代理的优点是性能好,在只有个别类需要被代理的时候,静态代理还是首选。

3. 动态代理实现

在 Java 中,动态代理有多种实现。

最直接的一种就是通过反射来实现。代码也不复杂,与上面静态代理不同的地方在于,使用动态代理可以生成任何类的代理。

如果选择使用反射来实现动态代理,那么就要求这个被代理的类是基于接口实现的。

下面代码中 InvocationHandlerProxy 都在 java.lang.reflect 中:

Hello hello = new HelloImpl();
HelloDynamicProxyHandler handler = new HelloDynamicProxyHandler(hello);
// 生成的动态代理对象
Hello helloProxy = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);

// 调用方法
helloProxy.sayHello("ray");

public class HelloDynamicProxyHandler implements InvocationHandler {

    private Object target;

    public HelloDynamicProxyHandler(Object object) {
        this.target = object;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] params) throws Throwable {

        long begin = System.currentTimeMillis();
        Object result = method.invoke(target, params);
        System.out.println("Invoke time: " + (System.currentTimeMillis() - begin) + " ms");

        return result;
    }
}

其实这种实现很容易理解,我把上篇文章中利用反射来动态执行方法的代码贴在这里:

// 利用反射动态执行方法
public Object methodInvoke(Object o, Method method, Object[] params) {
    try {
        return method.invoke(o, params);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return null;
}

与上面动态代理的实现基本一样,这是动态代理最直观,最简单的一种实现方式。

相比于静态代理,动态的性能就要差很多,但是更灵活。

其他的实现就需要引入额外的依赖,比如 cglib 等。cglib 是通过生成被代理类的子类对象来作为动态代理,所有就不要求被代理类是基于接口实现的。

这些具体的实现后续再介绍,这篇文章重点还是放在动态代理本身。

4. 动态代理的应用

动态代理在 Java 最常用的场景就是 AOP 编程和解耦。

在 Spring 中 AOP 的实现有两种,反射和 cglib,可以自由选择。

spring.aop.proxy-target-class=true

在 Java8 以前,选择使用 cglib 更多是因为性能的原因,但是在之后,就没必要了。

除了AOP,动态代理也可以用于解耦,典型的情况就是在 RPC 中,使用动态代理,可以让调用远程的接口和本地方法一样简单。

同时动态代理还可以将很多于业务无关的细节屏蔽,比如权限,统计等等。

文 / Rayjun