首頁 Java java教程 Java中靜態代理程式和動態代理程式的四種實作方法介紹

Java中靜態代理程式和動態代理程式的四種實作方法介紹

Oct 22, 2018 pm 02:53 PM
java jdk proxy_pass 代理商

這篇文章帶給大家的內容是關於Java中靜態代理和動態代理的四種實作方法介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

面試問題:Java裡的代理程式設計模式(Proxy Design Pattern)一共有幾種實作方式?這個題目很像孔乙己問「茴香豆的徠字有哪幾種寫法?」

所謂代理模式,是指客戶端(Client)並不會直接呼叫實際的物件(下圖右下角的RealSubject),而是透過呼叫代理(Proxy),來間接的呼叫實際的物件。

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

Java中靜態代理程式和動態代理程式的四種實作方法介紹

實作方式一:靜態代理

開發一個介面IDeveloper,該介麵包含一個方法writeCode ,寫程式碼。

public interface IDeveloper {

     public void writeCode();

}
登入後複製

建立一個Developer類,實作該介面。

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");
    }
}
登入後複製

測試程式碼:建立一個Developer實例,名叫Jerry,去寫程式碼!

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

現在問題來了。 Jerry的專案經理對Jerry光寫程式碼,而不維護任何的文件很不滿。假設哪天Jerry休假去了,其他的程式設計師來接替Jerry的工作,對著陌生的程式碼一臉問號。經全組討論決定,每位開發人員寫程式碼時,必須同步更新文件。

為了強迫每個程式設計師在開發時記著寫文檔,而又不影響大家寫程式碼這個動作本身, 我們不修改原來的Developer類,而是創建了一個新的類,同樣實現IDeveloper介面。這個新類別DeveloperProxy內部維護了一個成員變量,指向原始的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();
    }
}
登入後複製

這個代理類別實現的writeCode方法裡,在調用實際程式設計師writeCode方法之前,加上一個寫文檔的調用,這樣就確保了程式設計師寫程式碼時都伴隨著文件更新。

測試程式碼:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

#靜態代理程式方式的優點

1.易於理解和實現

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

靜態代理程式方式的缺點

每一個真實類別都需要一個建立新的代理類別。還是以上述文件更新為例,假設老闆對測試工程師也提出了新的要求,讓測試工程師每次測出bug時,也要及時更新對應的測試文件。那麼採用靜態代理的方式,測試工程師的實作類別ITester也得建立一個對應的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();
    }
}
登入後複製

正是因為有了靜態程式碼方式的這個缺點,才誕生了Java的動態代理實作方式。

Java動態代理實作方式一:InvocationHandler

InvocationHandler的原理我曾經專門寫文章介紹過:Java動態代理之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方法在動態代理類別裡透過反射的方式執行。

測試輸出:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

透過InvocationHandler實作動態代理的限制

假設有個產品經理類(ProductOwner) 沒有實作任何介面。

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

我們仍然採取EnginnerProxy代理類別去代理它,編譯時不會出錯。運行時會發生什麼事?

ProductOwner po = new ProductOwner("Ross");

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

poProxy.defineBackLog();
登入後複製

運行時報錯。所以限制就是:如果被代理的類別未實現任何接口,那麼不能採用透過InvocationHandler動態代理的方式去代理它的行為。

Java中靜態代理程式和動態代理程式的四種實作方法介紹

Java動態代理實作方式二:CGLIB

CGLIB是一個Java字節碼產生函式庫,提供了一個易用的API對Java字節碼進行建立和修改。關於這個開源庫的更多細節,請移步至CGLIB在github上的倉庫:https://github.com/cglib/cglib

我們現在嘗試用CGLIB來代理之前採用InvocationHandler沒有成功代理的ProductOwner類別(該類別未實作任何介面)。

現在我改為使用CGLIB API來建立代理類別:

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未实现任何代码,但它也成功被代理了:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

用CGLIB实现Java动态代理的局限性

如果我们了解了CGLIB创建代理类的原理,那么其局限性也就一目了然。我们现在做个实验,将ProductOwner类加上final修饰符,使其不可被继承:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

再次执行测试代码,这次就报错了: Cannot subclass final class XXXX。

所以通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。那么如果被代理类被标记成final,也就无法通过CGLIB去创建动态代理。

Java动态代理实现方式三:通过编译期提供的API动态创建代理类

假设我们确实需要给一个既是final,又未实现任何接口的ProductOwner类创建动态代码。除了InvocationHandler和CGLIB外,我们还有最后一招:

我直接把一个代理类的源代码用字符串拼出来,然后基于这个字符串调用JDK的Compiler(编译期)API,动态的创建一个新的.java文件,然后动态编译这个.java文件,这样也能得到一个新的代理类。

Java中靜態代理程式和動態代理程式的四種實作方法介紹

测试成功:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

我拼好了代码类的源代码,动态创建了代理类的.java文件,能够在Eclipse里打开这个用代码创建的.java文件,

Java中靜態代理程式和動態代理程式的四種實作方法介紹

Java中靜態代理程式和動態代理程式的四種實作方法介紹

下图是如何动态创建ProductPwnerSCProxy.java文件:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

下图是如何用JavaCompiler API动态编译前一步动态创建出的.java文件,生成.class文件:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

下图是如何用类加载器加载编译好的.class文件到内存:

Java中靜態代理程式和動態代理程式的四種實作方法介紹

如果您想试试这篇文章介绍的这四种代理模式(Proxy Design Pattern), 请参考我的github仓库,全部代码都在上面。感谢阅读。

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

以上是Java中靜態代理程式和動態代理程式的四種實作方法介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1663
14
CakePHP 教程
1419
52
Laravel 教程
1313
25
PHP教程
1264
29
C# 教程
1237
24
突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP的影響:網絡開發及以後 PHP的影響:網絡開發及以後 Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP:許多網站的基礎 PHP:許多網站的基礎 Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

See all articles