Java 技术之动态代理机制

之前的文章里讲解过了Java的反射机制、垃圾回收机制,这一次我们来讲解一个更有意思的机制:动态代理。学习下Java里为什么出现这样一个机制,什么场合下会使用这个机制。

《Java 技术之反射》
《Java 技术之类加载机制》

静态代理

常规的代理模式有以下三个部分组成:
功能接口

  • interface IFunction {
  • void doAThing();
  • }

功能提供者

  • class FunctionProvider implement IFunction {
  • public void doAThing {
  • System.out.print("do A");
  • }
  • }

功能代理者

  • class Proxy implement IFunction {
  • private FunctionProvider provider;
  • Proxy(FunctionProvider provider) {
  • this.provider = provider;
  • }
  • public void doAThing {
  • provider.doAThing();
  • }
  • }

前两者就是普通的接口和实现类,而第三个就是所谓的代理类。对于使用者而言,他会让代理类去完成某件任务,并不关心这件任务具体的跑腿者。

这就是静态代理,好处是方便调整变换具体实现类,而使用者不会受到任何影响。

不过这种方式也存在弊端:比如有多个接口需要进行代理,那么就要为每一个功能提供者创建对应的一个代理类,那就会越来越庞大。而且,所谓的“静态”代理,意味着必须提前知道被代理的委托类。

通过下面一个例子来说明下:

统计函数耗时--静态代理实现

现在希望通过一个代理类,对我感兴趣的方法进行耗时统计,利用静态代理有如下实现:

  • interface IAFunc {
  • void doA();
  • }
  • interface IBFunc {
  • void doB();
  • }
  • class TimeConsumeProxy implement IAFunc, IBFunc {
  • private AFunc a;
  • private BFunc b;
  • public(AFunc a, BFunc b) {
  • this.a = a;
  • this.b = b;
  • }
  • void doA() {
  • long start = System.currentMillions();
  • a.doA();
  • System.out.println("耗时:" + (System.currentMillions() - start));
  • }
  • void doB() {
  • long start = System.currentMillions();
  • b.doB();
  • System.out.println("耗时:" + (System.currentMillions() - start));
  • }
  • }

弊端很明显,如果接口越多,每新增一个函数都要去修改这个TimeConsumeProxy代理类:把委托类对象传进去,实现接口,在函数执行前后统计耗时。

这种方式显然不是可持续性的,下面来看下使用动态代理的实现方式,进行对比。

动态代理

动态代理的核心思想是通过Java Proxy类,为传入进来的任意对象动态生成一个代理对象,这个代理对象默认实现了原始对象的所有接口。

还是通过统计函数耗时例子来说明更加直接。

统计函数耗时--动态代理实现

  • interface IAFunc {
  • void doA();
  • }
  • interface IBFunc {
  • void doB();
  • }
  • class A implement IAFunc { ... }
  • class B implement IBFunc { ... }
  • class TimeConsumeProxy implements InvocationHandler {
  • private Object realObject;
  • public Object bind(Object realObject) {
  • this.realObject = realObject;
  • Object proxyObject = Proxy.newInstance(
  • realObject.getClass().getClassLoader(),
  • realObject.getClass().getInterfaces(),
  • this
  • );
  • return proxyObject;
  • }
  • @Override
  • public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  • long start = System.currentMillions();
  • Object result = method.invoke(target, args);
  • System.out.println("耗时:" + (System.currentMillions() - start));
  • return result;
  • }
  • }

具体使用时:

  • public static void main(String[] args) {
  • A a = new A();
  • IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
  • aProxy.doA();
  • B b = new B();
  • IBFunc bProxy = (IBFunc) new TimeConsumeProxy().bind(b);
  • bProxy.doB();
  • }

这里最大的区别就是:代理类和委托类互相透明独立,逻辑没有任何耦合,在运行时才绑定在一起。这也就是静态代理与动态代理最大的不同,带来的好处就是:无论委托类有多少个,代理类不受到任何影响,而且在编译时无需知道具体委托类。

回到动态代理本身,上面代码中最重要的就是:

  • Object proxyObject = Proxy.newInstance(
  • realObject.getClass().getClassLoader(),
  • realObject.getClass().getInterfaces(),
  • this
  • );

通过Proxy工具,把真实委托类转换成了一个代理类,最开始提到了一个代理模式的三要素:功能接口、功能提供者、功能代理者;在这里对应的就是:realObject.getClass().getInterfaces()realObjectTimeConsumeProxy

其实动态代理并不复杂,通过一个Proxy工具,为委托类的接口自动生成一个代理对象,后续的函数调用都通过这个代理对象进行发起,最终会执行到InvocationHandler#invoke方法,在这个方法里除了调用真实委托类对应的方法,还可以做一些其他自定义的逻辑,比如上面的运行耗时统计等。

探索动态代理实现机制

好了,上面我们已经把动态代理的基本用法及为什么要用动态代理进行了讲解,很多文章到这里也差不多了,不过我们还准备进一步探索一下给感兴趣的读者。

抛出几个问题:

  1. 上面生成的代理对象Object proxyObject究竟是个什么东西?为什么它可以转型成IAFunc,还能调用doA()方法?
  2. 这个proxyObject是怎么生成出来的?它是一个class吗?

下面我先给出答案,再一步步探究这个答案是如何来的。

问题一: proxyObject究竟是个什么 -> 动态生成的$Proxy0.class文件

在调用Proxy.newInstance后,Java最终会为委托类A生成一个真实的class文件:$Proxy0.class,而proxyObject就是这个class的一个实例。

猜一下,这个$Proxy0.class类长什么样呢,包含了什么方法呢?回看下刚刚的代码:

  • IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
  • aProxy.doA();

推理下,显然这个$Proxy0.class实现了 IAFunc 接口,同时它内部也实现了doA()方法,而且重点是:这个doA()方法在运行时会执行到TimeConsumeProxy#invoke()方法里。

重点来了!下面我们来看下这个$Proxy0.class文件,把它放进IDE反编译下,可以看到如下内容,来验证下刚刚的猜想:

```java
final class $Proxy0 extends Proxy implements IAFunc {
private static Method m1;
private static Method m3;

top Created with Sketch.