JAVA基础-----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方法供实现者提供相应的代理逻辑的实现。可以对实际的实现进行一些特殊的处理,像SpringAOP中的各种advice。下面来看看如何使用。

被代理的接口

[java] view plain copy

1.  package com.mikan.proxy;  

2.    

3.  /** 

4.   * @author Mikan 

5.   * @date 2015-09-15 18:00 

6.   */  

7.  public interface HelloWorld {  

8.    

9.      void sayHello(String name);  

10.   

11. }  

接口的实现类:

[java] view plain copy

1.  package com.mikan.proxy;  

2.    

3.  /** 

4.   * @author Mikan 

5.   * @date 2015-09-15 18:01 

6.   */  

7.  public class HelloWorldImpl implements HelloWorld {  

8.      @Override  

9.      public void sayHello(String name) {  

10.         System.out.println("Hello " + name);  

11.     }  

12. }  

实现一个java.lang.reflect.InvocationHandler

[java] view plain copy

1.  package com.mikan.proxy;  

2.    

3.  import java.lang.reflect.InvocationHandler;  

4.  import java.lang.reflect.Method;  

5.    

6.  /** 

7.   * @author Mikan 

8.   * @date 2015-09-15 19:53 

9.   */  

10. public class CustomInvocationHandler implements InvocationHandler {  

11.     private Object target;  

12.   

13.     public CustomInvocationHandler(Object target) {  

14.         this.target = target;  

15.     }  

16.   

17.     @Override  

18.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  

19.         System.out.println("Before invocation");  

20.         Object retVal = method.invoke(target, args);  

21.         System.out.println("After invocation");  

22.         return retVal;  

23.     }  

24. }  

使用代理:

[java] view plain copy

1.  package com.mikan.proxy;  

2.    

3.  import java.lang.reflect.Proxy;  

4.    

5.  /** 

6.   * @author Mikan 

7.   * @date 2015-09-15 18:01 

8.   */  

9.  public class ProxyTest {  

10.   

11.     public static void main(String[] args) throws Exception {  

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

13.   

14.         CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());  

15.         HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(  

16.                 ProxyTest.class.getClassLoader(),  

17.                 new Class[]{HelloWorld.class},  

18.                 handler);  

19.         proxy.sayHello("Mikan");  

20.     }  

21.   

22. }  

运行的输出结果:

[plain] view plain copy

1.  localhost:classes mikan$ java com/mikan/proxy/ProxyTest  

2.  Before invocation  

3.  Hello Mikan  

4.  After invocation  

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

[java] view plain copy

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这个方法的实现:

[java] view plain copy

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实现的,如下

[java] view plain copy

1.  private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  

2.      proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());  

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

[java] view plain copy

1.  private static final class ProxyClassFactory  

2.      implements BiFunction<ClassLoader, Class<?>[], Class<?>> {  

3.      // 所有代理类名字的前缀  

4.      private static final String proxyClassNamePrefix = "$Proxy";  

5.    

6.      // 用于生成代理类名字的计数器  

7.      private static final AtomicLong nextUniqueNumber = new AtomicLong();  

8.    

9.      @Override  

10.     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  

11.         // 省略验证代理接口的代码……  

12.   

13.         String proxyPkg = null;     // 生成的代理类的包名  

14.         // 对于非公共接口,代理类的包名与接口的相同  

15.         for (Class<?> intf : interfaces) {  

16.             int flags = intf.getModifiers();  

17.             if (!Modifier.isPublic(flags)) {  

18.                 String name = intf.getName();  

19.                 int n = name.lastIndexOf('.');  

20.                 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));  

21.                 if (proxyPkg == null) {  

22.                     proxyPkg = pkg;  

23.                 } else if (!pkg.equals(proxyPkg)) {  

24.                     throw new IllegalArgumentException(  

25.                         "non-public interfaces from different packages");  

26.                 }  

27.             }  

28.         }  

29.   

30.         // 对于公共接口的包名,默认为com.sun.proxy  

31.         if (proxyPkg == null) {  

32.             proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  

33.         }  

34.   

35.         // 获取计数  

36.         long num = nextUniqueNumber.getAndIncrement();  

37.         // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0com.sun.proxy.$Proxy1……依次递增  

38.         String proxyName = proxyPkg + proxyClassNamePrefix + num;  

39.   

40.         // 这里才是真正的生成代理类的字节码的地方  

41.         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  

42.             proxyName, interfaces);  

