JDK动态代理

关于JDK的动态代理,最为人熟知的可能要数spring AOP的实现,默认情况下,Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现。那么,什么是JDK的动态代理呢?

 

JDK的动态代理,就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,并加载运行的过程。JDK从1.3开始支持动态代理。那么JDK是如何生成动态代理的呢?JDK动态代理为什么不支持类的代理,只支持接口的代理?

 

首先来看一下如何使用JDK动态代理。JDK提供了Java.lang.reflect.Proxy类来实现动态代理的,可通过它的newProxyInstance来获得代理实现类。同时对于代理的接口的实际处理,是一个java.lang.reflect.InvocationHandler,它提供了一个invoke方法供实现者提供相应的代理逻辑的实现。可以对实际的实现进行一些特殊的处理,像Spring AOP中的各种advice。下面来看看如何使用。

 

被代理的接口

1 package com.mikan.proxy;  
2 /** 
3  * @author Mikan 
4  * @date 2015-09-15 18:00 
5  */  
6 public interface HelloWorld {  
7     void sayHello(String name);  
8 }  

接口的实现类:

 

 1 package com.mikan.proxy;  
 2 /** 
 3  * @author Mikan 
 4  * @date 2015-09-15 18:01 
 5  */  
 6 public class HelloWorldImpl implements HelloWorld {  
 7     @Override  
 8     public void sayHello(String name) {  
 9         System.out.println("Hello " + name);  
10     }  
11 }   

实现一个java.lang.reflect.InvocationHandler

 

 1 package com.mikan.proxy;  
 2 import java.lang.reflect.InvocationHandler;  
 3 import java.lang.reflect.Method;  
 4 /** 
 5  * @author Mikan 
 6  * @date 2015-09-15 19:53 
 7  */  
 8 public class CustomInvocationHandler implements InvocationHandler {  
 9     private Object target;  
10     public CustomInvocationHandler(Object target) {  
11         this.target = target;  
12     }  
13     @Override  
14     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
15         System.out.println("Before invocation");  
16         Object retVal = method.invoke(target, args);  
17         System.out.println("After invocation");  
18         return retVal;  
19     }  
20 }  

使用代理:

 

 1 package com.mikan.proxy;   
 2 import java.lang.reflect.Proxy;  
 3 /** 
 4  * @author Mikan 
 5  * @date 2015-09-15 18:01 
 6  */  
 7 public class ProxyTest {  
 8     public static void main(String[] args) throws Exception {  
 9         System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
10         CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());  
11         HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(  
12                 ProxyTest.class.getClassLoader(),  
13                 new Class[]{HelloWorld.class},  
14                 handler);  
15         proxy.sayHello("Mikan");  
16     }  
17 }  

 

运行的输出结果:

localhost:classes mikan$ java com/mikan/proxy/ProxyTest  
Before invocation  
Hello Mikan  
After invocation 

 

从上面可以看出,JDK的动态代理使用起来非常简单,但是只知道如何使用是不够的,知其然,还需知其所以然。所以要想搞清楚它的实现,那么得从源码入手。这里的源码是1.7.0_79。首先来看看它是如何生成代理类的:

 1 public static Object newProxyInstance(ClassLoader loader,  
 2                                       Class<?>[] interfaces,  
 3                                       InvocationHandler h)  
 4     throws IllegalArgumentException {  
 5     if (h == null) {  
 6         throw new NullPointerException();  
 7     }  
 8   
 9     final Class<?>[] intfs = interfaces.clone();  
10     final SecurityManager sm = System.getSecurityManager();  
11     if (sm != null) {  
12         checkProxyAccess(Reflection.getCallerClass(), loader, intfs);  
13     }  
14     // 这里是生成class的地方  
15     Class<?> cl = getProxyClass0(loader, intfs);  
16     // 使用我们实现的InvocationHandler作为参数调用构造方法来获得代理类的实例  
17     try {  
18         final Constructor<?> cons = cl.getConstructor(constructorParams);  
19         final InvocationHandler ih = h;  
20         if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {  
21             return AccessController.doPrivileged(new PrivilegedAction<Object>() {  
22                 public Object run() {  
23                     return newInstance(cons, ih);  
24                 }  
25             });  
26         } else {  
27             return newInstance(cons, ih);  
28         }  
29     } catch (NoSuchMethodException e) {  
30         throw new InternalError(e.toString());  
31     }  
32 }  

 

其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,这里重点是看getProxyClass0这个方法的实现:

 

1 private static Class<?> getProxyClass0(ClassLoader loader,  
2                                        Class<?>... interfaces) {  
3     // 代理的接口数量不能超过65535(没有这种变态吧)  
4     if (interfaces.length > 65535) {  
5         throw new IllegalArgumentException("interface limit exceeded");  
6     }  
7     // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理  
8     return proxyClassCache.get(loader, interfaces);  
9 }  

 

其中代理缓存是使用WeakCache实现的,如下

 

1 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  
2     proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());  

具体的缓存逻辑这里暂不关心,只需要关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现了WeakCache的内部接口BiFunction的apply方法:

 

 1 private static final class ProxyClassFactory  
 2     implements BiFunction<ClassLoader, Class<?>[], Class<?>> {  
 3     // 所有代理类名字的前缀  
 4     private static final String proxyClassNamePrefix = "$Proxy";  
 5     // 用于生成代理类名字的计数器  
 6     private static final AtomicLong nextUniqueNumber = new AtomicLong();  
 7     @Override  
 8     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  
 9         // 省略验证代理接口的代码……  
10         String proxyPkg = null;     // 生成的代理类的包名  
11         // 对于非公共接口,代理类的包名与接口的相同  
12         for (Class<?> intf : interfaces) {  
13             int flags = intf.getModifiers();  
14             if (!Modifier.isPublic(flags)) {  
15                 String name = intf.getName();  
16                 int n = name.lastIndexOf('.');  
17                 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));  
18                 if (proxyPkg == null) {  
19                     proxyPkg = pkg;  
20                 } else if (!pkg.equals(proxyPkg)) {  
21                     throw new IllegalArgumentException(  
22                         "non-public interfaces from different packages");  
23                 }  
24             }  
25         }  
26   
27         // 对于公共接口的包名,默认为com.sun.proxy  
28         if (proxyPkg == null) {  
29             proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
30         }  
31   
32         // 获取计数  
33         long num = nextUniqueNumber.getAndIncrement();  
34         // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增  
35         String proxyName = proxyPkg + proxyClassNamePrefix + num;  
36   
37         // 这里才是真正的生成代理类的字节码的地方  
38         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
39             proxyName, interfaces);  
40         try {  
41             // 根据二进制字节码返回相应的Class实例  
42             return defineClass0(loader, proxyName,  
43                                 proxyClassFile, 0, proxyClassFile.length);  
44         } catch (ClassFormatError e) {  
45             throw new IllegalArgumentException(e.toString());  
46         }  
47     }  
48 }  

 

ProxyGenerator是sun.misc包中的类,它没有开源,但是可以反编译来一探究竟:

 

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;  
}  

saveGeneratedFiles这个属性的值从哪里来呢:

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

 

GetBooleanAction实际上是调用Boolean.getBoolean(propName)来获得的,而Boolean.getBoolean(propName)调用了System.getProperty(name),所以我们可以设置sun.misc.ProxyGenerator.saveGeneratedFiles这个系统属性为true来把生成的class保存到本地文件来查看。

 

这里要注意,当把这个属性设置为true时,生成的class文件及其所在的路径都需要提前创建,否则会抛出FileNotFoundException异常。如:

 

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:672)  
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:592)  
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:455)  
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:738)  
at com.mikan.proxy.ProxyTest.main(ProxyTest.java:15)  
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:140)  

 

即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存class文件内容。

 

反编译$Proxy0.class文件,如下所示:

 

 1 package com.sun.proxy;   
 2 import com.mikan.proxy.HelloWorld;  
 3 import java.lang.reflect.InvocationHandler;  
 4 import java.lang.reflect.Method;  
 5 import java.lang.reflect.Proxy;  
 6 import java.lang.reflect.UndeclaredThrowableException;  
 7   
 8 public final class $Proxy0 extends Proxy implements HelloWorld {  
 9   private static Method m1;  
10   private static Method m3;  
11   private static Method m0;  
12   private static Method m2;  
13   
14   public $Proxy0(InvocationHandler paramInvocationHandler) {  
15     super(paramInvocationHandler);  
16   }  
17   
18   public final boolean equals(Object paramObject) {  
19     try {  
20       return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
21     }  
22     catch (Error|RuntimeException localError) {  
23       throw localError;  
24     }  
25     catch (Throwable localThrowable) {  
26       throw new UndeclaredThrowableException(localThrowable);  
27     }  
28   }  
29   
30   public final void sayHello(String paramString) {  
31     try {  
32       this.h.invoke(this, m3, new Object[] { paramString });  
33       return;  
34     }  
35     catch (Error|RuntimeException localError) {  
36       throw localError;  
37     }  
38     catch (Throwable localThrowable) {  
39       throw new UndeclaredThrowableException(localThrowable);  
40     }  
41   }  
42   
43   public final int hashCode() {  
44     try {  
45       return ((Integer)this.h.invoke(this, m0, null)).intValue();  
46     }  
47     catch (Error|RuntimeException localError) {  
48       throw localError;  
49     }  
50     catch (Throwable localThrowable) {  
51       throw new UndeclaredThrowableException(localThrowable);  
52     }  
53   }  
54   
55   public final String toString() {  
56     try {  
57       return (String)this.h.invoke(this, m2, null);  
58     }  
59     catch (Error|RuntimeException localError) {  
60       throw localError;  
61     }  
62     catch (Throwable localThrowable) {  
63       throw new UndeclaredThrowableException(localThrowable);  
64     }  
65   }  
66   
67   static {  
68     try {  
69       m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });  
70       m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });  
71       m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
72       m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
73       return;  
74     }  
75     catch (NoSuchMethodException localNoSuchMethodException) {  
76       throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
77     }  
78     catch (ClassNotFoundException localClassNotFoundException) {  
79       throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
80     }  
81   }  
82 }  

 

可以看到,动态生成的代理类有如下特性:

 

  1. 继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

  2. 提供了一个使用InvocationHandler作为参数的构造方法。

  3. 生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。

  4. 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。

  5. 代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。

 

至此JDK动态代理的实现原理就分析的差不多了。同时我们可以想像一下Spring AOP提供的各种拦截该如何实现,就已经很明了了,如下所示:

 

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
 2     // BeforeAdvice  
 3     Object retVal = null;  
 4     try {  
 5         // AroundAdvice  
 6         retVal = method.invoke(target, args);  
 7         // AroundAdvice  
 8         // AfterReturningAdvice  
 9     }  
10     catch (Throwable e) {  
11         // AfterThrowingAdvice  
12     }  
13     finally {  
14         // AfterAdvice  
15     }  
16     return retVal;  
17 }  

 

上面是对于Spring AOP使用JDK动态代理实现的基本框架代码,当然具体的实现肯定比这个复杂得多,但是基本原理不外乎如是。所以理解基本原理对于理解其他的代码也是很有好处的。

 

 

 

 

转载:微信公众号:IT哈哈

posted @ 2018-08-02 16:12  一念永恒乐  阅读(197)  评论(0)    收藏  举报