1. 程式人生 > >JDK的動態代理深入解析(Proxy,InvocationHandler)(轉)

JDK的動態代理深入解析(Proxy,InvocationHandler)(轉)

http 輸出 pre 是否 介紹 copy 詳細 mar throwable

技術分享圖片

一、什麽是動態代理

  動態代理可以提供對另一個對象的訪問,同時隱藏實際對象的具體事實。代理一般會實現它所表示的實際對象的接口。代理可以訪問實際對象,但是延遲實現實際對象的部分功能,實際對象實現系統的實際功能,代理對象對客戶隱藏了實際對象。客戶不知道它是與代理打交道還是與實際對象打交道。

目的:主要用來做方法的增強,讓你可以在不修改源碼(不用改變這個方法的簽名,原來調用這個方法的類依然能正常工作)的情況下,增強一些方法。在方法執行前後做任何你想做的事情(甚至根本不去執行這個方法),因為在InvocationHandler的invoke方法中,你可以直接獲取正在調用方法對應的Method對象,具體應用的話,比如可以添加調用日誌,做事務控制等。

還有一個有趣的作用是可以用作遠程調用,比如現在有Java接口,這個接口的實現部署在其它服務器上,在編寫客戶端代碼的時候,沒辦法直接調用接口方法,因為接口是不能直接生成對象的,這個時候就可以考慮代理模式(動態代理)了,通過Proxy.newProxyInstance代理一個該接口對應的InvocationHandler對象,然後在InvocationHandler的invoke方法內封裝通訊細節就可以了。具體的應用,最經典的當然是Java標準庫的RMI,其它比如hessian,各種webservice框架中的遠程調用,大致都是這麽實現的。
而像 AspectJ 這種 AOP 剛不同,它直接把人家的 class 代碼修改了,它就不需要使用代理。
這些在新的 JDK 6 中都可以通過 Instrument 來做到,不過也是個通用的方法,還得通過規則來定制什麽情況下處理,什麽時候不處理。

二、jdk的動態代理

  目前Java開發包中包含了對動態代理的支持,但是其實現只支持對接口的的實現。 其實現主要通過java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。

  Proxy類主要用來獲取動態代理對象,InvocationHandler接口用來約束調用者實現。

  動態代理是很多框架和技術的基礎, spring 的AOP實現就是基於動態代理實現的。了解動態代理的機制對於理解AOP的底層實現是很有幫助的。

2.1、Proxy類

Porxy類也是在java.lang.reflect,Proxy 提供用於創建動態代理類和實例的靜態方法,它還是由這些方法創建的所有動態代理類的超類。

代理類具用以下屬性:

  • 代理類是公共的、最終的,而不是抽象的。
  • 未指定代理類的非限定名稱。但是,以字符串 "$Proxy" 開頭的類名空間應該為代理類保留。
  • 代理類擴展 java.lang.reflect.Proxy
  • 代理類會按同一順序準確地實現其創建時指定的接口。
  • 如果代理類實現了非公共接口,那麽它將在與該接口相同的包中定義。否則,代理類的包也是未指定的。註意,包密封將不阻止代理類在運行時在特定包中的成功定義,也不會阻止相同類加載器和帶有特定簽名的包所定義的類。
  • 由於代理類將實現所有在其創建時指定的接口,所以對其 Class 對象調用 getInterfaces 將返回一個包含相同接口列表的數組(按其創建時指定的順序),對其 Class 對象調用 getMethods 將返回一個包括這些接口中所有方法的 Method 對象的數組,並且調用 getMethod 將會在代理接口中找到期望的一些方法。
  • 如果 Proxy.isProxyClass 方法傳遞代理類(由 Proxy.getProxyClass 返回的類,或由 Proxy.newProxyInstance 返回的對象的類),則該方法返回 true,否則返回 false。
  • 代理類的 java.security.ProtectionDomain 與由引導類加載器(如 java.lang.Object)加載的系統類相同,原因是代理類的代碼由受信任的系統代碼生成。此保護域通常被授予 java.security.AllPermission
  • 每個代理類都有一個可以帶一個參數(接口 InvocationHandler 的實現)的公共構造方法,用於設置代理實例的調用處理程序。並非必須使用反射 API 才能訪問公共構造方法,通過調用 Proxy.newInstance 方法(將調用 Proxy.getProxyClass 的操作和調用帶有調用處理程序的構造方法結合在一起)也可以創建代理實例。

