1. 程式人生 > >Java動態代理與在Android的應用

Java動態代理與在Android的應用

一、前言

1.1、什麼是代理?

大道理上講代理是一種軟體設計模式,目的地希望能做到程式碼重用。具體上講,代理這種設計模式是通過不直接訪問被代理物件的方式,而訪問被代理物件的方法。這個就好比 A---->B—>C 這種模式。A可以不通過直接與C對話的情況下,而通過B與其產生間接對話。

Java動態代理之所以叫做動態,因為它能避免傳統代理模式實現中人工一個一個的將java函式轉發過去,而是能夠讓程式碼自動做到這一點,這樣代理類的程式碼是和業務無關的,不會因為業務類的方法增多而逐漸龐大。使程式碼更易維護更易修改,實現自動化搬磚。

1.2、什麼情況下使用動態代理?

1、需要對較難修改的類方法進行功能增加。
2、RPC即遠端過程呼叫,通過動態代理的建立一箇中間人進行通訊。
3、實現切面程式設計(AOP)可以採用動態代理的機制來實現。

二、靜態代理和動態代理

根據載入被代理類的時機不同,將代理分為靜態代理動態代理編譯時就確定了被代理的類是哪一個,那麼就可以直接使用靜態代理;執行時才確定被代理的類是哪個,那麼可以使用類動態代理。

2.1、靜態代理

我們先建立一個介面,Java API代理機制求被代理類必須要實現某個介面,對於靜態代理方式 代理類 也要實現和 被代理類 相同的介面;

定義一個介面,這個介面定義了被代理類需要實現的功能:

public interface Subject {
    public void sayGoodBye();
    public void sayHello(String str);
}

定義被代理類(原來功能類)並實現被代理類的功能邏輯:

public class RealSubject implements Subject {
    @Override
    public void sayGoodBye() {
        System.out.println("RealSubject sayGoodBye");
    }

    @Override
    public void sayHello(String str) {
        System.out.println("RealSubject sayHello  " + str);
    }
}

定義靜態代理類(功能增加類),這個代理類也必須要實現和被代理類相同的Subject介面,便於對原有功能的增強:

public class ProxySubject implements Subject {    
    private Subject subject;

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

    @Override
    public void sayGoodBye() {
        //代理類,功能的增強
        System.out.println("ProxySubject sayGoodBye begin");       
        //在代理類的方法中 間接訪問被代理物件的方法
        subject.sayGoodBye();      
        System.out.println("ProxySubject sayGoodBye end");    
    }

    @Override
    public void sayHello(String str) {
        //代理類,功能的增強
        System.out.println("ProxySubject sayHello begin");
        //在代理類的方法中 間接訪問被代理物件的方法
        subject.sayHello(str);
        System.out.println("ProxySubject sayHello end");
    }
}

客戶端程式碼:

public static void main(String[] args) {
    //被代理的物件,某些情況下 我們不希望修改已有的程式碼,我們採用代理來間接訪問
    RealSubject realSubject = new RealSubject();
    //代理類物件
    ProxySubject proxySubject = new ProxySubject(realSubject);
    //呼叫代理類物件的方法
    proxySubject.sayGoodBye();
    System.out.println("******");
    proxySubject.sayHello("Test");
}

測試程式碼輸出:

ProxySubject sayGoodBye begin
RealSubject sayGoodBye
ProxySubject sayGoodBye end
******
ProxySubject sayHello begin
RealSubject sayHello  Test
ProxySubject sayHello end

靜態代理看起來是比較簡單的,它是運用原理跟裝飾設計模式類似,Subject介面相當於一個抽象構建Component ,被代理類RealSubject相當於一個具體構建ConcreteComponent,而代理類ProxySubject則相當於裝飾角色(Decorator)和具體裝飾角色(ConcreteDecorator)的結合。不管是靜態代理和裝飾設計模式其實都是為了對原有功能的增強 ,遮蔽或改變。

靜態代理(傳統代理模)的實現方式比較暴力直接,需要將所有被代理類的所有方法都寫一遍,並且一個個的手動轉發過去。在維護被代理類的同時,作為java碼工還需要同時維護代理類的相關程式碼,實在是累心。因此就需要我們的動態代理登場了,通過使用動態代理,動態代理能夠自動將代理類的相關方法轉發到被代理類。

2.2、動態代理

在java的動態代理機制中,有兩個重要的類或介面,一個是 InvocationHandler(Interface)、另一個則是Proxy(Class),這一個類和介面是實現我們動態代理所必須用到的。

InvocationHandler:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

