Spring代理模式


Spring代理模式

之前提到,Spring 的兩個關鍵點就是 IoC(控制反轉) 和 AOP(面向切面編程),IoC 已經研究過了,接下里就到 AOP 了。不過在學習 Spring AOP 前,必須要了解一下代理模式,因為代理模式是 AOP 的核心。

代理模式可以分為靜態代理和動態代理,新建 Spring-08-Proxy 項目研究一下(因為在學習 Spring 的過程中,就不額外開個分類了)。

1. 靜態代理

1.1 代理模式類圖

代理模式( Proxy Pattern )是一個使用率非常高的模式,其定義如下:

Provide a surrogate or placeholder for another object to control access to it.(為其他對象提供一種代理以控制對這個對象的訪問。)

代理模式的通用類圖為

其中的三個角色為

  • Subject 抽象角色:抽象主題類可以是抽象類也可以是接口,是一個最普通的業務類型定義
  • RealSubject 具體角色:也叫做被委托角色、被代理角色,是業務邏輯的具體執行者
  • Proxy 代理角色:也叫做委托類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制 委托給真實主題角色實現,並且在真實主題角色處理完畢前后做預處理和善后處理工作

1.2 租房例子

這里用租房的例子說明一下:租房本來有房東和客戶兩個對象,但房東不想自己去出租房子(或客戶找不到房東租房),就需要一個第三方房屋中介(出租代理)。

出租房子的類圖為

用代碼實現一下,首先有一個出租接口,是具體的業務

// 出租接口
// Subject:抽象角色,業務定義
public interface Rent {
    void rent();
}

要出租房子的房東就要實現這個接口

// 房東角色,要出租房子
// RealSubject:業務的具體執行者
public class Host implements Rent{
    public void rent() {
        System.out.println("房東的房子租出去了!");
    }
}

這時候客戶已經可以找房東租房了

// 我就是客戶!
public class Client {
    public static void main(String[] args){
        Host host = new Host();
        host.rent();
    }
}
// 執行結果
// 房東的房子租出去了!

不過現在房東不想自己去出租房子(或客戶找不到房東租房),就需要一個引入出租代理

// 出租代理,租房找他
// Proxy:代理角色,負責對真實角色的應用
public class RentProxy implements Rent{
    // 要代理的對象
    private Host host;

    public RentProxy(Host host) {
        this.host = host;
    }

    public void rent() {
        // 幫房東出租房子
        host.rent();
    }
}

這時候客戶要想租房,就可以去找代理了

// 我就是客戶!
public class Client {
    public static void main(String[] args){
        // 普通代理要求不能 new 真實對象,所以這里不算
        Host host = new Host();
        // 房東把房子交給代理了
        RentProxy rentProxy = new RentProxy(host);
        // 我們找代理租房就好了
        rentProxy.rent();
    }
}
// 執行結果
// 房東的房子租出去了!

在這里代理的作用就是,房東把自己的房子交給代理( +Host ),客戶租房直接去找代理就行了。

這樣看來代理的作用好像不大,不過上面提到

Proxy 代理角色:也叫做委托類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制 委托給真實主題角色實現,並且在真實主題角色處理完畢前后做預處理和善后處理工作

沒錯,代理可以在業務執行前進行預處理,或者業務完成后進行善后。

讓代理來點作用,出租前先帶客戶看看房,客戶如果要租房就簽合同,代理也要收錢

// 出租代理,租房找他
// Proxy:代理角色,負責對真實角色的應用
public class RentProxy implements Rent{
    // 要代理的對象
    private Host host;

    public RentProxy(Host host) {
        this.host = host;
    }

    public void rent() {
        // 客戶找代理租房,先帶去看房
        seeHouse();
        // 客戶覺得可以,簽合同
        getContract();
        // 幫房東出租房子
        host.rent();
        // 租完房子要收錢的
        getCost();
    }

    // 預處理 before
    public void seeHouse(){
        System.out.println("中介帶客戶看房!");
    }

    // 應該算預處理
    public void getContract(){
        System.out.println("確認要租,簽合同了!");
    }

    // 善后 after
    public void getCost(){
        System.out.println("出租完成,收米!");
    }
}