方法

技術分享圖片
protected  Proxy(InvocationHandler h)  //使用其調用處理程序的指定值從子類(通常為動態代理類)構建新的 Proxy 實例。 

static InvocationHandler getInvocationHandler(Object proxy) //返回指定代理實例的調用處理程序。

static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) //返回代理類的 java.lang.Class 對象,並向其提供類加載器和接口數組。

static boolean isProxyClass(Class<?> cl) //當且僅當指定的類通過 getProxyClass 方法或 newProxyInstance 方法動態生成為代理類時,返回 true。

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) //返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
技術分享圖片

2.1、InvocationHandler接口

InvocationHandler接口也是在java.lang.reflect,唯一的一個方法是invoke如下:

Object invoke(Object proxy, Method method, Object[] args)

這個方法有三個參數,其中第二和第三個參數都比較好理解,一個是被攔截的方法,一個是該方法的參數列表。關鍵是第一個參數。按照doc文檔的解析,proxy - the proxy instance that the method was invoked on也就是說,proxy應該是一個代理實例(動態代理類)。

三、示例

好了,在介紹完這兩個接口(類)以後,我們來通過一個實例來看看我們的動態代理模式是什麽樣的:

首先我們定義了一個Subject類型的接口,為其聲明了兩個方法:

public interface Subject
{
    public void rent();
    
    public void hello(String str);
}

接著,定義了一個類來實現這個接口,這個類就是我們的真實對象,RealSubject類:

技術分享圖片 技術分享圖片
public class RealSubject implements Subject
{
    @Override
    public void rent()
    {
        System.out.println("I want to rent my house");
    }
    
    @Override
    public void hello(String str)
    {
        System.out.println("hello: " + str);
    }
}
技術分享圖片 技術分享圖片

下一步,我們就要定義一個動態代理類了,前面說個,每一個動態代理類都必須要實現 InvocationHandler 這個接口,因此我們這個動態代理類也不例外:

技術分享圖片 技術分享圖片
public class DynamicProxy implements InvocationHandler
{
    // 這個就是我們要代理的真實對象
    private Object subject;
    
    //    構造方法,給我們要代理的真實對象賦初值
    public DynamicProxy(Object subject)
    {
        this.subject = subject;
    }
    
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真實對象前我們可以添加一些自己的操作
        System.out.println("before rent house");
        
        System.out.println("Method:" + method);
        
        //    當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
        method.invoke(subject, args);
        
        //  在代理真實對象後我們也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }

}
技術分享圖片 技術分享圖片

最後,來看看我們的Client類:

技術分享圖片 技術分享圖片
public class Client
{
    public static void main(String[] args)
    {
        //    我們要代理的真實對象
        Subject realSubject = new RealSubject();

        //    我們要代理哪個真實對象,就將該對象傳進去,最後是通過該真實對象來調用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通過Proxy的newProxyInstance方法來創建我們的代理對象,我們來看看其三個參數
         * 第一個參數 handler.getClass().getClassLoader() ,我們這裏使用handler這個類的ClassLoader對象來加載我們的代理對象
         * 第二個參數realSubject.getClass().getInterfaces(),我們這裏為代理對象提供的接口是真實對象所實行的接口,表示我要代理的是該真實對象,這樣我就能調用這組接口中的方法了
         * 第三個參數handler, 我們這裏將這個代理對象關聯到了上方的 InvocationHandler 這個對象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);
        
        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}
技術分享圖片 技術分享圖片

我們先來看看控制臺的輸出:

技術分享圖片 技術分享圖片
$Proxy0
before rent house Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent() I want to rent my house after rent house
before rent house Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String) hello: world after rent house
技術分享圖片 技術分享圖片

我們首先來看看 $Proxy0 這東西,我們看到,這個東西是由 System.out.println(subject.getClass().getName()); 這條語句打印出來的,那麽為什麽我們返回的這個代理對象的類名是這樣的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我以為返回的這個代理對象會是Subject類型的對象,或者是InvocationHandler的對象,結果卻不是,首先我們解釋一下為什麽我們這裏可以將其轉化為Subject類型的對象?原因就是在newProxyInstance這個方法的第二個參數上,我們給這個代理對象提供了一組什麽接口,那麽我這個代理對象就會實現了這組接口,這個時候我們當然可以將這個代理對象強制類型轉化為這組接口中的任意一個,因為這裏的接口是Subject類型,所以就可以將其轉化為Subject類型了。

同時我們一定要記住,通過 Proxy.newProxyInstance 創建的代理對象是在jvm運行時動態生成的一個對象,它並不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型,而是在運行是動態生成的一個對象,並且命名方式都是這樣的形式,以$開頭,proxy為中,最後一個數字表示對象的標號

接著我們來看看這兩句

subject.rent();
subject.hello("world");

這裏是通過代理對象來調用實現的那種接口中的方法,這個時候程序就會跳轉到由這個代理對象關聯到的 handler 中的invoke方法去執行,而我們的這個 handler 對象又接受了一個 RealSubject類型的參數,表示我要代理的就是這個真實對象,所以此時就會調用 handler 中的invoke方法去執行:

技術分享圖片 技術分享圖片
public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真實對象前我們可以添加一些自己的操作
        System.out.println("before rent house");
        
        System.out.println("Method:" + method);
        
        //    當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
        method.invoke(subject, args);
        
        //  在代理真實對象後我們也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }
技術分享圖片 技術分享圖片

我們看到,在真正通過代理對象來調用真實對象的方法的時候,我們可以在該方法前後添加自己的一些操作,同時我們看到我們的這個 method 對象是這樣的:

public abstract void com.xiaoluo.dynamicproxy.Subject.rent()

public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)

正好就是我們的Subject接口中的兩個方法,這也就證明了當我通過代理對象來調用方法的時候,起實際就是委托由其關聯到的 handler 對象的invoke方法中來調用,並不是自己來真實調用,而是通過代理的方式來調用的。

動態代理內部實現

首先來看看類Proxy的代碼實現 Proxy的主要靜態變量

技術分享圖片 技術分享圖片
// 映射表:用於維護類裝載器對象到其對應的代理類緩存
private static Map loaderToCache = new WeakHashMap(); 

// 標記:用於標記一個動態代理類正在被創建中
private static Object pendingGenerationMarker = new Object(); 

// 同步表:記錄已經被創建的動態代理類類型,主要被方法 isProxyClass 進行相關的判斷
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 

// 關聯的調用處理器引用
protected InvocationHandler h;
技術分享圖片 技術分享圖片

Proxy的構造方法

// 由於 Proxy 內部從不直接調用構造函數,所以 private 類型意味著禁止任何調用
private Proxy() {} 

// 由於 Proxy 內部從不直接調用構造函數,所以 protected 意味著只有子類可以調用
protected Proxy(InvocationHandler h) {this.h = h;} 

Proxy靜態方法newProxyInstance

技術分享圖片 技術分享圖片
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
    // 檢查 h 不為空,否則拋異常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 獲得與指定類裝載器和一組接口相關的代理類類型對象
    Class cl = getProxyClass(loader, interfaces); 

    // 通過反射獲取構造函數對象並生成代理類實例
    try { 
        Constructor cons = cl.getConstructor(constructorParams); 
        return (Object) cons.newInstance(new Object[] { h }); 
    } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
    } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
    } catch (InstantiationException e) { throw new InternalError(e.toString()); 
    } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
    } 
}
技術分享圖片 技術分享圖片

ProxygetProxyClass方法調用ProxyGeneratorgenerateProxyClass方法產生ProxySubject.class的二進制數據:

public static byte[] generateProxyClass(final String name, Class[] interfaces)

我們可以import sun.misc.ProxyGenerator,調用 generateProxyClass方法產生binary data,然後寫入文件,最後通過反編譯工具來查看內部實現原理。 反編譯後的ProxySubject.java Proxy靜態方法newProxyInstance

技術分享圖片 技術分享圖片
import java.lang.reflect.*;   
public final class ProxySubject extends Proxy   
    implements Subject   
{   
    private static Method m1;   
    private static Method m0;   
    private static Method m3;   
    private static Method m2;   
    public ProxySubject(InvocationHandler invocationhandler)   
    {   
        super(invocationhandler);   
    }   
    public final boolean equals(Object obj)   
    {   
        try  
        {   
            return ((Boolean)super.h.invoke(this, m1, new Object[] {   
                obj   
            })).booleanValue();   
        }   
        catch(Error _ex) { }   
        catch(Throwable throwable)   
        {   
            throw new UndeclaredThrowableException(throwable);   
        }   
    }   
    public final int hashCode()   
    {   
        try  
        {   
            return ((Integer)super.h.invoke(this, m0, null)).intValue();   
        }   
        catch(Error _ex) { }   
        catch(Throwable throwable)   
        {   
            throw new UndeclaredThrowableException(throwable);   
        }   
    }   
    public final void doSomething()   
    {   
        try  
        {   
            super.h.invoke(this, m3, null);   
            return;   
        }   
        catch(Error _ex) { }   
        catch(Throwable throwable)   
        {   
            throw new UndeclaredThrowableException(throwable);   
        }   
    }   
    public final String toString()   
    {   
        try  
        {   
            return (String)super.h.invoke(this, m2, null);   
        }   
        catch(Error _ex) { }   
        catch(Throwable throwable)   
        {   
            throw new UndeclaredThrowableException(throwable);   
        }   
    }   
    static    
    {   
        try  
        {   
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {   
                Class.forName("java.lang.Object")   
            });   
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);   
            m3 = Class.forName("Subject").getMethod("doSomething", new Class[0]);   
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);   
        }   
        catch(NoSuchMethodException nosuchmethodexception)   
        {   
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());   
        }   
        catch(ClassNotFoundException classnotfoundexception)   
        {   
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());   
        }   
    }   
}  
技術分享圖片 技術分享圖片

ProxyGenerator內部是如何生成class二進制數據,可以參考源代碼。

技術分享圖片 技術分享圖片
private byte[] generateClassFile() {   
  /*  
   * Record that proxy methods are needed for the hashCode, equals,  
   * and toString methods of java.lang.Object.  This is done before  
   * the methods from the proxy interfaces so that the methods from  
   * java.lang.Object take precedence over duplicate methods in the  
   * proxy interfaces.  
   */  
  addProxyMethod(hashCodeMethod, Object.class);   
  addProxyMethod(equalsMethod, Object.class);   
  addProxyMethod(toStringMethod, Object.class);   
  /*  
   * Now record all of the methods from the proxy interfaces, giving  
   * earlier interfaces precedence over later ones with duplicate  
   * methods.  
   */  
  for (int i = 0; i < interfaces.length; i++) {   
      Method[] methods = interfaces[i].getMethods();   
      for (int j = 0; j < methods.length; j++) {   
    addProxyMethod(methods[j], interfaces[i]);   
      }   
  }   
  /*  
   * For each set of proxy methods with the same signature,  
   * verify that the methods‘ return types are compatible.  
   */  
  for (List<ProxyMethod> sigmethods : proxyMethods.values()) {   
      checkReturnTypes(sigmethods);   
  }   
  /* ============================================================  
   * Step 2: Assemble FieldInfo and MethodInfo structs for all of  
   * fields and methods in the class we are generating.  
   */  
  try {   
      methods.add(generateConstructor());   
      for (List<ProxyMethod> sigmethods : proxyMethods.values()) {   
    for (ProxyMethod pm : sigmethods) {   
        // add static field for method‘s Method object   
        fields.add(new FieldInfo(pm.methodFieldName,   
      "Ljava/lang/reflect/Method;",   
       ACC_PRIVATE | ACC_STATIC));   
        // generate code for proxy method and add it   
        methods.add(pm.generateMethod());   
    }   
      }   
      methods.add(generateStaticInitializer());   
  } catch (IOException e) {   
      throw new InternalError("unexpected I/O Exception");   
  }   
  /* ============================================================  
   * Step 3: Write the final class file.  
   */  
  /*  
   * Make sure that constant pool indexes are reserved for the  
   * following items before starting to write the final class file.  
   */  
  cp.getClass(dotToSlash(className));   
  cp.getClass(superclassName);   
  for (int i = 0; i < interfaces.length; i++) {   
      cp.getClass(dotToSlash(interfaces[i].getName()));   
  }   
  /*  
   * Disallow new constant pool additions beyond this point, since  
   * we are about to write the final constant pool table.  
   */  
  cp.setReadOnly();   
  ByteArrayOutputStream bout = new ByteArrayOutputStream();   
  DataOutputStream dout = new DataOutputStream(bout);   
  try {   
      /*  
       * Write all the items of the "ClassFile" structure.  
       * See JVMS section 4.1.  
       */  
          // u4 magic;   
      dout.writeInt(0xCAFEBABE);   
          // u2 minor_version;   
      dout.writeShort(CLASSFILE_MINOR_VERSION);   
          // u2 major_version;   
      dout.writeShort(CLASSFILE_MAJOR_VERSION);   
      cp.write(dout);   // (write constant pool)   
          // u2 access_flags;   
      dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);   
          // u2 this_class;   
      dout.writeShort(cp.getClass(dotToSlash(className)));   
          // u2 super_class;   
      dout.writeShort(cp.getClass(superclassName));   
          // u2 interfaces_count;   
      dout.writeShort(interfaces.length);   
          // u2 interfaces[interfaces_count];   
      for (int i = 0; i < interfaces.length; i++) {   
    dout.writeShort(cp.getClass(   
        dotToSlash(interfaces[i].getName())));   
      }   
          // u2 fields_count;   
      dout.writeShort(fields.size());   
          // field_info fields[fields_count];   
      for (FieldInfo f : fields) {   
    f.write(dout);   
      }   
          // u2 methods_count;   
      dout.writeShort(methods.size());   
          // method_info methods[methods_count];   
      for (MethodInfo m : methods) {   
    m.write(dout);   
      }   
             // u2 attributes_count;   
      dout.writeShort(0); // (no ClassFile attributes for proxy classes)   
  } catch (IOException e) {   
      throw new InternalError("unexpected I/O Exception");   
  }   
  return bout.toByteArray(); 
技術分享圖片 技術分享圖片

總結

一個典型的動態代理創建對象過程可分為以下四個步驟:
1、通過實現InvocationHandler接口創建自己的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通過為Proxy類指定ClassLoader對象和一組interface創建動態代理類
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通過構造函數創建代理類實例,此時需將調用處理器對象作為參數被傳入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
為了簡化對象創建過程,Proxy類中的newInstance方法封裝了2~4,只需兩步即可完成代理對象的創建。
生成的ProxySubject繼承Proxy類實現Subject接口,實現的Subject的方法實際調用處理器的invoke方法,而invoke方法利用反射調用的是被代理對象的的方法(Object result=method.invoke(proxied,args))

美中不足

誠然,Proxy已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持interface代理的桎梏,因為它的設計註定了這個遺憾。回想一下那些動態生成的代理類的繼承關系圖,它們已經註定有一個共同的父類叫Proxy。Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理,原因是多繼承在Java中本質上就行不通。有很多條理由,人們可以否定對 class代理的必要性,但是同樣有一些理由,相信支持class動態代理會更美好。接口和類的劃分,本就不是很明顯,只是到了Java中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實現任何接口而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。但是,不完美並不等於不偉大,偉大是一種本質,Java動態代理就是佐例。

cglib

jdk給目標類提供動態要求目標類必須實現接口,當一個目標類不實現接口時,jdk是無法為其提供動態代理的。cglib 卻能給這樣的類提供動態代理。

詳細見:Java之代理(jdk靜態代理,jdk動態代理,cglib動態代理,aop,aspectj)

Spring AOP

詳細見:Spring AOP 實現原理

攔截器

struts2攔截器的實現原理及源碼剖析

JDK的動態代理深入解析(Proxy,InvocationHandler)(轉)