成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java代理設計模式(Proxy)的四種具體實現(xiàn):靜態(tài)代理和動態(tài)代理

gclove / 3067人閱讀

摘要:除了和外,我們還有最后一招我直接把一個代理類的源代碼用字符串拼出來,然后基于這個字符串調(diào)用的編譯期,動態(tài)的創(chuàng)建一個新的文件,然后動態(tài)編譯這個文件,這樣也能得到一個新的代理類。

面試問題:Java里的代理設計模式(Proxy Design Pattern)一共有幾種實現(xiàn)方式?這個題目很像孔乙己問“茴香豆的茴字有哪幾種寫法?”

所謂代理模式,是指客戶端(Client)并不直接調(diào)用實際的對象(下圖右下角的RealSubject),而是通過調(diào)用代理(Proxy),來間接的調(diào)用實際的對象。

代理模式的使用場合,一般是由于客戶端不想直接訪問實際對象,或者訪問實際的對象存在技術上的障礙,因而通過代理對象作為橋梁,來完成間接訪問。

實現(xiàn)方式一:靜態(tài)代理

開發(fā)一個接口IDeveloper,該接口包含一個方法writeCode,寫代碼。

public interface IDeveloper {

     public void writeCode();

}

創(chuàng)建一個Developer類,實現(xiàn)該接口。

public class Developer implements IDeveloper{
    private String name;
    public Developer(String name){
        this.name = name;
    }
    @Override
    public void writeCode() {
        System.out.println("Developer " + name + " writes code");
    }
}

測試代碼:創(chuàng)建一個Developer實例,名叫Jerry,去寫代碼!

public class DeveloperTest {
    public static void main(String[] args) {
        IDeveloper jerry = new Developer("Jerry");
        jerry.writeCode();
    }
}

現(xiàn)在問題來了。Jerry的項目經(jīng)理對Jerry光寫代碼,而不維護任何的文檔很不滿。假設哪天Jerry休假去了,其他的程序員來接替Jerry的工作,對著陌生的代碼一臉問號。經(jīng)全組討論決定,每個開發(fā)人員寫代碼時,必須同步更新文檔。

為了強迫每個程序員在開發(fā)時記著寫文檔,而又不影響大家寫代碼這個動作本身, 我們不修改原來的Developer類,而是創(chuàng)建了一個新的類,同樣實現(xiàn)IDeveloper接口。這個新類DeveloperProxy內(nèi)部維護了一個成員變量,指向原始的IDeveloper實例:

public class DeveloperProxy implements IDeveloper{
    private IDeveloper developer;
    public DeveloperProxy(IDeveloper developer){
        this.developer = developer;
    }
    @Override
    public void writeCode() {
        System.out.println("Write documentation...");
        this.developer.writeCode();
    }
}

這個代理類實現(xiàn)的writeCode方法里,在調(diào)用實際程序員writeCode方法之前,加上一個寫文檔的調(diào)用,這樣就確保了程序員寫代碼時都伴隨著文檔更新。

測試代碼:

靜態(tài)代理方式的優(yōu)點

1. 易于理解和實現(xiàn)

2. 代理類和真實類的關系是編譯期靜態(tài)決定的,和下文馬上要介紹的動態(tài)代理比較起來,執(zhí)行時沒有任何額外開銷。

靜態(tài)代理方式的缺點

每一個真實類都需要一個創(chuàng)建新的代理類。還是以上述文檔更新為例,假設老板對測試工程師也提出了新的要求,讓測試工程師每次測出bug時,也要及時更新對應的測試文檔。那么采用靜態(tài)代理的方式,測試工程師的實現(xiàn)類ITester也得創(chuàng)建一個對應的ITesterProxy類。

public interface ITester {
    public void doTesting();
}
Original tester implementation class:
public class Tester implements ITester {
    private String name;
    public Tester(String name){
        this.name = name;
    }
    @Override
    public void doTesting() {
        System.out.println("Tester " + name + " is testing code");
    }
}
public class TesterProxy implements ITester{
    private ITester tester;
    public TesterProxy(ITester tester){
        this.tester = tester;
    }
    @Override
    public void doTesting() {
        System.out.println("Tester is preparing test documentation...");
        tester.doTesting();
    }
}

正是因為有了靜態(tài)代碼方式的這個缺點,才誕生了Java的動態(tài)代理實現(xiàn)方式。

Java動態(tài)代理實現(xiàn)方式一:InvocationHandler

InvocationHandler的原理我曾經(jīng)專門寫文章介紹過:Java動態(tài)代理之InvocationHandler最簡單的入門教程

通過InvocationHandler, 我可以用一個EnginnerProxy代理類來同時代理Developer和Tester的行為。

public class EnginnerProxy implements InvocationHandler {
    Object obj;
    public Object bind(Object obj)
    {
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable
    {
        System.out.println("Enginner writes document");
        Object res = method.invoke(obj, args);
        return res;
    }
}

真實類的writeCode和doTesting方法在動態(tài)代理類里通過反射的方式進行執(zhí)行。

測試輸出:

通過InvocationHandler實現(xiàn)動態(tài)代理的局限性

假設有個產(chǎn)品經(jīng)理類(ProductOwner) 沒有實現(xiàn)任何接口。

public class ProductOwner {
    private String name;
    public ProductOwner(String name){
        this.name = name;
    }
    public void defineBackLog(){
        System.out.println("PO: " + name + " defines Backlog.");
    }
}

我們?nèi)匀徊扇nginnerProxy代理類去代理它,編譯時不會出錯。運行時會發(fā)生什么事?

ProductOwner po = new ProductOwner("Ross");

ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po);

poProxy.defineBackLog();

運行時報錯。所以局限性就是:如果被代理的類未實現(xiàn)任何接口,那么不能采用通過InvocationHandler動態(tài)代理的方式去代理它的行為。

Java動態(tài)代理實現(xiàn)方式二:CGLIB

CGLIB是一個Java字節(jié)碼生成庫,提供了易用的API對Java字節(jié)碼進行創(chuàng)建和修改。關于這個開源庫的更多細節(jié),請移步至CGLIB在github上的倉庫:https://github.com/cglib/cglib

我們現(xiàn)在嘗試用CGLIB來代理之前采用InvocationHandler沒有成功代理的ProductOwner類(該類未實現(xiàn)任何接口)。

現(xiàn)在我改為使用CGLIB API來創(chuàng)建代理類:

public class EnginnerCGLibProxy {
    Object obj;
    public Object bind(final Object target)
    {
        this.obj = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable
            {
                System.out.println("Enginner 2 writes document");
                Object res = method.invoke(target, args);
                return res;
            }
        }
        );
        return enhancer.create();
    }
}

測試代碼:

ProductOwner ross = new ProductOwner("Ross");

ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross);

rossProxy.defineBackLog();

盡管ProductOwner未實現(xiàn)任何代碼,但它也成功被代理了:

用CGLIB實現(xiàn)Java動態(tài)代理的局限性

如果我們了解了CGLIB創(chuàng)建代理類的原理,那么其局限性也就一目了然。我們現(xiàn)在做個實驗,將ProductOwner類加上final修飾符,使其不可被繼承:

再次執(zhí)行測試代碼,這次就報錯了: Cannot subclass final class XXXX。

所以通過CGLIB成功創(chuàng)建的動態(tài)代理,實際是被代理類的一個子類。那么如果被代理類被標記成final,也就無法通過CGLIB去創(chuàng)建動態(tài)代理。

Java動態(tài)代理實現(xiàn)方式三:通過編譯期提供的API動態(tài)創(chuàng)建代理類

假設我們確實需要給一個既是final,又未實現(xiàn)任何接口的ProductOwner類創(chuàng)建動態(tài)代碼。除了InvocationHandler和CGLIB外,我們還有最后一招:

我直接把一個代理類的源代碼用字符串拼出來,然后基于這個字符串調(diào)用JDK的Compiler(編譯期)API,動態(tài)的創(chuàng)建一個新的.java文件,然后動態(tài)編譯這個.java文件,這樣也能得到一個新的代理類。

測試成功:

我拼好了代碼類的源代碼,動態(tài)創(chuàng)建了代理類的.java文件,能夠在Eclipse里打開這個用代碼創(chuàng)建的.java文件,

下圖是如何動態(tài)創(chuàng)建ProductPwnerSCProxy.java文件:

下圖是如何用JavaCompiler API動態(tài)編譯前一步動態(tài)創(chuàng)建出的.java文件,生成.class文件:

下圖是如何用類加載器加載編譯好的.class文件到內(nèi)存:

如果您想試試這篇文章介紹的這四種代理模式(Proxy Design Pattern), 請參考我的github倉庫,全部代碼都在上面。感謝閱讀。

https://github.com/i042416/Ja...

要獲取更多Jerry的原創(chuàng)技術文章,請關注公眾號"汪子熙"或者掃描下面二維碼:

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/71808.html

相關文章

  • 100行代碼讓您學會JavaScript原生的Proxy設計模式

    摘要:面向?qū)ο笤O計里的設計模式之代理模式,相信很多朋友已經(jīng)很熟悉了。代表當前執(zhí)行方法的實例,即方法調(diào)用者。代表具體的方法名稱?,F(xiàn)在我們再次調(diào)用,傳入構造器返回的代理對象打印輸出,代理邏輯生效了和的一樣優(yōu)雅地實現(xiàn)了代理設計模式。 showImg(https://segmentfault.com/img/remote/1460000016760603);面向?qū)ο笤O計里的設計模式之Proxy(代理...

    txgcwm 評論0 收藏0
  • 當Kotlin邂逅設計模式代理模式(二)

    摘要:簡述從這篇文章起,我們將繼續(xù)邂逅設計模式系列篇中的第二篇代理模式。代理模式可以說很多初級中級開發(fā)者迷惑的設計模式。首先我們需要使用類圖直觀地表示出代理模式思想。所以基于代理模式很輕松就實現(xiàn)。簡述: 從這篇文章起,我們將繼續(xù)Kotlin邂逅設計模式系列篇中的第二篇代理模式。代理模式可以說很多初級中級開發(fā)者迷惑的設計模式。但是它確實應用很廣,不用多說大家非常熟悉的Retrofit框架,內(nèi)部使用了...

    番茄西紅柿 評論0 收藏0
  • 跨域四種方式

    摘要:本文主要是關于跨域的幾種方式,關于什么是跨域這里就不多說了,寫這個也是為了記住一些知識點的。我自己用和的寫過一些,但是沒有在實際工作中用過,所以對這一塊了解不深。 本文主要是關于跨域的幾種方式,關于什么是跨域這里就不多說了,寫這個也是為了記住一些知識點的。 一. jsonp jsonp的跨域方式很容易理解,頁面的的每一個script標簽瀏覽器都會發(fā)送get請求獲取對應的文本資源,獲取到...

    StonePanda 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<