1. 程式人生 > >java中動態代理

java中動態代理

代理模式的使用場景如下:

當無法或不想直接訪問某個物件或訪問物件存在困難時可以通過一個代理物件來間接訪問,為了保證客戶端使用的透明性,委託物件與代理物件需要實現相同的介面。

在 Android 程式碼中經常會看見代理模式的存在,尤其是在 Binder 跨程序通訊方面。比如 Activity 與 ActivityManagerService 的通訊,Activity 拿到的就是 ActivityManagerService 的代理物件 ActivityManagerProxy ,由這個代理物件去執行跨程序通訊。

而 ActivityManagerProxy 和 ActivityManagerService 又實現了同樣的介面 IActivityManager。ActivityManagerService 是抽象類 ActivityManagerNative 的子類,ActivityManagerNative 實現了 IActivityManager介面。

代理模式的作用就是為其他物件提供一種代理以控制對這個物件的訪問。而在 Android 程式碼中,這個代理大多通過跨程序的方式來訪問了。

靜態代理

Java 的靜態代理實現比較簡單,就是一個代理物件持有了被代理物件的引用。

// 被代理的類
RealSubject real = new RealSubject();
// 靜態代理
ProxySubject proxySubject = new ProxySubject(real);
proxySubject.visit();12345

另外,代理物件和被代理物件都要實現相同的介面或者繼承相同的父類。

// 定義介面
public interface ISubject 
// 被代理物件實現介面
public class RealSubject implements  ISubject
// 代理物件實現介面
public class ProxySubject implements ISubject123456

由於是靜態代理,我們的程式碼在執行前編譯, Java 的 class檔案就會建立,然而, 一旦需要代理的類多了,就需要為每一個類都編寫一個代理類,也就是生成了多個class檔案。這樣建立的靜態代理還不如直接編寫程式碼了,畢竟都是要生成檔案的。

因此,為了解決上述問題,就可以考慮在程式執行時動態地生成代理的物件,在編譯階段不需要知道誰代理了誰,而 Java 也提供了一個便捷的動態代理介面 InvocationHandler,實現該介面需要重寫其呼叫方法invoke。

動態代理

Proxy 類提供了用於建立動態代理類和代理物件的靜態方法,它也是所有動態代理類的父類。

如果在程式中為一個或多個介面動態地生成實現類,就可以使用 Proxy getProxyClass 方法來建立動態代理類。

如果需要為一個介面或多個介面動態地建立例項,也可以使用 Proxy newProxyInstance 方法來建立動態代理例項。

建立動態代理類

private static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)1

該方法建立一個動態代理類所對應的 Class 物件,該代理類將實現 interfaces所指定的多個介面,第一個引數ClassLoader指定生成動態代理類的類載入器。

定義如下介面,動態建立的代理類要實現該介面。

public interface SampleInterface { void showMessage(); }123

通過 getProxyClass 方法建立動態代理類:

    Class proxyClass = Proxy.getProxyClass(
            SampleInterface.class.getClassLoader(), new Class[]{SampleInterface.class}
            );123

然後在通過反射去建立動態代理類的一個例項物件:

    // 得到的建構函式 getConstructor 帶有引數,newInstance 也有引數
    SampleInterface proxy = (SampleInterface) proxyClass
            .getConstructor(new Class[]{InvocationHandler.class})
            .newInstance(new Object[]{handler});1234

此時例項化動態代理類的一個物件需要傳遞一個引數,它必須實現了 InvocationHandler 介面的引數。

InvocationHandler handler = new SampleInvocationHandler();

public class SampleInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.printf(“Sample InvocationHandler \n”); return null; } }123456789

我們通過 getProxyClass 建立了類,並且這個類實現了指定的介面,那麼這些介面的呼叫就是由 InvocationHandler 來實現的。

在 invoke 方法裡面,我們要根據 method 和 args 等引數來判斷當前呼叫是對應介面的哪個方法,從而執行對應方法。

建立動態代理物件例項

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

該方法建立一個動態代理物件,該代理物件的例項實現了 interfaces指定的系列介面,執行代理物件的每個方法時都會被替換成InvocationHandler物件的invoke方法。

下面就是一個簡單示例:

首先定義一個代理介面:

public interface IDynamicProxy { void show(); }123

然後,通過 newProxyInstance 方法來建立代理物件例項,其中第一個引數是類載入器,第二個引數是要實現的介面,最後的 InvocationHandler 物件就是當呼叫代理物件實現的那個介面方法時,都會通過它的 invoke 方法來呼叫到具體的對應方法。

invoke 方法也是通過 method 和 args 引數來判斷具體對應的介面方法是哪一個。

    IDynamicProxy proxy = (IDynamicProxy) Proxy.newProxyInstance(
            IDynamicProxy.class.getClassLoader(), new Class<?>[]{IDynamicProxy.class
    }, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.printf("invoke\n");
            if (method.getReturnType() != Void.class){
                System.out.printf("return void");
            }
            return 1;
        }
    });
    proxy.show();12345678910111213

當通過 newProxyInstance 建立物件之後,就可以直接呼叫介面方法了。

小結

通過動態代理來建立代理類或者代理物件,有一個公共點就是都需要建立 InvocationHandler 類來執行具體的方法呼叫,並且在 invoke 方法裡面通過 method 和 args 來區分具體呼叫的方法。