深入理解系列之JAVA動態代理機制

代理的作用,就是生成代理對象使得真實對象的某些方法執行被代理對象攔截,從而在真實方法執行前、執行後添加額外的“動作”!動態代理則是指不需要修改原來的對象方法,在程序運行的過程中動態的生成代理對象,從而動態的生成這些“額外的”動作,主要從兩個方面來深入理解動態代理機制!

問題一、動態代理的基本實現是什麼?

動態代理本質上還是java中的“代理設計模式”,所以啓UML圖如下所示
這裏寫圖片描述
真正實現的時候分爲以下幾個步驟:

1、創建被代理對象的接口

interface Subject{
      public void run();
    }

2、實現被代理的真實對象

class RealSubject implements Subject{
      @Override
      public void run(){
        System.out.println("真實對象正在運行");
      }
    }

3、創建調用處理器:

class SubjectInvocation implements InvocationHandler{

      private Subject subject;

      public SubjectInvocation(Subject subject){
        this.subject = subject;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("將要代理");
        Object result = method.invoke(subject,args);
        System.out.println("代理結束");
        return null;
      }
    }

調用處理器對象很關鍵,我們可以看到調用處理器對象持有被代理對象的接口,當invoke()執行的時候,通過method.invoke()執行實際對象的具體方法,這樣就相當於攔截了真實對象的執行,從而在實際方法執行的時候添加額外的動作!

4、生成代理對象:

public class ProxyTest {

      public static void main(String[] args){

        SubjectInvocation subjectInvocation = new SubjectInvocation(new RealSubject());
        Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},subjectInvocation);
        proxySubject.run();
        }
    }

重點關注代碼:

Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},
                subjectInvocation);

我們發現,代理對象的類型是強制轉換爲的被代理的接口類型,其中代理對象的生成是調用Proxy類的靜態方法實現的,

public static Object newProxyInstance(
ClassLoader loader, 
Class<?>[] interfaces, 
InvocationHandler h) 
throws IllegalArgumentException{…}

參數的含義如下:
loader:定義了代理類的ClassLoder,通常是interface.class.getClassLoader獲得;
interfaces:代理類實現的接口列表
h:調用處理器,也就是我們上面定義的實現了InvocationHandler接口的類實例
最後當調用proxySubject.run()時,實際上被攔截進而執行invoke()函數。我們運行一下,看看我們的動態代理是否能正常工作。我這裏運行後的輸出爲:

將要代理
真實對象正在運行
代理結束

問題二、爲什麼執行proxySubject.run()之後,會被攔截從而執行invoke()函數呢?

我們看到,invoke()函數其實是調用處理器裏的函數,而ProxySubject是Subject類型的,他們之間有什麼關係,爲什麼執行run之後會跳到invoke函數呢?解開這個謎底的第一步是去看 Proxy.newProxyInstance()方法的源碼!
第一步:

public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);

            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }

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

            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }

                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }

我們把多餘的異常處理等代碼去除,只看核心代碼

public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {

            final Class<?>[] intfs = interfaces.clone();
            Class<?> cl = getProxyClass0(loader, intfs);
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            return cons.newInstance(new Object[]{h});
         }

我們可以看到,代理對象的生成實際上是利用反射獲取構造函數,通過加載構造函數生成的,其中生成的對象持有調用處理器h。這裏有個很隱藏的問題:return cons.newInstance(new Object[]{h});到底返回的是什麼類型的對象?Subject?Proxy?其實都不是,我們可以打印出來該類的名稱來驗證一下,所以代碼修改成這樣:

public class ProxyTest {

      public static void main(String[] args){
        SubjectInvocation subjectInvocation = new SubjectInvocation(new RealSubject());
        Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},subjectInvocation);
        System.out.println(proxySubject.getClass().getName());
        proxySubject.run();
        }
    }

運行輸出爲:

proxySubject的類型是:$Proxy0
正在代理
真實對象正在運行
代理結束

OK,我們看到了一個意想不到的結果:Proxy0這個是什麼類型?還記得文章開始所說的代理類是動態生成的嗎?其中proxySubject就是代理類的實例對象,所以這個Proxy0就是動態生成的代理類,proxySubject就是根據這個類創建的動態代理對象!那麼這個類和Proxy、Subject又有什麼關係呢?爲什麼它持有run方法而且能調用invoke方法呢?解開謎底的第二步就是研究Proxy0!
第二步:

按理說,生成的動態代理類是直接加載到內存中我們無法獲取的,直觀的方法可以通過反射還原這個類的內容,但實在複雜!但是好在JDK提供了代理類的生成函數,所以代碼改成這樣:

public class ProxyTest {
    public static void main(String[] args){
//添加這一句
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    SubjectInvocation subjectInvocation = new SubjectInvocation(new RealSubject());
    Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},subjectInvocation);
    System.out.println(proxySubject.getClass().getName());
    proxySubject.run();
} 

System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”) 這句話執行後再進行動態代理生成就會在項目的根目錄下生成代理類的字節碼文件(實際路徑是包名+類名)然後再借用IDE的反編譯功能就能看到這個動態生成類的廬山真面目:

import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;

    final class $Proxy0 extends Proxy implements Subject {
      private static Method m1;
      private static Method m3;
      private static Method m2;
      private static Method m0;

      public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
      }

      public final boolean equals(Object var1) throws  {
        try {
          return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
          throw var3;
        } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
        }
      }

      public final void run() throws  {
        try {
          super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

      public final String toString() throws  {
        try {
          return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

      public final int hashCode() throws  {
        try {
          return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

      static {
        try {
          m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          m3 = Class.forName("proxy.Subject").getMethod("run");
          m2 = Class.forName("java.lang.Object").getMethod("toString");
          m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
        }
      }
    }

我們很明顯的看到, Proxy0繼承了Proxy實現了Subject!這樣,因爲繼承Proxy,所以Proxy0就持有了調用處理器對象,因爲實現Subject,所以Proxy0就可以擁有run()方法。這樣我們繼續看代碼就可以輕而易舉的找出run()方法時如何聯繫上調用處理器的invoke()方法的了!

首先看到,$Proxy0類聲明瞭m0~m3幾個Method類型的變量:

      private static Method m1;
      private static Method m3;
      private static Method m2;
      private static Method m0;

然後看最後的靜態代碼塊(類加載的時候,靜態代碼塊首先執行):

static {
        try {
          m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          m3 = Class.forName("Subject").getMethod("run");
          m2 = Class.forName("java.lang.Object").getMethod("toString");
          m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
        }
      }

可以看到,這些變量實際上是通過反射機制利用Class.forName().getMethod()動態加載的指向特定類型下的方法引用變量,這裏我們只看run()方法:

public final void run() throws  {
        try {
          super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

“很巧合”的是,$Proxy0中“竟然”也有一個run()方法(實際上是因爲繼承了Subject罷了),而且裏面傳入的方法引用便是m3,而執行run方法後,竟然調用的是h.invoke()!

到這裏基本可以說是真相大白了: ProxySubject是$Proxy0類型對象,而$Proxy0類因爲繼承Proxy實現了Subject,所以持有了調用處理器h和run()方法,而且重寫了run方法:執行run()方法後實際執行的是invoke方法,而invoke方法因爲傳入了由反射機制生成的方法引用,所以執行invoke方法後再執行method.invoke(),自然而然的執行了method也就是run方法!

讀者應該也注意到,$Proxy0除了包含run方法外還有hashcode、equals、toString方法,這是因爲所有的類包括 Subject類都是繼承的Object類,$Proxy0自然也包含這些方法! 這麼說來,JDK的動態代理機制的UML圖就變成了這樣:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章