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

這篇文章帶給大家的內容是關於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中文網其他相關文章!

陳述
本文轉載於:segmentfault思否。如有侵權,請聯絡admin@php.cn刪除
為什麼Java是開發跨平台桌面應用程序的流行選擇?為什麼Java是開發跨平台桌面應用程序的流行選擇?Apr 25, 2025 am 12:23 AM

javaispopularforcross-platformdesktopapplicationsduetoits“ writeonce,runany where”哲學。 1)itusesbytiesebyTecodeThatrunsonAnyJvm-備用Platform.2)librarieslikeslikeslikeswingingandjavafxhelpcreatenative-lookingenative-lookinguisis.3)

討論可能需要在Java中編寫平台特定代碼的情況。討論可能需要在Java中編寫平台特定代碼的情況。Apr 25, 2025 am 12:22 AM

在Java中編寫平台特定代碼的原因包括訪問特定操作系統功能、與特定硬件交互和優化性能。 1)使用JNA或JNI訪問Windows註冊表;2)通過JNI與Linux特定硬件驅動程序交互;3)通過JNI使用Metal優化macOS上的遊戲性能。儘管如此,編寫平台特定代碼會影響代碼的可移植性、增加複雜性、可能帶來性能開銷和安全風險。

與平台獨立性相關的Java開發的未來趨勢是什麼?與平台獨立性相關的Java開發的未來趨勢是什麼?Apr 25, 2025 am 12:12 AM

Java將通過雲原生應用、多平台部署和跨語言互操作進一步提昇平台獨立性。 1)雲原生應用將使用GraalVM和Quarkus提升啟動速度。 2)Java將擴展到嵌入式設備、移動設備和量子計算機。 3)通過GraalVM,Java將與Python、JavaScript等語言無縫集成,增強跨語言互操作性。

Java的強鍵入如何有助於平台獨立性?Java的強鍵入如何有助於平台獨立性?Apr 25, 2025 am 12:11 AM

Java的強類型系統通過類型安全、統一的類型轉換和多態性確保了平台獨立性。 1)類型安全在編譯時進行類型檢查,避免運行時錯誤;2)統一的類型轉換規則在所有平台上一致;3)多態性和接口機制使代碼在不同平台上行為一致。

說明Java本機界面(JNI)如何損害平台獨立性。說明Java本機界面(JNI)如何損害平台獨立性。Apr 25, 2025 am 12:07 AM

JNI會破壞Java的平台獨立性。 1)JNI需要特定平台的本地庫,2)本地代碼需在目標平台編譯和鏈接,3)不同版本的操作系統或JVM可能需要不同的本地庫版本,4)本地代碼可能引入安全漏洞或導致程序崩潰。

是否有任何威脅或增強Java平台獨立性的新興技術?是否有任何威脅或增強Java平台獨立性的新興技術?Apr 24, 2025 am 12:11 AM

新興技術對Java的平台獨立性既有威脅也有增強。 1)雲計算和容器化技術如Docker增強了Java的平台獨立性,但需要優化以適應不同雲環境。 2)WebAssembly通過GraalVM編譯Java代碼,擴展了其平台獨立性,但需與其他語言競爭性能。

JVM的實現是什麼,它們都提供了相同的平台獨立性?JVM的實現是什麼,它們都提供了相同的平台獨立性?Apr 24, 2025 am 12:10 AM

不同JVM實現都能提供平台獨立性,但表現略有不同。 1.OracleHotSpot和OpenJDKJVM在平台獨立性上表現相似,但OpenJDK可能需額外配置。 2.IBMJ9JVM在特定操作系統上表現優化。 3.GraalVM支持多語言,需額外配置。 4.AzulZingJVM需特定平台調整。

平台獨立性如何降低發展成本和時間?平台獨立性如何降低發展成本和時間?Apr 24, 2025 am 12:08 AM

平台獨立性通過在多種操作系統上運行同一套代碼,降低開發成本和縮短開發時間。具體表現為:1.減少開發時間,只需維護一套代碼;2.降低維護成本,統一測試流程;3.快速迭代和團隊協作,簡化部署過程。

See all articles

熱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

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

熱工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SublimeText3 Mac版

SublimeText3 Mac版

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