Proxy.newProxyInstance(classloader,Class,invocationHandler) 调用getProxyClass0(loader, interfaces)生成代理类,拥有相同的接口,即拥有原来相同的方法,由相同的类加载器加载,然后拿到构造方法,cons.newInstance(new Object[]{h})传入invocationHandler,代理类的父类是Proxy,具体方法的实现都是h.invoke(),还把object的方法代理了,如tostring,hashcode,equals
 

动态代理,听上去很高大上的技术,在Java里应用广泛,尤其是在Hibernate和Spring这两种框架里,在AOP,权限控制,事务管理等方面都有动态代理的实现。JDK本身有实现动态代理技术,但是略有限制,即被代理的类必须实现某个接口,否则无法使用JDK自带的动态代理,因此,如果不满足条件,就只能使用另一种更加灵活,功能更加强大的动态代理技术—— CGLIB。Spring里会自动在JDK的代理和CGLIB之间切换,同时我们也可以强制Spring使用CGLIB。下面我们就动态代理方面的知识点从头至尾依次介绍一下。

我们先来看一个例子:

新建一个接口,UserService.java, 只有一个方法add()。

  1. package com.adam.java.basic;
  2. public interface UserService {
  3. public abstract void add();
  4. }

建一个该接口的实现类UserServiceImpl.java

  1. package com.adam.java.basic;
  2. public class UserServiceImpl implements UserService {
  3. @Override
  4. public void add() {
  5. System.out.println("----- add -----");
  6. }
  7. }

建一个代理处理类MyInvocationHandler.java

  1. package com.adam.java.basic;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. public class MyInvocationHandler implements InvocationHandler {
  6. private Object target;
  7. public MyInvocationHandler(Object target) {
  8. super();
  9. this.target = target;
  10. }
  11. public Object getProxy() {
  12. return Proxy.newProxyInstance(Thread.currentThread()
  13. .getContextClassLoader(), target.getClass().getInterfaces(),
  14. this);
  15. }
  16. @Override
  17. public Object invoke(Object proxy, Method method, Object[] args)
  18. throws Throwable {
  19. System.out.println("----- before -----");
  20. Object result = method.invoke(target, args);
  21. System.out.println("----- after -----");
  22. return result;
  23. }
  24. }

测试类

  1. package com.adam.java.basic;
  2. public class DynamicProxyTest {
  3. public static void main(String[] args) {
  4. UserService userService = new UserServiceImpl();
  5. MyInvocationHandler invocationHandler = new MyInvocationHandler(
  6. userService);
  7. UserService proxy = (UserService) invocationHandler.getProxy();
  8. proxy.add();
  9. }
  10. }

执行测试类,得到如下输出:
----- before -----
----- add -----
----- after -----
到这里,我们应该会想到点问题:
1. 这个代理对象是由谁且怎么生成的?
2. invoke方法是怎么调用的?
3. invoke和add方法有什么对应关系?
4. 生成的代理对象是什么样子的?
带着这些问题,我们看一下源码。首先,我们的入口便是上面测试类里的getProxy()方法,我们跟进去,看看这个方法:

  1. public Object getProxy() {
  2. return Proxy.newProxyInstance(Thread.currentThread()
  3. .getContextClassLoader(), target.getClass().getInterfaces(),this);
  4. }

也就是说,JDK的动态代理,是通过一个叫Proxy的类来实现的,我们继续跟进去,看看Proxy类的newProxyInstance()方法。先来看看JDK的注释:

  1. /**
  2. * Returns an instance of a proxy class for the specified interfaces
  3. * that dispatches method invocations to the specified invocation
  4. * handler.
  5. *
  6. * <p>{@code Proxy.newProxyInstance} throws
  7. * {@code IllegalArgumentException} for the same reasons that
  8. * {@code Proxy.getProxyClass} does.
  9. *
  10. * @param   loader the class loader to define the proxy class
  11. * @param   interfaces the list of interfaces for the proxy class
  12. *          to implement
  13. * @param   h the invocation handler to dispatch method invocations to
  14. * @return  a proxy instance with the specified invocation handler of a
  15. *          proxy class that is defined by the specified class loader
  16. *          and that implements the specified interfaces

根据JDK注释我们得知,newProxyInstance方法最终将返回一个实现了指定接口的类的实例,其三个参数分别是:ClassLoader,指定的接口及我们自己定义的InvocationHandler类。我摘几条关键的代码出来,看看这个代理类的实例对象到底是怎么生成的。

  1. Class<?> cl = getProxyClass0(loader, intfs);
  2. ...
  3. final Constructor<?> cons = cl.getConstructor(constructorParams);
  4. ...
  5. return cons.newInstance(new Object[]{h});

有兴趣的同学可以自己看看JDK的源码,当前我用的版本是JDK1.8.25,每个版本实现方式可能会不一样,但基本一致,请研究源码的同学注意这一点。上面的代码表明,首先通过getProxyClass获得这个代理类,然后通过c1.getConstructor()拿到构造函数,最后一步,通过cons.newInstance返回这个新的代理类的一个实例,注意:调用newInstance的时候,传入的参数为h,即我们自己定义好的InvocationHandler类,先记着这一步,后面我们就知道这里这样做的原因。

其实这三条代码,核心就是这个getProxyClass方法,另外两行代码是Java反射的应用,和我们当前的兴趣点没什么关系,所以我们继续研究这个getProxyClass方法。这个方法,注释很简单,如下:

  1. /*
  2. * Look up or generate the designated proxy class.
  3. */
  4. Class<?> cl = getProxyClass0(loader, intfs);

就是生成这个关键的代理类,我们跟进去看一下。

  1. private static Class<?> getProxyClass0(ClassLoader loader,
  2. Class<?>... interfaces) {
  3. if (interfaces.length > 65535) {
  4. throw new IllegalArgumentException("interface limit exceeded");
  5. }
  6. // If the proxy class defined by the given loader implementing
  7. // the given interfaces exists, this will simply return the cached copy;
  8. // otherwise, it will create the proxy class via the ProxyClassFactory
  9. return proxyClassCache.get(loader, interfaces);
  10. }

这里用到了缓存,先从缓存里查一下,如果存在,直接返回,不存在就新创建。在这个get方法里,我们看到了如下代码:
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
此处提到了apply(),是Proxy类的内部类ProxyClassFactory实现其接口的一个方法,具体实现如下:

  1. public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
  2. Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
  3. for (Class<?> intf : interfaces) {
  4. /*
  5. * Verify that the class loader resolves the name of this
  6. * interface to the same Class object.
  7. */
  8. Class<?> interfaceClass = null;
  9. try {
  10. interfaceClass = Class.forName(intf.getName(), false, loader);
  11. } catch (ClassNotFoundException e) {
  12. }
  13. if (interfaceClass != intf) {
  14. throw new IllegalArgumentException(
  15. intf + " is not visible from class loader");
  16. }...

看到Class.forName()的时候,我想大多数人会笑了,终于看到熟悉的方法了,没错!这个地方就是要加载指定的接口,既然是生成类,那就要有对应的class字节码,我们继续往下看:

  1. /*
  2. * Generate the specified proxy class.
  3. */
  4. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  5. proxyName, interfaces, accessFlags);
  6. try {
  7. return defineClass0(loader, proxyName,
  8. proxyClassFile, 0, proxyClassFile.length);

这段代码就是利用ProxyGenerator为我们生成了最终代理类的字节码文件,即getProxyClass0()方法的最终返回值。所以让我们回顾一下最初的四个问题:

1. 这个代理对象是由谁且怎么生成的?

2. invoke方法是怎么调用的?

3. invoke和add方法有什么对应关系?

4. 生成的代理对象是什么样子的?

对于第一个问题,我想答案已经清楚了,我再屡一下思路:由Proxy类的getProxyClass0()方法生成目标代理类,然后拿到该类的构造方法,最后通过反射的newInstance方法,产生代理类的实例对象。

接下来,我们看看其他的三个方法,我想先从第四个入手,因为有了上面的生成字节码的代码,那我们可以模仿这一步,自己生成字节码文件看看,所以,我用如下代码,生成了这个最终的代理类。

  1. package com.adam.java.basic;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import sun.misc.ProxyGenerator;
  5. public class DynamicProxyTest {
  6. public static void main(String[] args) {
  7. UserService userService = new UserServiceImpl();
  8. MyInvocationHandler invocationHandler = new MyInvocationHandler(
  9. userService);
  10. UserService proxy = (UserService) invocationHandler.getProxy();
  11. proxy.add();
  12. String path = "C:/$Proxy0.class";
  13. byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
  14. UserServiceImpl.class.getInterfaces());
  15. FileOutputStream out = null;
  16. try {
  17. out = new FileOutputStream(path);
  18. out.write(classFile);
  19. out.flush();
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. } finally {
  23. try {
  24. out.close();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }
  30. }

上面测试方法里的proxy.add(),此处的add()方法,就已经不是原始的UserService里的add()方法了,而是新生成的代理类的add()方法,我们将生成的$Proxy0.class文件用jd-gui打开,我去掉了一些代码,add()方法如下:

  1. public final void add()
  2. throws
  3. {
  4. try
  5. {
  6. this.h.invoke(this, m3, null);
  7. return;
  8. }
  9. catch (Error|RuntimeException localError)
  10. {
  11. throw localError;
  12. }
  13. catch (Throwable localThrowable)
  14. {
  15. throw new UndeclaredThrowableException(localThrowable);
  16. }
  17. }

核心就在于this.h.invoke(this. m3, null);此处的h是啥呢?我们看看这个类的类名:

public final class $Proxy0 extends Proxy implements UserService

不难发现,新生成的这个类,继承了Proxy类实现了UserService这个方法,而这个UserService就是我们指定的接口,所以,这里我们基本可以断定,JDK的动态代理,生成的新代理类就是继承了Proxy基类,实现了传入的接口的类。那这个h到底是啥呢?我们再看看这个新代理类,看看构造函数:

  1. public $Proxy0(InvocationHandler paramInvocationHandler)
  2. throws
  3. {
  4. super(paramInvocationHandler);
  5. }

构造函数里传入了一个InvocationHandler类型的参数,看到这里,我们就应该想到之前的一行代码:

return cons.newInstance(new Object[]{h});

这是newInstance方法的最后一句,传入的h,就是这里用到的h,也就是我们最初自己定义的MyInvocationHandler类的实例。所以,我们发现,其实最后调用的add()方法,其实调用的是MyInvocationHandler的invoke()方法。我们再来看一下这个方法,找一下m3的含义,继续看代理类的源码:

  1. static
  2. {
  3. try
  4. {
  5. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
  6. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  7. m3 = Class.forName("com.adam.java.basic.UserService").getMethod("add", new Class[0]);
  8. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  9. return;
  10. }

惊喜的发现,原来这个m3,就是原接口的add()方法,看到这里,还有什么不明白的呢?我想2,3,4问题都应该迎刃而解了吧?我们继续,看看原始MyInvocationHandler里的invoke()方法:

  1. <span style="white-space:pre">  </span>@Override
  2. public Object invoke(Object proxy, Method method, Object[] args)
  3. throws Throwable {
  4. System.out.println("----- before -----");
  5. Object result = method.invoke(target, args);
  6. System.out.println("----- after -----");
  7. return result;
  8. }

m3就是将要传入的method,所以,为什么先输出before,后输出after,到这里是不是全明白了呢?这,就是JDK的动态代理整个过程,不难吧?

最后,我稍微总结一下JDK动态代理的操作过程:

1. 定义一个接口,该接口里有需要实现的方法,并且编写实际的实现类。

2. 定义一个InvocationHandler类,实现InvocationHandler接口,重写invoke()方法,且添加getProxy()方法。

总结一下动态代理实现过程:

1. 通过getProxyClass0()生成代理类。

2. 通过Proxy.newProxyInstance()生成代理类的实例对象,创建对象时传入InvocationHandler类型的实例。

3. 调用新实例的方法,即此例中的add(),即原InvocationHandler类中的invoke()方法。

好了,写了这么多,也该结尾了,感谢博友Rejoy的一篇文章,让我有了参考。同时欢迎大家一起提问讨论,如有问题,请留言,我会抽空回复。相关代码已经上传至百度网盘,下载地址

JDK动态代理的实现及原理的更多相关文章

  1. Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理

    Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理 JDK动态代理的实现及原理 作者:二青 邮箱:xtfggef@gmail.com     微博:http://weibo.com/xtfg ...

  2. Java JDK 动态代理使用及实现原理分析

    转载:http://blog.csdn.net/jiankunking   一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...

  3. Java JDK 动态代理实现和代码分析

    JDK 动态代理 内容 一.动态代理解析 1. 代理模式 2. 为什么要使用动态代理 3. JDK 动态代理简单结构图 4. JDK 动态代理实现步骤 5. JDK 动态代理 API 5.1 java ...

  4. JDK动态代理的实现原理

    学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504

  5. JDK 动态代理实现原理

    一.引言 Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象便能动态生成代理类.代理类会负责将所有方法的调用分派到委托对象上反射执行,在分派执行的过 ...

  6. JDK动态代理实现原理--转载

    之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了.  ...

  7. jdk动态代理与cglib代理、spring aop代理实现原理

    原创声明:本博客来源与本人另一博客[http://blog.csdn.net/liaohaojian/article/details/63683317]原创作品,绝非他处摘取 代理(proxy)的定义 ...

  8. jdk动态代理与cglib代理、spring aop代理实现原理解析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  9. 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

随机推荐

  1. 阅读《Effective C++》系列

    <Effective C++>条款07:为多态基类声明virtual析构函数 这样做主要是为了防止内存泄漏,见我hexo博客. C++的虚析构函数 <Effective C++> ...

  2. [LeetCode] Range Sum Query 2D - Immutable

    Very similar to Range Sum Query - Immutable, but we now need to compute a 2d accunulated-sum. In fac ...

  3. How to Debug Enterprise Portal Code in Dynamics AX 2009

    转载 To set up debugging for pages1. Log into the server that is running the AOS.2. Open the Microsoft ...

  4. Backbone1.0.0数据验证的变化

    0.5.3版本对Model数据验证时,绑定Error就可以了: (function(){ var Model = Backbone.Model.extend({ initialize : functi ...

  5. Approvals for EBS 1.4 Now Available

    If you haven't been following the excellent Workflow blog, you might have missed the announcement ab ...

  6. DMSFrame 之简单用法(一)

    1.DMSFrame是一个完整的ORM框架,框架相对来说也比成熟了.使用上有些地方还是比较方便的.DLL文件大约300K左右,但却可以支持各种方式的查询,完全的LINQ化的方式书写代码,更有利于维护. ...

  7. java 时间转换

    public static int timestrtosec(String time) {        if (Strings.isNullOrEmpty(time)) {            r ...

  8. JS算法总结

    1.选择排序: var arr = [3,6,7,2,6,4,1,6,8,24,12,53]; function sort(arr){ // 当数组的长度小于1的时候结束递归 if(arr.lengt ...

  9. C中调用Lua函数

    我们先来看一个简单的例子: lua_State* L = NULL; // 内部调用lua函数 double f(double x, double y) { double z; lua_getglob ...

  10. 关于鼠标事件的screenY,pageY,clientY,layerY,offsetY属性 (详细图解)

    screenY 鼠标相对于显示器屏幕左上角的偏移 pageY 鼠标相对于页面左上角的偏移 (其值不会受滚动条的影响) IE9之下并不支持这个属性 但是可以写点代码计算出来. jQuery中的实现: / ...