JDK动态代理简述

Spring 中常见的动态代理有两种:

  • 基于JDK自身的
  • 基于CGLIB的

两者的区别主要是:

  • 基于JDK自身的代理方式需要被代理类的方法是接口中的方法;
  • 基于CGLIB的则无此限制要求。

本文通过一个小demo,来说明JDK的动态代理工作方式。

要有接口

1
2
3
4
5
6
public interface IWoker {

    void doWork();

    void getSalary();
}

接口的实现类

1
2
3
4
5
6
7
8
9
10
11
public class Programmer implements IWoker {
    @Override
    public void doWork() {
        System.out.println("Coding....");
    }

    @Override
    public void getSalary() {
        System.out.println("+100k...");
    }
}

代理者要做的事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WorkerInvocationHandler implements InvocationHandler {
    private IWoker target;

    public WorkerInvocationHandler(IWoker target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("doWork".equals(method.getName())) {
            System.out.println("AI can help!");
            method.invoke(target, args);
        } else if ("getSalary".equals(method.getName())) {
            System.out.println("I want double!");
            method.invoke(target, args);
            method.invoke(target, args);
        }
        return null;
    }
}

InvocationHandler 接口中只有一个 invoke 方法:

1
2
3
4
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

当代理对外提供服务时,都会调用该方法,因此所有的增强逻辑均在该方法中实现。

简单测试一下

1
2
3
4
5
6
7
8
9
10
11
public class ProxyTest {
    public static void main(String[] args) {
        IWoker worker = new Programmer();
        IWoker aiWorker = (IWoker) Proxy.newProxyInstance(
                worker.getClass().getClassLoader(),
                worker.getClass().getInterfaces(),
                new WorkerInvocationHandler(worker));
        aiWorker.doWork();
        aiWorker.getSalary();
    }
}

输出:

1
2
3
4
5
AI can help!
Coding....
I want double!
+100k...
+100k...

可以看到,代理已生效了。

简易版的Spring AOP 的 JdkDynamicAopProxy

在 Spring 中的 JdkDynamicAopProxy类实现了 AopProxyInvocationHandler接口,其中 invoke方法的逻辑还是挺复杂的。

现在我们来“照虎画猫”,写一个简易版的JdkDynamicAopProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SimpleJdkProxy implements AopProxy, InvocationHandler {
    // 要被代理增强的目标类
    private Object target;

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object getProxy() {
        return getProxy(target.getClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return Proxy.newProxyInstance(
                classLoader,
                target.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method invocation.");
        Object result = method.invoke(target, args);
        System.out.println("After method invocation.");
        return result;
    }
}

照例要测试一下:

1
2
3
4
5
6
7
8
9
10
public class ProxyTest {
    public static void main(String[] args) {
        IWoker worker = new Programmer();
        SimpleJdkProxy simpleJdkProxy = new SimpleJdkProxy();
        simpleJdkProxy.setTarget(worker);
        IWoker proxyWorker = (IWoker) simpleJdkProxy.getProxy();
        proxyWorker.doWork();
        proxyWorker.getSalary();
    }
}

输出:

1
2
3
4
5
6
Before method invocation.
Coding....
After method invocation.
Before method invocation.
+100k...
After method invocation.

知其所以然

通过上面的demo例子,对于动态代理想必多少有点了解了。但是知其然,还要知其所以然。

生成代理类的方法 Proxy.newProxyInstance(...) 主要做了以下几件事:

  1. 生成一个实现了入参中所有的接口,并继承了 Proxy 类的代理类的字节码;
  2. 使用入参中的类加载器来加载这个字节码;
  3. 使用代理类父类ProxyProxy(InvocationHandler h)构造方法,将入参中的自定义InvocationHandler传入,实例化对象。

Talk is cheap, let’s look the code.

通过在main方法加上这行代码,就可以把生成的代理类保存在本地磁盘了。

1
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

将代理类的class文件反编译一下,有点长:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.sun.proxy;

import com.wzt.proxy.jdk.IWoker;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements IWoker {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void getSalary() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void doWork() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m4 = Class.forName("com.wzt.proxy.jdk.IWoker").getMethod("getSalary", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.wzt.proxy.jdk.IWoker").getMethod("doWork", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从上面的代码中可以看到,代理类一共实现了5个方法:

  • 属于我们需要的,接口中的2个方法;
  • 以及Object类中的 equalstoStringhashCode方法。

这些方法都是通过反射去获取的;在调用这些方法时,都是通过InvocationHandlerinvoke方法去调用的。

从这个动态生成的代理类也可以说明,为啥JDk的动态代理只能基于接口的了: 因为代理类要继承 Proxy 类,而Java又不能多重继承,自然也就不能基于类去动态生成代理了。

而能够基于类动态代理的CGLIB方式,下次有机会再说吧。

(完)