43.         try {  

44.             // 根据二进制字节码返回相应的Class实例  

45.             return defineClass0(loader, proxyName,  

46.                                 proxyClassFile, 0, proxyClassFile.length);  

47.         } catch (ClassFormatError e) {  

48.             throw new IllegalArgumentException(e.toString());  

49.         }  

50.     }  

51. }  

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

[java] view plain copy

1.  public static byte[] generateProxyClass(final String var0, Class[] var1) {  

2.      ProxyGenerator var2 = new ProxyGenerator(var0, var1);  

3.      final byte[] var3 = var2.generateClassFile();  

4.      // 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观  

5.      if(saveGeneratedFiles) {  

6.          AccessController.doPrivileged(new PrivilegedAction() {  

7.              public Void run() {  

8.                  try {  

9.                      FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");  

10.                     var1.write(var3);  

11.                     var1.close();  

12.                     return null;  

13.                 } catch (IOException var2) {  

14.                     throw new InternalError("I/O exception saving generated file: " + var2);  

15.                 }  

16.             }  

17.         });  

18.     }  

19.     return var3;  

20. }  

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

[java] view plain copy

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异常。如:

[java] view plain copy

1.  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)  

2.  at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:336)  

3.  at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:327)  

4.  at java.security.AccessController.doPrivileged(Native Method)  

5.  at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:326)  

6.  at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:672)  

7.  at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:592)  

8.  at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:244)  

9.  at java.lang.reflect.WeakCache.get(WeakCache.java:141)  

10. at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:455)  

11. at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:738)  

12. at com.mikan.proxy.ProxyTest.main(ProxyTest.java:15)  

13. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  

14. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)  

15. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  

16. at java.lang.reflect.Method.invoke(Method.java:606)  

17. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)  

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

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

[java] view plain copy

1.  package com.sun.proxy;  

2.    

3.  import com.mikan.proxy.HelloWorld;  

4.  import java.lang.reflect.InvocationHandler;  

5.  import java.lang.reflect.Method;  

6.  import java.lang.reflect.Proxy;  

7.  import java.lang.reflect.UndeclaredThrowableException;  

8.    

9.  public final class $Proxy0 extends Proxy implements HelloWorld {  

10.   private static Method m1;  

11.   private static Method m3;  

12.   private static Method m0;  

13.   private static Method m2;  

14.   

15.   public $Proxy0(InvocationHandler paramInvocationHandler) {  

16.     super(paramInvocationHandler);  

17.   }  

18.   

19.   public final boolean equals(Object paramObject) {  

20.     try {  

21.       return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  

22.     }  

23.     catch (Error|RuntimeException localError) {  

24.       throw localError;  

25.     }  

26.     catch (Throwable localThrowable) {  

27.       throw new UndeclaredThrowableException(localThrowable);  

28.     }  

29.   }  

30.   

31.   public final void sayHello(String paramString) {  

32.     try {  

33.       this.h.invoke(this, m3, new Object[] { paramString });  

34.       return;  

35.     }  

36.     catch (Error|RuntimeException localError) {  

37.       throw localError;  

38.     }  

39.     catch (Throwable localThrowable) {  

40.       throw new UndeclaredThrowableException(localThrowable);  

41.     }  

42.   }  

43.   

44.   public final int hashCode() {  

45.     try {  

46.       return ((Integer)this.h.invoke(this, m0, null)).intValue();  

47.     }  

48.     catch (Error|RuntimeException localError) {  

49.       throw localError;  

50.     }  

51.     catch (Throwable localThrowable) {  

52.       throw new UndeclaredThrowableException(localThrowable);  

53.     }  

54.   }  

55.   

56.   public final String toString() {  

57.     try {  

58.       return (String)this.h.invoke(this, m2, null);  

59.     }  

60.     catch (Error|RuntimeException localError) {  

61.       throw localError;  

62.     }  

63.     catch (Throwable localThrowable) {  

64.       throw new UndeclaredThrowableException(localThrowable);  

65.     }  

66.   }  

67.   

68.   static {  

69.     try {  

70.       m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] { Class.forName("java.lang.Object") });  

71.       m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello"new Class[] { Class.forName("java.lang.String") });  

72.       m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);  

73.       m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);  

74.       return;  

75.     }  

76.     catch (NoSuchMethodException localNoSuchMethodException) {  

77.       throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  

78.     }  

79.     catch (ClassNotFoundException localClassNotFoundException) {  

80.       throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  

81.     }  

82.   }  

83. }  

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

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提供的各种拦截该如何实现,就已经很明了了,如下所示:

[java] view plain copy

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

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


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值