天天看點

了解設計模式之代理模式

1. 代理模式的作用:為其他對象提供一種代理以控制對這個對象的通路。

2. 代理模式一般涉及到的角色有:

a) 抽象角色:聲明真實對象和代理對象的共同接口

b) 代理角色:代理對象角色内部含有對真實對象的引用,進而可以操作真實對象,同時代理對象提供與真實相同的接口以便在任何時刻都能代替真實對象。同時,代理對象可以在執行真實對象操作時,附加其他的操作,相當于對真實對象進行封裝。

c) 真實角色:代理角色所代表的真實對象,是我們最終要引用的對象。

3. 每一個代理執行個體都有一個與之關聯的invocation handler,當調用了代理執行個體中的方法,流程就會轉到與該代理執行個體關聯的invocation handler中的invoke()方法。

4. 動态代理隻是一個代理,它不會執行任何實質性的工作,在生成它的執行個體時你必須提供一個handler,由他接管實際的工作。

5. 一個代理類有以下特性:

a) 代理類是public、final的,不是抽象的

b) 代理類繼承自java.lang.reflect.Proxy類

6. Java中的動态代理對比靜态代理,相當于代理角色的有兩個類:一個是Proxy,另一個是InvocationHandler,也就是說如果按照代理模式來劃分角色的話,這兩個類都屬于代理角色。

7. Java中的動态代理的圖示:

了解設計模式之代理模式

8. Java負責建立真實代理類和對象,我們隻需提供在方法調用發生時知道做什麼的handler。

9. 不管代理被調用的是何種方法,handler被調用的一定是invoke()方法。

10. 關于Java中動态代理的了解:

首先,我們需要了解靜态代理中的幾個角色:抽象角色,代理角色,真實角色,要了解每個角色的作用。然後,關于dynamic Proxy,我們首先意識到:動态動态,這個動态意味着什麼?答:這個意味着我們的代理類是程式在運作期間生成的,以前,我們定義一個類,都是我們手動填寫,例如:

class Person{

String name;

int age;

//生成set和get方法

};但是現在,我們要轉變這個固式思維,這個代理類是Java幫助我們在運作時自動生成的,也就是說在我們的代碼執行之前,這個代理類是不存在的。那麼問題又來了,這個類的結構是怎樣的?(比如說有哪些方法)當然這個是需要我們程式員去提供這種規範的,這個規範實際上就是多個接口,我們的代理類需要去實作這些接口中的方法,如何實作我們不用管,我們隻需要提供這些接口。再回過頭想一想靜态代理中的幾個角色,其實抽象角色就是一個接口,那麼我們在動态代理中同樣需要這樣一個角色。另外,下面我們看一下這幾個類:

1) Proxy類

newProxyInstance()方法:

參數1:ClassLoader,類加載器,一個類要想被使用,需要通過類加載器去加載它,即每個類都需要被類加載器加載之後JVM才認識它,比如我們平時使用的java.lang.String類就是通過bootstrap類加載器加載的,而我們使用者自定義類是通過Application類加載器加載,後面的第13點提到,這個方法所起的作用,是以那個動态代理類也不例外,也需要類加載器去加載它。

參數2:Class[]數組,也就是動态代理需要實作的接口的Class對象數組。注意:在這裡必須是接口所對應的Class對象數組,類和抽象類都不可以。

參數3:invocationHandler。介紹到這個參數時,我們可以回到上面的語境,動态代理,說到底還隻是個代理,它并不去做實質性的工作,那麼這個做實質性工作的角色誰來扮演呢?換句話說,也就是在哪裡去通路我們的真實對象。咱們可以去查InvocationHandler這個接口的API,讀一下這個接口的說明,大緻的意思是說:

了解設計模式之代理模式

InvocationHandler是一個需要被代理執行個體的調用句柄實作(implements)的接口。每一個代理執行個體都有一個與之關聯的調用句柄。當調用了代理執行個體中的方法(無論哪個方法,隻要是調用了代理執行個體中的方法),流程就會轉到與該代理執行個體關聯的invocation handler中的invoke()方法。上面這個标注為紅色字型的這句話,個人認為是了解整個動态代理的核心,一定要切記。讀到這,我們也就明白了,實質性工作是在invoke()方法中去進行的。好,下面我們來看看invoke()方法中參數表示什麼含義:

第一個參數:代理執行個體,通常我們用不上,看Java程式設計思想上的一句話:

了解設計模式之代理模式

第二個參數:是個Method對象,通過前面的反射的學習知道,Method對象實際上代表一個方法,那麼它代表哪個方法呢?前面提到,無論調用代理執行個體的哪個方法,程式流程都會轉到這個invoke方法。雖然我們不知道目前調用哪個方法,但是有一點可以确定,那就是:在某一個時刻,這個invoke方法肯定隻會處理一個執行個體方法,那麼這個Method對象肯定隻會對應一個方法,也就是說目前是因為執行哪個方法而使流程跳轉到invoke方法,那麼這個Method對象就代表那個方法。

第三個參數:參數,方法的參數,哪個方法的參數?分析和上面的Method對象一模一樣。

在invoke方法中可以對方法的調用進行相關通路的控制。

11. 在提到代理模式的三個角色時,對代理角色的描述是:提供與真實角色相同的接口以便在任何時刻都能代替真實對象。換句話說真實對象中有的方法,代理對象就應該也有。那麼為什麼在Java動态代理中InvocationHandler類沒有重寫所有的真實角色的方法呢?因為InvocationHandler并不是Proxy,而隻是代理将調用真實對象的工作配置設定給了它。這就好比你在軟體公司,你老闆拉來了項目,分析了使用者需求之後,就把編寫代碼的任務配置設定給了你,難道說因為你寫了代碼你就變成了老闆了,客戶就會把所有資金交到你的手上,顯然不是這樣的。同理,InvocationHandler并不是真正意義上的代理,也就不需要去重寫所有的真實對象的方法了。

12. 動态代理的步驟:

a) 建立一個實作接口InvocationHandler的類,它必須實作invoke方法。

b) 建立被代理的類和接口(抽象角色和真實角色,真實角色可以省略)

c) 通過newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)建立一個代理

d) 通過代理調用方法。

13. Proxy.newInstance()方法做了兩件事情:第一件,動态地建立了一個代理類;第二件,生成了一個該代理類的對象傳回回來。

###2018.11.1添加示例代碼

14. 示例代碼:

a) 聲明抽象主題角色:

public interface Subject {

    String doSomething(String name);
}
           

b) 定義真實角色:

public class RealSubject implements Subject {
    @Override
    public String doSomething(String name) {
        return "hello " + name;
    }
}
           

c) 實作InvocationHandler:

public class MyInvocationHandler implements InvocationHandler {

    private RealSubject foo;

    public MyInvocationHandler(RealSubject foo) {
        this.foo = foo;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before invoke...");
        Object retObj = method.invoke(foo, args);
        System.out.println("after invoke...");
        return retObj;
    }
}
           

d) Client測試類:

public class Client {

    public static void main(String[] args) {

        RealSubject foo = new RealSubject();
        InvocationHandler invocationHandler = new MyInvocationHandler(foo);
        Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), new Class[]{Subject.class}, invocationHandler);

        String result = subject.doSomething("david");
        System.out.println(result);
    }
}
           

###2019.4.18日 補充

今天在使用動态代理的時候,抛出了一個異常:

Caused by: java.lang.IllegalArgumentException: android.content.res.ResourcesImpl is not an interface

這也就意味着,Proxy.newProxyInstance()方法的第二個參數中,必須是接口類型的Class對象數組。換句話說,被動态代理類必須是實作了某個接口。舉個例子,在本文前面的那個例子中,如果被代理的RealSubject類沒有實作接口:Subject,那麼動态代理在此處無法生效。那麼為什麼需要這樣去限制的呢?

所謂的動态代理,實際上就是在運作期間根據被代理接口動态生成一個代理類,這個代理類必須是繼承自Proxy類。同時,由于Java是單繼承的,那麼我們隻能通過與被代理類實作共同的接口進而實作代理,自然而然也就意味着:被代理類必須實作某個接口!