Java 中的動態代理

来源:https://www.cnblogs.com/zmy-520131499/archive/2019/08/22/11396753.html
-Advertisement-
Play Games

一、概述 1. 什麼是代理 我們大家都知道微商代理,簡單地說就是代替廠家賣商品,廠家“委托”代理為其銷售商品。關於微商代理,首先我們從他們那裡買東西時通常不知道背後的廠家究竟是誰,也就是說,“委托者”對我們來說是不可見的;其次,微商代理主要以朋友圈的人為目標客戶,這就相當於為廠家做了一次對客戶群體的 ...


一、概述

1. 什麼是代理

  我們大家都知道微商代理,簡單地說就是代替廠家賣商品,廠家“委托”代理為其銷售商品。關於微商代理,首先我們從他們那裡買東西時通常不知道背後的廠家究竟是誰,也就是說,“委托者”對我們來說是不可見的;其次,微商代理主要以朋友圈的人為目標客戶,這就相當於為廠家做了一次對客戶群體的“過濾”。我們把微商代理和廠家進一步抽象,前者可抽象為代理類,後者可抽象為委托類(被代理類)。通過使用代理,通常有兩個優點,並且能夠分別與我們提到的微商代理的兩個特點對應起來:

  優點一:可以隱藏委托類(被代理類)的實現;

  優點二:可以實現客戶與委托類間的解耦,在不修改委托類代碼的情況下能夠做一些額外的處理。

2. 靜態代理

  若代理類在程式運行前就已經存在,那麼這種代理方式被成為 靜態代理 ,這種情況下的代理類通常都是我們在Java代碼中定義的。 通常情況下, 靜態代理中的代理類和委托類會實現同一介面或是派生自相同的父類。 下麵我們用Vendor類代表生產廠家,BusinessAgent類代表微商代理,來介紹下靜態代理的簡單實現,委托類和代理類都實現了Sell介面,Sell介面的定義如下:

public interface Sell {
    void sell();

    void ad();

}

  被代理類Vendor類的定義如下:

public class Vendor implements Sell {
    public void sell() {

        System.out.println("In sell method");

    }

    public void ad() {

        System.out.println("ad method");

    }

}

  代理類BusinessAgent的定義如下:

public class Vendor implements Sell {
    public void sell() {

        System.out.println("In sell method");

    }

    public void ad() {

        System.out.println("ad method");

    }

}

  從BusinessAgent類的定義我們可以瞭解到,靜態代理可以通過聚合來實現,讓代理類持有一個委托類的引用即可。

下麵我們考慮一下這個需求:給Vendor類增加一個過濾功能,只賣貨給大學生。通過靜態代理,我們無需修改Vendor類的代碼就可以實現,只需在BusinessAgent類中的sell方法中添加一個判斷即可如下所示:

public class BusinessAgent implements Sell { 
     
    ... 
     
    public void sell() { 
     
        if (isCollegeStudent()) { 
     
            vendor.sell(); 
     
        } 
     
    } 
     
    ... 
 
} 

  這對應著我們上面提到的使用代理的第二個優點:可以實現客戶與委托類間的解耦,在不修改委托類代碼的情況下能夠做一些額外的處理。靜態代理的局限在於運行前必須編寫好代理類,下麵我們重點來介紹下運行時生成代理類的動態代理方式。

二、動態代理

1. 什麼是動態代理

  代理類在程式運行時創建的代理方式被稱為動態代理。 也就是說,這種情況下,代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類的函數。 這麼說比較抽象,下麵我們結合一個實例來介紹一下動態代理的這個優勢是怎麼體現的。

現在,假設我們要實現這樣一個需求:在執行委托(被代理)類中的方法之前輸出“before”,在執行完畢後輸出“after”。我們還是以上面例子中的Vendor類作為委托類,BusinessAgent類作為代理類來進行介紹。首先我們來使用靜態代理來實現這一需求,相關代碼如下:

//代理類
public
class BusinessAgent implements Sell { private Vendor mVendor; public BusinessAgent(Vendor vendor) { this.mVendor = vendor; } public void sell() { System.out.println("before"); mVendor.sell(); System.out.println("after"); } public void ad() { System.out.println("before"); mVendor.ad(); System.out.println("after"); } }

  從以上代碼中我們可以瞭解到,通過靜態代理實現我們的需求需要我們在每個方法中都添加相應的邏輯,這裡只存在兩個方法所以工作量還不算大,假如Sell介面中包含上百個方法呢?這時候使用靜態代理就會編寫許多冗餘代碼。通過使用動態代理,我們可以做一個“統一指示”,從而對所有代理類的方法進行統一處理,而不用逐一修改每個方法。下麵我們來具體介紹下如何使用動態代理方式實現我們的需求。

2. 使用動態代理

(1)InvocationHandler介面

在使用動態代理時,我們需要定義一個位於代理類與委托被代理()類之間的中介類,這個中介類被要求實現InvocationHandler介面,這個介面的定義如下:

public interface InvocationHandler {

    Object invoke(Object proxy, Method method, Object[] args);

}

  從InvocationHandler這個名稱我們就可以知道,實現了這個介面的中介類用做“調用處理器”。當我們調用代理類對象的方法時,這個“調用”會轉送到invoke方法中,代理類對象作為proxy參數傳入,參數method標識了我們具體調用的是代理類的哪個方法,args為這個方法的可變參數(可以有0個,1個,2個,...,N個)。這樣一來,我們對代理類中的所有方法的調用都會變為對invoke的調用,這樣我們可以在invoke方法中添加統一的處理邏輯(也可以根據method參數對不同的代理類方法做不同的處理)。因此我們只需在中介類的invoke方法實現中輸出“before”,然後調用委托類的invoke方法,再輸出“after”。下麵我們來一步一步具體實現它。

(2)委托類的定義

  動態代理方式下,要求委托類必須實現某個介面,這裡我們實現的是Sell介面。委托類Vendor類的定義如下:

public class Vendor implements Sell {

    public void sell() {

        System.out.println("In sell method");

    }

    public void ad() {

        System.out.println("ad method");

    }

}

(3)中介類

上面我們提到過,中介類必須實現InvocationHandler介面,作為調用處理器”攔截“對代理類方法的調用。中介類的定義如下:

public class DynamicProxy implements InvocationHandler {

    private Object obj; // obj為委托類對象;

    public DynamicProxy(Object obj) {

        this.obj = obj;

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("before");

        Object result = method.invoke(obj, args);

        System.out.println("after");

        return result;

    }

}

  從以上代碼中我們可以看到,中介類持有一個委托類(被代理)對象引用,在invoke方法中調用了委托(被代理)類對象的相應方法,看到這裡是不是覺得似曾相識?通過聚合方式持有委托(被代理)類對象引用,把外部對invoke的調用最終都轉為對委托(被代理)類對象的調用。這不就是我們上面介紹的靜態代理的一種實現方式嗎?實際上,中介類與委托(被代理)類構成了靜態代理關係,在這個關係中,中介類是代理類,委托(被代理)類就是委托(被代理)類; 代理類與中介類也構成一個靜態代理關係,在這個關係中,中介類是委托(被代理)類,代理類是代理類。也就是說,動態代理關係由兩組靜態代理關係組成,這就是動態代理的原理。下麵我們來介紹一下如何”指示“以動態生成代理類。

(4)動態生成代理類

  動態生成代理類的相關代碼如下:

public class Main {

    public static void main(String[] args) {

        // 創建中介類實例

        DynamicProxy inter = new DynamicProxy(new Vendor());

        // 加上這句將會產生一個$Proxy0.class文件,這個文件即為動態生成的代理類文件

        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // 獲取代理類實例sell

        Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] { Sell.class }, inter));

        // 通過代理類對象調用代理類方法,實際上會轉到invoke方法調用

        sell.sell();

        sell.ad();

    }

}

  在以上代碼中,我們調用Proxy類的newProxyInstance方法來獲取一個代理類實例。這個代理類實現了我們指定的介面並且會把方法調用分發到指定的調用處理器。這個方法的聲明如下:

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

方法的三個參數含義分別如下:

  loader:定義了代理類的ClassLoder;

  interfaces:代理類實現的介面列表

  h:調用處理器,也就是我們上面定義的實現了InvocationHandler介面的類實例

我們運行一下,看看我們的動態代理是否能正常工作。我這裡運行後的輸出為:

說明我們的動態代理確實奏效了。

  上面我們已經簡單提到過動態代理的原理,這裡再簡單的總結下:首先通過newProxyInstance方法獲取代理類實例,而後我們便可以通過這個代理類實例調用代理類的方法,對代理類的方法的調用實際上都會調用中介類(調用處理器)的invoke方法,在invoke方法中我們調用委托類的相應方法,並且可以添加自己的處理邏輯。

先看一下代理模式,這個應該是設計模式中最簡單的一個了,類圖

代理模式最大的特點就是代理類和實際業務類實現同一個介面(或繼承同一父類),代理對象持有一個實際對象的引用,外部調用時操作的是代理對象,而在代理對象的內部實現中又會去調用實際對象的操作

 Java動態代理其實內部也是通過Java反射機制來實現的,即已知的一個對象,然後在運行時動態調用其方法,這樣在調用前後作一些相應的處理,這樣說的比較籠統,舉個簡單的例子

   比如我們在應用中有這樣一個需求,在對某個類的一個方法的調用前和調用後都要做一下日誌操作,

一個普通的介面

public interface AppService {
      public boolean createApp(String name);
}

該介面的預設實現類

public class AppServiceImpl implements AppService {
    public boolean createApp(String name) {
        System.out.println("App[" + name + "] has been created.");
        return true;
    }
}

日誌處理器

public class LoggerInterceptor implements InvocationHandler {// 註意實現這個Handler介面
    private Object target;// 目標對象的引用,這裡設計成Object類型,更具通用性

    public LoggerInterceptor(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
        System.out.println(
                "Entered " + target.getClass().getName() + "-" + method.getName() + ",with arguments{" + arg[0] + "}");
        Object result = method.invoke(target, arg);// 調用目標對象的方法
        System.out.println("Before return:" + result);
        return result;
    }
}

外部調用

public class Main {
    public static void main(String[] args) {
        AppService target = new AppServiceImpl();// 生成目標對象
        // 接下來創建代理對象
        AppService proxy = (AppService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new LoggerInterceptor(target));
        proxy.createApp("Kevin Test");
    }
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 類spring ioc 泛型保留 什麼是泛型擦除 Java並不會傳遞泛型類,舉個直觀的慄子: 這裡 嘗試列印泛型類型, 泛型指定了 類,來個測試看看 是否能被獲取到? 依賴腳本build.gradle 運行可以看到結果是,spring ioc並不能註入獲取泛型 自定義IOC泛型註入 在解決sprin ...
  • 摘要: 的兩大核心技術就是 和`AOP AOP Spring AOP CGLIB Spring AOP Spring AOP`的一個運行過程。知其然,知其所以然,才能更好的駕馭這門核心技術。 所有的 驅動技術都得看他的 ,所以上面最重要的是這一句 ,下麵看看它 是一個項容器註冊自動代理創建器 說明 ...
  • 對於剛入門的springboot的新手來說,學的過程中碰到的一些問題記錄下。 1. 首先,配置好Maven環境及本地倉庫 之後進入Maven安裝目錄conf文件夾下的settings.xml配置文件,用Notepadd++打開文件。 配置本地倉庫指向自己創建的本地倉庫,如圖 把jdk版本固定為1.8 ...
  • 對Series的理解也源於對其相關的代碼操作,本次僅貼一些代碼來加深理解以及記憶 ...
  • 第十二章 Django框架 12.1 伺服器程式和應用程式 伺服器程式負責對socket伺服器進行封裝,併在請求到來時,對請求的各種數據進行整理。應用程式則負責具體的邏輯處理。為了方便應用程式的開發,就出現了眾多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的開發方 ...
  • 一、使用JSONObject來解析JSON數據官方提供的,所以不需要導入第三方jar包;直接上代碼,如下 步驟解讀: 定義一個JSON數組,用於將伺服器返回的數據傳入到一個JSONArray對象中; 然後迴圈遍歷這個JSONArray,從中取出每一個元素(JSONObject對象),接下來只需調用g ...
  • String轉成jsonObject JsonObject json = JsonObject.fromObject(String str) String轉成JsonArray JsonArray jsonArray = JsonArray.fromObject(String str) 在開發過程中 ...
  • Spring Security 解析(二) —— 認證過程   在學習Spring Cloud 時,遇到了授權服務oauth 相關內容時,總是一知半解,因此決定先把Spring Security 、Spring Security Oauth2 等許可權、認證相關的內容、原理及設計學 ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...