這里想到了一個問題:客戶找代理租房,是客戶跟代理說 “你帶我去看房”,“你跟我簽合同”,“你收我錢”;還是代理跟客戶說 “要租房?先去看房”,“可以就簽合同吧”,“把錢付一下” 呢?顯然應該是后者。

這里的區別就體現在,Client 中要主動調用 seeHouse、getContract、getCost 這種預處理和善后工作嗎?顯然不應該。這種工作應該讓代理去負責,客戶找到代理租房,代理直接一條龍服務!對應的就是在代理的 rent 方法中進行預處理和善后。

這時客戶去找代理租房

// 我就是客戶!
public class Client {
    public static void main(String[] args){
        // 普通代理要求不能 new 真實對象,所以這里不算
        Host host = new Host();
        // 房東把房子交給代理了
        RentProxy rentProxy = new RentProxy(host);
        // 我們找代理租房,簡單的租房操作其實背后有一條龍服務!
        rentProxy.rent();
    }
}
// 執行結果
/*
    中介帶客戶看房!
    確認要租,簽合同了!
    房東的房子租出去了!
    出租完成,收米!
*/

房東只管出租他的房子,客戶只需要找代理租房,煩人的事情都讓代理干完了,減輕了客戶和房東的負擔,這就是代理模式。

1.3 用戶業務例子

這里再用一個之前項目中的 UserService 例子加深對代理模式的理解。

首先有一個 UserService 接口,它是業務層的接口,對應 Dao 層的對數據庫的操作

// Subject 抽象角色
public interface UserService {
    // 對應 Dao 層的增刪改查!
    public void add();
    public void delete();
    public void update();
    public void query();
}

然后是接口的具體實現類,之前在實現類中進行了對 Dao 層的調用,這里簡化一下(億下)

// RealSubject 真實對象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加用戶!");
    }

    public void delete() {
        System.out.println("刪除用戶!");
    }

    public void update() {
        System.out.println("修改用戶!");
    }

    public void query() {
        System.out.println("查詢用戶!");
    }
}

好了,現在的要求是,要在執行某個操作時輸出日志信息,那要怎么辦呢?

如果在 UserServiceImpl 類上修改,就是

// RealSubject 真實對象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("[Debug]:進行了Add操作");
        System.out.println("增加用戶!");
    }
    // 同上
    ...
}

這樣好像完成了要求。但此時的 UserServiceImpl 類承擔了不應該由它來做的事情,一個業務實現類,為什么要輸出日志啊?這就增加了代碼的耦合性。如果要添加的奇奇怪怪的功能更多,這個業務實現類中關鍵的業務實現部分就更加不起眼了。

所以這時候就需要代理了!增加 UserServiceProxy 類,輸出日志這種事情,屬於代理的預處理或善后工作

// Proxy 角色
public class UserServiceProxy implements UserService{
    // 要代理的對象!
    private UserService userService;

    // 不要直接 new 對象!添加 set 方法讓 Spring 注入!連起來了!
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void add() {
        // 就當是 before 吧!
        printLog("[Debug]:進行了Add操作");
        userService.add();
    }

    public void delete() {
        printLog("[Debug]:進行了Delete操作");
        userService.delete();
    }

    public void update() {
        printLog("[Debug]:進行了Update操作");
        userService.update();
    }

    public void query() {
        printLog("[Debug]:進行了Query操作");
        userService.query();
    }

    public void printLog(String msg){
        System.out.println(msg);
    }
}

讓代理來輸出日志,保證了 UserServiceImpl 類的職責清晰!假設用到了增加用戶的方法

// 客戶端!
public class Client {
    public static void main(String[] args){
        // 這三步應該讓 Spring 來干!不過不麻煩了
        UserService userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);
        // 增加用戶
        proxy.add();
    }
}
// 執行結果
// [Debug]:進行了Add操作
// 增加用戶!

可以看到通過調用代理來執行業務,成功輸出了日志。而如果有其他的要求,也可以通過擴展代理類去實現!

這大概就是面向切面編程( AOP )的思想吧!

擴展:差點和裝飾模式搞混了!在裝飾模式中,裝飾類的作用就是一個特殊的代理類。

1.4 靜態代理小結

代理模式的優點

  • 職責清晰:真實角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過后期的代理完成一件事務,附帶的結果就是編程簡潔清晰。

    在租房例子中體現在房東只管如何出租他房子,不用管其他亂七八糟的事情!

  • 高擴展性:具體角色是隨時都會發生變化的,只要它實現了接口,無論它如何變化,代理類完全就可以在不做任何修改的情況下使用。

    租房例子中的房東只要實現了出租房子接口,無論他想出租公寓還是別墅,代理都能幫他出租!

  • 智能化:還沒有體現出來,這就要看動態代理了。

代理模式的缺點

  • 這種代理其實是靜態代理,問題就是每有一個真實角色,就需要一個對應的代理。當真實角色很多的時候,代碼量就非常龐大了。這個問題就需要動態代理解決。

代理模式又可以擴展為普通代理強制代理,這里先不仔細研究了,到時候繼續設計模式之禪。

2. 動態代理

什么是動態代理?

動態代理就是,在程序運行期間創建目標對象的代理對象,並對目標對象中的方法進行功能性增強的一種技術。在生成代理對象的過程中,目標對象不變,代理對象中的方法是目標對象方法的增強方法。可以理解為運行期間,對象中方法的動態攔截,在攔截方法的前后執行功能操作。

動態代理的原理比較復雜,涉及到反射和 JVM 的知識(我都不會!),所以只能先照葫蘆畫瓢先用起來。

動態代理實現的方式有兩種:JDK 和 CGLib,這里使用 JDK 實現。

2.1 代理類模板

要使用動態代理,就要用到 JDK 提供的 Proxy 類和 InvocationHandler 接口

  • Proxy 類:用於生成被代理接口的代理類對象!
  • InvocationHandler 接口:只有一個方法 invoke 要實現,就是這個方法去調用被代理接口的方法!

首先創建 ProxyHandler 類,這個類就是動態代理類,用它來動態地創建某個接口(實現該接口的對象)的代理類,所以要有一個 target 屬性接收要被代理的接口

// 用它來動態生成代理類!
public class ProxyHandler {
    // 被代理的接口
    private Object target;

	// 用注入的方式!
    public void setTarget(Object target) {
        this.target = target;
    }
}

然后要實現調用處理器 InvocationHandler 接口,讓這個類具有調用被代理接口的方法的能力(通過反射)

// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
    // 被代理的接口
    private Object target;

    // 用注入的方式!
    public void setTarget(Object target) {
        this.target = target;
    }

    // 處理代理實例,調用被代理對象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在這里調用了被代理對象的方法!
        // 就是這里進行 面向切面編程!
        Object result = method.invoke(target, args);
        return result;
    }
}

最后要通過 Proxy 類,獲取被代理接口的代理類對象!

// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
    // 被代理的接口
    private Object target;

    // 用注入的方式!
    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成代理類
    public Object getProxy(){
        // 用代理類,創建代理實例
        // 參數為 類加載器ClassLoader loader
        //      被代理的接口 Class<?>[] interfaces
        //      調用處理器對象 InvocationHandler h
        // 這個類就實現了調用處理器接口,所以傳自己!
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    // 處理代理實例,調用被代理對象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在這里調用了被代理對象的方法!
        // 就是這里進行 面向切面編程!
        Object result = method.invoke(target, args);
        return result;
    }
}

這就是一個動態代理類的模板了,下面來使用一下。

2.2 租房動態代理

把租房例子用動態代理改寫一下!

首先,租房接口沒有變,房東也還是那個房東

public interface Rent {
    void rent();
}
public class Host implements Rent{
    public void rent() {
        System.out.println("房東的房子租出去了!");
    }
}

現在,沒有手動寫的代理類 RentProxy 了,要通過動態代理去獲取

public class Client {
    @Test
    public void rentTest(){
        // 真實角色
        Rent host = new Host();
        // 代理角色,不存在!怎么辦?找動態代理類要!
        ProxyHandler ph = new ProxyHandler();
        // 傳入要被代理的對象
        ph.setTarget(host);
        // 動態生成對應的代理類!
        Rent proxy = (Rent) ph.getProxy();
        // 使用被代理對象的方法
        proxy.rent();
    }
}
// 執行結果
// 房東的房子租出去了!

我們並沒有去寫租房的代理類 RentProxy,也成功實現了代理!

不過這里沒有擴展的功能,要想進行預處理和善后工作,就要找到調用了被代理對象方法的方法——invoke方法,在調用前后增加新操作

// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
	...

    // 處理代理實例,調用被代理對象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在這里調用了被代理對象的方法!
        seeHouse();
        getContract();
        Object result = method.invoke(target, args);
        getCost();
        return result;
    }

    // 預處理 before
    public void seeHouse(){
        System.out.println("中介帶客戶看房!");
    }

    // 應該算預處理
    public void getContract(){
        System.out.println("確認要租,簽合同了!");
    }

    // 善后 after
    public void getCost(){
        System.out.println("出租完成,收米!");
    }
}

這時候客戶再去找代理租房,結果就和之前一樣了

// Client 同上
// 執行結果
/*
    中介帶客戶看房!
    確認要租,簽合同了!
    房東的房子租出去了!
    出租完成,收米!
*/

2.3 用戶業務動態代理

再把用戶業務的例子用動態代理改寫一下,要求還是加個日志!

UserService 接口和 UserServiceImpl 實現類不變,這里省略。

在動態代理類中增加擴展的日志方法,並且在 invoke 方法中使用

// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
	...
        
    // 處理代理實例,調用被代理對象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在這里調用了被代理對象的方法!
        // 通過反射獲取調用的方法的名字!
        printLog(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }

    // 擴展功能,輸出日志
    public void printLog(String msg){
        System.out.println("[Debug]:進行了"+msg+"操作");
    }
}

在客戶端中通過動態代理調用 add 方法

public class Client {
    @Test
    public void userServiceTest(){
        // 真實角色
        UserService userService = new UserServiceImpl();
        // 找動態代理類
        ProxyHandler ph = new ProxyHandler();
        // 傳入要被代理的對象
        ph.setTarget(userService);
        // 動態生成對應的代理類!
        UserService proxy = (UserService) ph.getProxy();
        // 使用被代理對象的方法
        proxy.add();
        proxy.delete();
    }
}
// 執行結果
/*
    [Debug]:進行了add操作
    增加用戶!
    [Debug]:進行了delete操作
    刪除用戶!
*/

沒有寫 UserServiceProxy 類,也進行了代理操作!在 invoke 方法中,還通過 method 的 getName 方法獲取了外部調用方法的名字( 原理還是反射😥),更加簡潔了!

2.4 動態代理小結

動態代理的入口是 Proxy 類,通過其中的 newProxyInstance 方法獲取被代理接口的代理類,參數為

  • ClassLoader loader:用哪個類加載器去加載代理對象(),通過 Class 對象中的 getClassLoader 方法獲取
  • Class<?>[] interfaces:被代理的接口,通過 Class 對象的 getInterfaces 方法獲取,可以獲取到 Class 對象實現的所有接口
  • InvocationHandler h:調用處理器對象,調用被代理接口中的方法時,會通過其中的 invoke 方法去執行;修改 invoke 方法就相當於修改了代理類

動態代理的橋梁是 InvocationHandler 接口,通過其中的 invoke 方法調用被代理接口中的方法,參數為

  • Object proxy:具體的代理類對象,其中有被代理接口的方法!
  • Method method:被調用的方法
  • Object[] args:被調用方法的參數

動態代理通過 Proxy 類創建具體的代理類,代理類又通過 InvocationHandler 接口中的 invoke 方法完成對被代理接口中的方法的調用。

3. 總結

代理模式:由最簡單的靜態代理,可以擴展為普通代理和強制代理(這里沒深入),然后是最強大的動態代理!

靜態代理好是好,但要寫代理類太麻煩!所以出現了動態代理,動態代理的原理有點深奧,類加載器 Java 虛擬機啥啥的,后面再研究吧!

不過現在還不知道動態代理除了添加日志外還有什么應用場景···就像上面的租房和用戶業務,不還得對應不同的動態代理類的操作嘛?

麻了,后面再說吧😥!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM