Java,JDK动态代理的原理分析

1. 代理基本概念:

  以下是代理概念的百度解释:代理(百度百科)

       总之一句话:三个元素,数据--->代理对象--->真实对象;复杂一点的可以理解为五个元素:输入数据--->代理对象--->真实对象--->代理对象--->输出数据。

2. JDK的动态代理概念:

  JDK的动态代理和正常的代理逻辑有些区别。

  首先先明确一下术语:类 class ,接口 interface。

  JDK动态代理是基于 interface 创建的,而不是真正的对象;也就是说,即使没有真正的对象,JDK依然可以创建代理对象。下面用代码来解释:

复制代码
public class JDKProxy implements InvocationHandler{
    public Object getObject(TestInterface ref){
        return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}
复制代码

  当然,实际中使用的情况是有真正的对象的,像下面这样: 

复制代码
public class JDKProxy implements InvocationHandler{
    TestInterface ref;
    public Object getObject(TestInterface ref){
        this.ref = ref;
        return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("doBefore");
        Object o = method.invoke(ref, args);
        System.out.println("doAfter");
        return o;
    }
}
复制代码

  那么,和正常的代理逻辑区别就在这里了,JDK的动态代理多依赖一个元素,就是被代理对象ref所实现的接口。如果ref对象没有实现任何接口,那么这个对象是无法被代理的。 

  那么问题来了: 为什么Java自带的动态代理 选择 要基于接口 ?基于什么考虑,或者说Java如果 选择 直接 代理真正的对象会有什么问题?

3. 进入正题:JDK动态代理是如何实现的?(基于JDK1.8)

  Java中涉及到的关键先生:InvocationHandler , Proxy

  3.1 使用方法及参数详细解释

    代码使用方法:

     

复制代码
//代理类,实现InvocationHandler
public class JDKProxy implements InvocationHandler{ private UserService userServiceRef;
   //获取代理对象 public UserService getProxy(UserService userServiceRef){ this.userServiceRef = userServiceRef;
     //Proxy生成代理对象 return (UserService) Proxy.newProxyInstance(getClass().getClassLoader(), userServiceRef.getClass().getInterfaces(), this); }
   //代理对象做的事 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("someone is logining"); Object returnObject = method.invoke(userServiceRef, args); System.out.println("someone login success"); return returnObject; } }  
复制代码

    下面是InvocationHandler的接口描述:

   package java.lang.reflect;
   public interface InvocationHandler {
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
   }

    其中的参数:

      proxy: 代理对象本身,也就是 getProxy(UserService userServiceRef) 获取到的对象。大家思考一下,为什么要把这个代理对象作为参数传进来?

              我个人觉得这是个完全没有必要的参数。

      method:userServiceRef中的方法。

      args: method方法的参数。

    再来看一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法参数:

      loader:类加载器,用来加载代理类的,即Proxy.newProxyInstance()的返回结果的类字节码。

      interfaces:代理对象所实现的接口。 这里接口是数组参数,通常被代理只实现一个接口。那实现多个接口时使用代理对象有什么问题?其实也没问题,就是调用不同接口的方法前需要先强转为对应的接口类,麻烦。

      h:实现InvocationHandler的类,也就是示例代码中的JDKProxy类 。

    测试代码:

    public static void main(String[] args) {
          UserService userService = new UserServiceImpl();
          JDKProxy proxy = new JDKProxy();
          UserService userServiceProxy = proxy.getProxy(userService);
          userServiceProxy.login();
      }

  3.2 实现的原理与细节:

      1.代理对象的创建过程:

    创建代理对象的方法:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

    通过这个方法的参数其实可以看到一些眉目,loader用来加载代理类字节码,interfaces作为代理类实现的接口,h为代理对象实际调用的方法(即invoke方法)。

    创建过程大致分为几步:

    • 从缓存中获取代理对象,获取到则直接返回;

      缓存由WeakCache中的ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map来存储;

      为什么是两级Map? 想一想,Java类的唯一性由ClassLoader+Class决定,所以Key Object是ClassLoader,Key Object是所有接口组成的对象().

       WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 

      其中KeyFactory的作用就是将interfaces转换为Key Object 。

    • 生成代理对象的类名proxyName;

      由 private static final class ProxyClassFactory 来完成.

      private static final String proxyClassNamePrefix = "$Proxy"; //ProxyClassFactory
      // 每次使用时 自增1
      private static final AtomicLong nextUniqueNumber = new AtomicLong();//ProxyClassFactory
      ReflectUtil.PROXY_PACKAGE = "com.sun.proxy";//RelectionUtil

      proxyName = PROXY_PACKAGE + proxyClassNamePrefix nextUniqueNumber

    • 生成proxyName类的字节码;

      由 sun.misc.ProxyGenerator.generateProxyClass(String proxyName, Class<?>[] interfaces, int accessFlags) ,方法返回二进制字节码。

      参数: 类名,需要实现的接口,访问标志。

    • 将字节码加载到虚拟机中,即方法区内存(jdk1.7之前是永久代,jdk8之后的元数据区);

      由 java.lang.reflect.Proxy.defineClass0(ClassLoader loader, String proxyName, byte[] proxyClass, int offset, int length) 完成。

       private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len); 

      这是一个本地方法,通过JNI调用,返回代理的Class对象。

    • 生成代理Class对象的实例;构造器实例化

      生成代理对象的三个参数中的 interfaces, classLoader都使用过了,还有一个InvocationHandler 没有使用。

      通过反射获取代理Class的参数为InvocationHandler的构造器,通过 Constructor.newInstance(new Object[]{h}); 返回最后的代理实例对象。

    创建代理的过程就完成了。

      2. 代理对象的字节码分析:

    还是以上面3.1的例子分析,测试代码稍作修改,如下:

   public static void main(String[] args) {
       System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //生成的代理对象字节码保存到.class文件中。
          JDKProxy proxy = new JDKProxy();
         proxy.getProxy(new UserServiceImpl()); //生成代理对象
     }

    运行测试代码之后,user.dir目录下会多出一个目录:com/sun/proxy,打开后可以看到$Proxy0.class文件。

    jd-gui反编译该class文件:为方便阅读,我把方法里的try catch全部移掉了。

复制代码
   package com.sun.proxy;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    import myproxy.UserService;
    public final class $Proxy0 extends Proxy implements UserService {
      private static Method m1;
      private static Method m2;
      private static Method m3;
      private static Method m0;
      
      public $Proxy0(InvocationHandler paramInvocationHandler){
        super(paramInvocationHandler); //h引用在父类Proxy中
      }
      public final boolean equals(Object paramObject){
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
      }
      public final String toString(){
          return (String)this.h.invoke(this, m2, null);
      }
      public final boolean login(){
          return ((Boolean)this.h.invoke(this, m3, null)).booleanValue(); //boolean存在包装和解包装
      }
      public final int hashCode(){
          return ((Integer)this.h.invoke(this, m0, null)).intValue();
      } 
      static{
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m3 = Class.forName("myproxy.UserService").getMethod("login", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
      }
    }
复制代码

    一目了然。主要五部分内容:

    代理对象继承了Proxy类,并实现了目标接口。

    生成了以InvocationHandler为参数的构造器,实例化时将我们的自定JDKProxy(实现了InvocationHandler,并持有被代理对象)传递进去;

    生成了Object中的三个方法:equals, hashCode, toString;

    生成了接口中的所有方法,全部调用InvocationHandler对象的invoke方法;

    生成了对应方法的Method对象属性,传递给invoke方法。

  3 .提示细节:

    • 对于有参和无参方法,都是通过invoke方法调用,无参方法会直接传入null,所以在invoke方法中使用args参数时一定要先进行null的判断;
    • 对于原始数据类型(int,boolean等8种),代理对象的方法中参数和返回值都进行了包装和解包装。
    • 代理对象生成过程中用到了反射,生成字节码时,反射Object对象方法和反射接口方法;生成实例时,反射获取代理对象的构造器;代理对象方法调用过程中是没有使用反射的。
    • 有没有感觉跟 装饰器模式 有一些 异曲同工 呢?

4. 以上就是个人总结分享的JDK动态代理的内容,原创内容,转载请注明出处。

 

转自https://www.cnblogs.com/selfchange/p/9814290.html

posted @ 2018-10-19 16:24  人情世故  阅读(273)  评论(0编辑  收藏  举报
"不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!"