动态代理之JDK实现

2017-07-09
JDK SOURCE

最近呢,看到了一个开源的PRC FRAMEWORK,当然不是Dubbo。然后想去了解一下RPC到底是怎么去实现的。于是乎就了解了一番,发现其灵魂在于动态代理和反射。

What’s Dynamic Proxy ?

在讲什么是动态代理前先得明白什么是代理。在日常生活中的代理其实就是委托的意思。将事情交代给委托对象去做,这就是代理。程序中的代理是什么意思呢?这里更准确的解释应该是一种设计模式–代理模式(Proxy)
Proxy
下面给出一个简单的关于代理的Java实现。

1
2
3
4
public interface Sourceable {
//主题接口
public void method();
}

1
2
3
4
5
6
7
public class Source implements Sourceable {
//接口实现
@Override
public void method() {
System.out.println("the original method!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//代理类
public class Proxy implements Sourceable {
private Source source;
public Proxy(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method();
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
1
2
3
4
5
6
7
//调用客户端
public class ProxyTest {
public static void main(String[] args) {
Sourceable source = new Proxy();
source.method();
}
}

其中代理类中对主题接口进行了增强—在主题方法调用前后调用了beforeafter 。Spring AOP中的思想正是如此。
这就是Java中的代理模式,只不过上面的代码实现是基于硬编码的,也就是所说的静态代理。那么区别于静态代理,那就一定有动态代理了
通过以上的代码可以看出,静态代理将代码写死,是在编译阶段完成的具体代理类的绑定。但是动态代理不是这么做的,而是在程序运行时完成的这操作。下面使用动态代理方式实现。

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
//使用jdk动态代理实现需要实现接口InvocationHandler
public class JdkProxy implements InvocationHandler{
private Object targrt;//代理的真实对象,也就是接口的实现类
public JdkProxy(Object targrt){
this.targrt = targrt;
}
/**
*
* 该方法负责集中处理动态代理类上的所有方法调用。
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
* @param proxy 代理类实例
* @param method 被调用的方法对象
* @param args 调用参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里来处理代理的具体工作
//假设在执行前进行一个打印日志的处理
before();
Object result = method.invoke(targrt, args);
//假设执行结束后打印一个执行完成的通知
atfer();
//将执行结果返回如果有的话
return result;
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
public Object getProxy(Class[] interfaceClass){
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
interfaceClass,this);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
//客户端
public class JdkProxyCase {
public static void main(String[] args) {
Sourceable s = new Source();
JdkProxy proxy = new JdkProxy(s);
Sourceable hello = (Sourceable) proxy.getProxy(new Class[]{Sourceable.class});
hello.method();
}
}

由此可见,我们对主题对象所有的方法的调用都会变成对invoke方法的调用,而我们可以在这个方法中添加统一的逻辑处理。
所以可以看出,动态代理的几个好处:

  • 易于维护。相对于静态代理来说,只要在Proxy类中固定好处理逻辑而不用针对每个方法去编写代码了。控制了代码量,便于维护。
  • 使AOP编程更加容易。在Spring的帮助下轻松添加、移除动态代理,且对源代码没有任何影响。
  • 解耦。可以通过参数就能判断具体实现类,不需要事先实例化,更加灵活多变。
    The Mechanism of Dynamic Proxy
    谈完了什么是动态代理,现在就可以来了解一下动态代理是怎么实现的了。要去知道Jdk动态代理是怎么实现的还得去源码中找。
    看看Proxy#newProxyInstance的实现。
    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
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
    throws IllegalArgumentException
    {
    if (h == null) {
    throw new NullPointerException();
    }
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
    checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
    }
    /*
    * 找缓存里有没有Proxy对象,没有就生成一个.
    */
    Class<?> cl = getProxyClass0(loader, interfaces);
    /*
    * Invoke its constructor with the designated invocation handler.
    * 用指定的handler来调用构造方法
    */
    try {
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
    // create proxy instance with doPrivilege as the proxy class may
    // implement non-public interfaces that requires a special permission
    return AccessController.doPrivileged(new PrivilegedAction<Object>() {
    public Object run() {
    return newInstance(cons, ih);
    }
    });
    } else {
    return newInstance(cons, ih);
    }
    } catch (NoSuchMethodException e) {
    throw new InternalError(e.toString());
    }
    }

从上面的代码中不难看出其核心在于获取一个代理类对象。得到类对象后再通过反射去创建代理对象实例。看看getProxyClass0怎么实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}

呃…好吧!这个方法是从一个cache对象中去拿Proxy类对象的。所以我们还是不知道代理类对象是怎么产生的。不过这里有一点有点意思,就是代理类实现的接口数不能超过65535个。看到这个数字是否很惊喜或者也有点意外?关于65535我特意去查了一下Wiki)。这是Java语言机制导致的。当然没有哪个变态去实现这么多个接口吧。
既然从cache中去找,那么看看这个cache到底是何方神圣。

1
2
3
4
5
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

具体到这个cache是怎么实现的就不去纠结了,因为纠结也没用(API文档中没有这个类的解释,因为这个类的修饰是package-private的而不是public)。仔细以看有一个ProxyClassFactory对象作为参数传给这个cache了。这下自貌似有搞头了。看看这个类的名字就很爽—代理类工厂嘛。实际上这是Proxy的一个静态内部类。

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
84
85
86
87
88
89
90
91
92
93
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 生成代理类名称的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 用于生成代理类名字的计数器
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
//各种验证
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
//代理类的包名
String proxyPkg = null; // package to define proxy class in
//对于不是public修饰的接口,代理类的包名和接口包名一致
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
//public修饰的接口 包名统一为com.sun.proxy
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
// 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 这里才是真正的生成代理类的字节码的地方
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// 根据二进制字节码返回相应的Class实例
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}

唉,感觉看了半天发现又被绕进去了。没办法,继续看看ProxyGenerator这个类怎么去实现的。这个是Jdk私有的,但是可以反编译查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static byte[] generateProxyClass(final String var0, Class[] var1) {
ProxyGenerator var2 = new ProxyGenerator(var0, var1);
final byte[] var3 = var2.generateClassFile();
// 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观
if(saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
try {
FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
var1.write(var3);
var1.close();
return null;
} catch (IOException var2) {
throw new InternalError("I/O exception saving generated file: " + var2);
}
}
});
}
return var3;
}

其中,这个参数的定义是酱紫的。

1
private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

所以我们可以设置sun.misc.ProxyGenerator.saveGeneratedFiles这个系统属性为true来把生成的class保存到本地文件来查看。

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

加上这段代码后执行main会报这样的错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Exception in thread "main" java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException: com/sun/proxy/$Proxy0.class (No such file or directory)
at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:336)
at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:327)
at java.security.AccessController.doPrivileged(Native Method)
at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:326)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:671)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:591)
at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:244)
at java.lang.reflect.WeakCache.get(WeakCache.java:141)
at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:454)
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:736)
at com.dw.test.JdkProxy.getProxy(JdkProxy.java:45)
at com.dw.test.JdkProxyCase.main(JdkProxyCase.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

解决办法是在工程根路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存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
public final class $Proxy0 extends Proxy implements Hello{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
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 String sayHello(String var1) throws {
try {
//调用父类的handler的invoke方法 实际上就是调用我们实现InvocationHandler接口的类的invoke 我们的逻辑在这里处理
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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);
}
}
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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.dw.service_api.Hello").getMethod("sayHello", new Class[]{Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}

从生成出来的这个类中可以看到

  1. 这个类继承自Proxy实现了代理接口。所以JDK动态代理只能对接口进行代理,而不能对实现类进行代理。这是Java语言不能多继承导致的。
  2. 构造方法的参数是InvocationHandler。这个参数是由我们调用Proxy#newProxyInstance方法传进去的。
  3. 重写了Object类的equalshashCodetoString,它们都只是简单的调用了InvocationHandlerinvoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
    从这里可以联想到Spring AOP的机制和这个原理其实是一样的,可能实现会比这个复杂的多。
    Summary
  4. 使用JDK实现动态代理需要实现InvacationHandler接口,使用Proxy#newProxyInstacne返回代理对象。
  5. JDK动态代理的机制是通过在程序运行时动态地去生成字节码文件,然后加载到内存生成实例。
  6. JDK动态代理只能对接口进行代理,不能对实现类代理。因为Java语言不支持多继承。要想对实现类进行代理可以使用Cglib来实现。

留言: