10分鐘了解 代理模式與java中的動態代理

分類:IT技術 時間:2017-10-04

前言

?? 代理模式又分為靜態代理與動態代理,其中動態代理是Java各大框架中運用的最為廣泛的一種模式之一,下面就用簡單的例子來說明靜態代理與動態代理。

場景

?? 李雷是一個唱片公司的大老板,很忙,但是經常有一些雞毛蒜皮的小事總不能自己親自去跑腿吧,大老板哪裏有這麽多時間,於是他找了一個助手叫做韓梅梅,韓梅梅就負責幫老板去處理這些事情。下面就模擬韓梅梅代理老板去處理一些小事的情況。

靜態代理

代碼

  • 老板類
public class Boss {
    
    private String name;
    
    private Boss(String name){
        this.name = name;
    }
    
    public void ok(){
        system.out.println(name +"說: 我覺得OK! :)");
    }
    
    public void notOk(){
        System.out.println(name + "說: 我覺得不行! :(");
    }
    
    public void normal(){
        System.out.println(name + "說: 我覺得很普通! XD");
    }

}
  • 助理類
public class Secretary {
    
    private Boss boss;
    
    private String name;

    public Secretary(String secreataryName,Boss boss){
        name = secreataryName;
        this.boss = boss;
    }
    
    public void ok(){
        boss.ok();
    }
    
    public void notOk(){
        boss.notOk();
    }
    
    public void normal(){
        boss.normal();
    }
}
  • 客戶端
public class HanMMClient {
    public static void main(String[] args) {
        
        Boss boss = new Boss("李雷");
        Secretary proxy = new Secretary("韓梅梅", boss);
        
        proxy.ok();
        proxy.notOk();
        proxy.normal();
    }
}

這樣一個最初版本的代理模式就完成了,可以看到客戶端中並沒有與老板直接接觸就通過韓梅梅代理成功的調用了老板的三個方法。
可以發現 代理類韓梅梅與老板實現了相同的方法,因此將他們抽象成為一個Iboss接口,再分別實現,這樣的靜態代理模式就趨於完整了。

  • IBoss 接口

    public interface Iboss {
    
    void ok();
    
    void notOk();
    
    void normal();
    }
  • 客戶端

public class HanMMClient {
    public static void main(String[] args) {
        
        Iboss boss = new Boss("李雷");
        Iboss proxy = new Secretary("韓梅梅",boss);
        
        proxy.ok();
        proxy.notOk();
        proxy.normal();
    }
}

運行一下,結果良好 :)

李雷說: 我覺得OK! :)
李雷說: 我覺得不行! :(
李雷說: 我覺得很普通! XD
  • 結構圖

  • 靜態代理的特點在於,是提前編寫好的,編譯期間就生成了代理,並且一個主題類對應一個代理類。

新的需求

?? 現在唱片公司有兩個老板,李雷A與李雷B,他們分別對應的兩個代理是韓梅梅A與韓梅梅B,現在要求韓梅梅們在代理的時候要聲明自己是誰,這樣確保是在正確的人在代理。
?? 乍一看,這還不簡單,我在兩個代理類的每個方法前都加上聲明語句就好了。可是假設整個唱片集團需要100個代理,他們都要加聲明該怎麽辦呢?難道實現100個代理類然後每個方法前面加聲明嗎?這樣忒蠢了點,所以動態代理應運而生。

動態代理

??上面的需求很容易讓人想到Spring中的AOP,事實上aop就是實現動態代理的一個很典型的例子。

  • 動態代理的特點是在於代理類在運行期間生成,一個代理類可以對應多個主題類

?? 動態生成代理類需要運用到Proxy這個類,api文檔這樣描述的

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

翻譯一下,就是Proxy類提供可以創造動態類和實例的靜態方法,同時也是這些被創造的類和實例的父類。
在這裏需要使用其中的newProxyInstance方法來創造代理實例

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

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler

再翻譯一下,返回指定接口的代理類的實例,這些接口將方法調用分派給指定的調用處理程序。
說明白點就是,這個newProxyInstance可以根據參數動態生成一個代理類,下面是參數的解釋

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

loader: 類加載器,這個不用多說了,涉及到運行時操作必然要涉及到反射,通過類加載器裝載類

interfaces: 一個Interface對象的數組,代表給代理對象提供一組接口實現,如果我提供了一組接口給它,那麽這個代理對象就宣稱實現了該接口,這樣就能調用這組接口中的方法了

handler: 這個參數代表的是動態代理對象在調用方法的時候,會將方法轉發到哪一個invocationHandler對象身上

通過這三個參數,類加載器裝載,實現接口,以及調用方法的處理,就可以動態的生成一個代理類。

上面三個參數第三個參數時InvocationHandler對象,那這個是什麽呢?
依舊是查看api文檔

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

翻譯 每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯到了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用
該接口只有一個方法,就是invoke,每次調用代理類的方法的時候,就會被轉發到這裏來處理,所以叫handler啦

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

proxy:  生成的代理實例對象

method: 要調用真實對象的某個方法的Method對象

args:  真實對象某個方法時接受的參數

介紹完這兩個類之後,下面編寫我們的動態代理代碼

  • 老板行為接口

    public interface Iboss {
    
    void ok();
    
    void notOk();
    
    void normal();
    }
  • 老板類

public class Boss implements Iboss{
    
    private String name;
    
    public Boss(String name){
        this.name = name;
    }
    
    @Override
    public void ok(){
        System.out.println(name +"說: 我覺得OK! :)");
    }
    
    @Override
    public void notOk(){
        System.out.println(name + "說: 我覺得不行! :(");
    }
    
    @Override
    public void normal(){
        System.out.println(name + "說: 我覺得很普通! XD");
    }

}
  • 動態代理類
public class DynamicProxy implements InvocationHandler{

    private Object subject;

    private String secretaryName;

    public DynamicProxy(Object subObject, String secretaryName) {
        this.subject = subObject;
        this.secretaryName = secretaryName;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] methodParams)
            throws Throwable {
        
        //這裏打印一下方法,看看究竟是不是接口裏的方法呢
        System.out.println("Method :" + method);
        
        //before method
        System.out.printf("我是%s代理,我為老板代言!\n",secretaryName);
        
        method.invoke(subject, methodParams);
        
        //after method
        System.out.println("代理完了,謝謝!\n");
        
        return null;
    }

}
  • 客戶端調用代碼
public class HanMMClient {
    public static void main(String[] args) {

        Iboss realSubject = new Boss("李雷");

        /* 實例化 類加載器 clasLoader 參數,這裏只要是appClassLoader級別的裝載都可以裝載 */
        ClassLoader loader = realSubject.getClass().getClassLoader();

        /* 實例化 代理類要實現的接口組 Interfaces 參數 */
        Class<?>[] interfaces = realSubject.getClass().getInterfaces();

        /* 實例化 調用方法的方法轉發處理  handler參數 */
        DynamicProxy handler = new DynamicProxy(realSubject, "韓梅梅");

        Iboss subject = (Iboss) Proxy.newProxyInstance(loader,interfaces,handler);

        System.out.println("運行期間動態生成的代理類 類名是 : " + subject.getClass().getName()+"\n");

        subject.ok();
        subject.notOk();
        subject.normal();
    }
}
  • 運行結果
運行期間動態生成的代理類 類名是 : com.sun.proxy.$Proxy0

Method :public abstract void com.matrix.repository.Iboss.ok()
我是韓梅梅代理,我為老板代言!
李雷說: 我覺得OK! :)
代理完了,謝謝!

Method :public abstract void com.matrix.repository.Iboss.notOk()
我是韓梅梅代理,我為老板代言!
李雷說: 我覺得不行! :(
代理完了,謝謝!

Method :public abstract void com.matrix.repository.Iboss.normal()
我是韓梅梅代理,我為老板代言!
李雷說: 我覺得很普通! XD
代理完了,謝謝!

運行結果良好:)

控制臺輸出的第一行我打印了一下動態生成的代理類的類名,可以看到這個類是在proxy下的,肯定的嘛,畢竟你調用的方法就叫newProxyInstance方法,後面的$和數字代表他的標號,123456一直往下排
後面的控制臺輸出 Method 哪一行輸出結果可以看到和猜想的一樣,是通過提供的第二個參數,也就是接口組裏的方法來定位的,然後通過執行
Method.invoke(subject,methodParms)
這一行,通過反射去調用了被代理類的方法,也就是說,起到調用效果的是這一行,和平常的new出對象然後調用是不一樣的,這也就是為什麽可以在這裏加上before Method和after Method或者各種其他操作的原因。

相信你看到這裏應該明白了什麽是動態代理,同時aop的實現方式是怎麽樣的也不用說了吧?

總結

動態代理平常直接使用的不多,因為平常操作都是基於框架與組件之上的,但是如果你想要真正的了解其中的原理或者編寫屬於自己的框架,那麽動態代理就十分重要啦^_^


Tags: 代理 public 梅梅 name 老板 boss

文章來源:


ads
ads

相關文章
ads

相關文章

ad