每一個代理實類例的invocation handler都要實現InvocationHandler這個介面。並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。

  • proxy:指代生成的代理物件;
  • method:指代的是我們所要呼叫真實物件的某個方法的Method物件;
  • args:指代的是呼叫真實物件某個方法時接受的引數;

我們來看看Proxy這個類,這個類的作用就是用來動態建立一個代理物件的類,它提供了許多的方法,但用的最多的就是 newProxyInstance 這個方法:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  • loader:一個ClassLoader物件,定義了由哪個ClassLoader物件來對生成的代理物件進行載入
  • interfaces:一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了
  • h:一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上。

通過上面的需要傳入介面的引數可以看出,JDK動態代理需要藉助介面來實現,如果我們要代理的物件功能沒有抽成任何介面,那麼我們就無法通過JDK動態代理的方式來實現

好了,在介紹完這兩個介面(類)以後,我們來通過一個例項來看看我們的動態代理模式是什麼樣的。首先我們定義了一個Subject型別的介面,為其聲明瞭兩個方法,這兩個方法表示被代理類需要實現的功能:

public interface Subject {
    public void sayGoodBye();
    public void sayHello(String str);
}

接著,定義了一個類來實現這個介面,這個類就是我們的真實物件(被代理類),RealSubject類:

public class RealSubject implements Subject {
    @Override
    public void sayGoodBye() {
        System.out.println("RealSubject sayGoodBye");
    }

    @Override
    public void sayHello(String str) {
        System.out.println("RealSubject sayHello  " + str);
    }
}

下一步,我們就要定義一個InvocationHandler了,相當於一個代理處理器。前面說個,每一個動態代理類例項的invocation handler 都必須要實現 InvocationHandler 這個介面:

public class SubjectInvocationHandler implements InvocationHandler {
    //這個就是我們要代理的真實物件
    private Object subject;

    //構造方法,給我們要代理的真實物件賦初值
    public SubjectInvocationHandler(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        //在代理真實物件前我們可以新增一些自己的操作
        System.out.println("before Method invoke");
        System.out.println("Method:" + method);
        //當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫
        method.invoke(subject, args);
        //在代理真實物件後我們也可以新增一些自己的操作
        System.out.println("after Method invoke");
        return null;
    }
}

SubjectInvocationHandler並不是真正的代理類,而是用於定義代理類需要擴充套件、增強那些方法功能的類。在invoke函式中,對代理物件的所有方法的呼叫都被轉發至該函式處理。在這裡可以靈活的自定義各種你能想到的邏輯。

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

 public static void main(String[] args) {
       //被代理類
       Subject realSubject = new RealSubject();
       //我們要代理哪個類,就將該物件傳進去,最後是通過該被代理物件來呼叫其方法的
       SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);
       //生成代理類
       Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), 
                                                          realSubject.getClass().getInterfaces(), handler);
       //輸出代理類物件
       System.out.println("Proxy : "+ subject.getClass().getName());
       System.out.println("Proxy super : "+ subject.getClass().getSuperclass().getName());
       System.out.println("Proxy interfaces : "+ subject.getClass().getInterfaces()[0].getName());
       //呼叫代理類sayGoodBye方法
       subject.sayGoodBye();
       System.out.println("--------");
       //呼叫代理類sayHello方法
       subject.sayHello("Test");
   }

輸出結果:

Proxy : com.sun.proxy.$Proxy0
Proxy super : java.lang.reflect.Proxy
Proxy interfaces : com.company.ha.Subject
before Method invoke
Method:public abstract void com.company.ha.Subject.sayGoodBye()
RealSubject sayGoodBye
after Method invoke
--------
before Method invoke
Method:public abstract void com.company.ha.Subject.sayHello(java.lang.String)
RealSubject sayHello  Test
after Method invoke

與靜態代理相比,動態代理具有如下的優點:
1、代理轉發的過程自動化了,實現自動化搬磚;
2、代理類的程式碼邏輯和具體業務邏輯解耦,與業務無關;

我們首先來看看 $Proxy0 這東西,這個東西就是真正的代理類物件,我們定義SubjectInvocationHandler類則是用於新增對代理類的功能擴充套件!而 $Proxy0類繼承java.lang.reflect.Proxy類 並實現Subject介面 ,因此它的類宣告方式如下:

public class  $Proxy0 extends Proxy  implements Subject 

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

如果我們定義的方法有返回值,那麼可以通過invoke中把該方法的返回值進行返回,因為返回值的物件是Object,所以支援返回值為空(void)的返回。

 @Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
    //直接返回呼叫物件的返回值
    return method.invoke(subject,args);
}

那麼在呼叫代理類該方法的時候就能獲取其返回值了。

當然我們可以針對某些方法的返回值進行加工再進行返回,比如我們假設public int sayGoodBye();方法是具有返回值的,那麼我們希望在呼叫代理類的 sayGoodBye()方法的時候,返回值總能在原有的基礎上+10,那麼invoke中可以這麼寫:

 @Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
    if (method.getName().equals("sayGoodBye")) {//在呼叫sayGoodBye方法的時候 對返回值進行處理
        int result = (int) method.invoke(subject, args);
        return result + 10;
    } else {//其他方法一律不處理
        return method.invoke(subject, args);
    }
}

客戶端呼叫:

Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
int result = subject.sayGoodBye();
System.out.println("reslut "+result);

客戶端獲得result的值就總是對原有返回值基礎上+10的返回值了。

三、動態代理原始碼分析

動態類Proxy的是通過newProxyInstance方法來生成的:

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

因此我們從newProxyInstance來入手,為了更好的理解其原理,以下是精簡後的程式碼:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
     //所有被實現的業務介面
      final Class<?>[] intfs = interfaces.clone();
      //1、尋找或生成指定的代理類:com.sun.proxy.$ProxyX
      Class<?> cl = getProxyClass0(loader, intfs);
      //通過反射類中的Constructor獲取其所有構造方法
      final Constructor<?> cons = cl.getConstructor(constructorParams);
      //2、用構造方法建立代理類com.sun.proxy.$ProxyX的例項,並傳入InvocationHandler引數
      return cons.newInstance(new Object[]{h});
}

上面的程式碼主要實現兩個操作:1、生成ProxyX 的Class類物件 ,2、通過該Class物件建立對應的例項,並傳入InvocationHandler參與構造。

生成ProxyX的Class類物件的操作在getProxyClass0方法中:

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    //...
    return proxyClassCache.get(loader, interfaces);
}

proxyClassCache會快取所有的代理類,如果快取中有這個業務代理類,則會從快取中取出,否則從ProxyClassFactory中生成。ProxyClassFactorys是Proxy的靜態內部類,而ProxyClassFactory中的apply方法就是主要生成Class物件的地方:

Proxy$ProxyClassFactory:

 /**
   * A factory function that generates, defines and returns the proxy class given
   * the ClassLoader and array of interfaces.
   */
  private static final class ProxyClassFactory
      implements BiFunction<ClassLoader, Class<?>[], Class<?>>
  {

      // next number to use for generation of unique proxy class names
      private static final AtomicLong nextUniqueNumber = new AtomicLong();

      @Override
      public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
         ...
          if (proxyPkg == null) {
              // if no non-public proxy interfaces, use com.sun.proxy package
              proxyPkg = "$Proxy" + ".";
          }

           // nextUniqueNumber是一個靜態類每次生成一個代理類都會自增
          long num = nextUniqueNumber.getAndIncrement();
          //生成後的類名為com.sun.proxy.$ProxyX
          String proxyName = proxyPkg + proxyClassNamePrefix + num;

          //傳入介面、類名,生成了我們要的位元組碼形式的代理類
          //Generate the specified proxy class. 
          byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
              proxyName, interfaces, accessFlags);
         //defineClass0是個native方法,生成二進位制資料對應的Class類物件       
          return defineClass0(loader, proxyName,
                                  proxyClassFile, 0, proxyClassFile.length);
         
      }
  }

真正核心生成位元組碼的方法是ProxyGenerator.generateProxyClass方法,這裡就不深究內部是如何生成的,由於ProxyX是以位元組碼形式存在於記憶體中,我們無法看到其全貌,因此我們可以仿ProxyGenerator.generateProxyClass方法的方式生成其class位元組碼,檢視其全貌:

 //真實物件
Subject realSubject = new RealSubject();
        
//生成代理類
byte[] proxyClassFile = ProxyGenerator.generateProxyClass("MyName", realSubject.getClass().getInterfaces());

//儲存在本地檔案中
try (FileOutputStream fis = new FileOutputStream(new File("./myname.class"))){
    fis.write(proxyClassFile);
} catch (Exception e) {
    e.printStackTrace();
}

開啟myname.class檔案,最終代理類$ProxyX類是這樣的:

public final class MyName extends Proxy implements Subject {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public MyName(InvocationHandler var1) throws  {
        super(var1);
    }

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

    public final void sayHello(String var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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 void sayGoodBye() throws  {
        try {
            super.h.invoke(this, m3, (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);
        } 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"));
            m4 = Class.forName<