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 .提示細節:

    • 代理對象的父類java.lang.reflect.Proxy類是實現了java.io.Serializable接口的,所以代理對象都是可序列化的;
    • 對於有參和無參方法,都是通過invoke方法調用,無參方法會直接傳入null,所以在invoke方法中使用args參數時一定要先進行null的判斷;
    • 對於原始數據類型(int,boolean等8種),代理對象的方法中參數和返回值都進行了包裝和解包裝。
    • 代理對象生成過程中用到了反射,生成字節碼時,反射Object對象方法和反射接口方法;生成實例時,反射獲取代理對象的構造器;代理對象方法調用過程中是沒有使用反射的。
    • 有沒有感覺跟 裝飾器模式 有一些 異曲同工 呢?

4. 以上就是個人總結分享的JDK動態代理的內容,原創內容,轉載請注明出處。

 

個人水平有限,有誤的地方歡迎評論中指正。

  

 

    

  

    

 

  

byte[]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM