zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    【java规则引擎】drools6.5.0版本中kmodule.xml解析
    【java规则引擎】drools6.5.0版本api简介
    git撤销各种状态下的操作
    【策略】一致性Hash算法
    【策略】一致性Hash算法(Hash环)的java代码实现
    【转载】对一致性Hash算法,Java代码实现的深入研究
    Zookeeper之Curator(1)客户端对节点的一些监控事件的api使用
    【2】基于zookeeper,quartz,rocketMQ实现集群化定时系统
    NIO之直接缓冲区与非直接缓冲区
    NIO之缓冲区(Buffer)的数据存取
  • 原文地址:https://www.cnblogs.com/shizhijie/p/9817169.html
Copyright © 2011-2022 走看看