1. 程式人生 > >代理模式詳解(靜態代理和動態代理的區別以及聯系)

代理模式詳解(靜態代理和動態代理的區別以及聯系)

sys 事務處理 getname 沒有 面向接口編程 簡單 關註 不知道 正是

原文鏈接:https://www.cnblogs.com/takumicx/p/9285230.html

1. 前言

代理模式可以說是生活中處處可見。比如說在攜程上定火車票,攜程在這裏就起到了一個代理的作用,比起我們在官網上或者直接去櫃臺訂票,攜程可以為用戶提供更多人性化的選擇。再比如代購,我自己的mbp就是委托別人從香港買回來的,那麽那個代購人就相當於代理,免去了我來回的車費以及辦簽證的麻煩。直觀的理解,代理是這麽一個對象,我們可以把工作委托給它,由它幫我們去執行工作同時解決工作相關的各種麻煩,最後把工作成果交給我們。這樣一來我們只需要關註問題的核心,並告知代理,而不需要為其他瑣碎的細節操心,這正是代理模式最大的好處,讓客戶端專註於真正的事務處理。具體而言,代理模式分為靜態代理和動態代理,它們的設計思想類似,實現卻大相徑庭。它們的實現方式以及它們的區別是面試時經常會被問到的。下面我們就來詳細介紹它們。

2. 代理模式詳解

2.1 定義

為另一個對象提供一個替身或占位符以控制對這個對象的訪問
定義簡單明了。但還是有些沒有解釋清楚的地方,控制對對象的訪問是為了幹什麽?其實是為了對真實的業務方法作某種形式的增強,比如在業務方法調用前作前置處理,在方法調用後作後置處理,而這些對客戶端都是透明的。

2.2 普通代理模式類結構

技術分享圖片

被代理類和代理類實現同一接口,根據面向接口編程原則,可以使用被代理類的地方,統統可以用代理類代替,同時類內部持有被代理類的引用,真正的業務處理邏輯可以交給被代理類去作。

2.3 普通代理模式的實現(靜態代理)

假設我要寫一個日誌代理,在真實的業務方法調用前後各記一條日誌,看看靜態代理是怎麽做的。

  • 業務接口
public interface IService {

    void service1();

    void service2();
}

接口含有兩個抽象業務方法

  • 被代理類
public class RealService implements IService {

    @Override
    public void service1() {
        System.out.println("service1");
    }

    @Override
    public void service2() {
        System.out.println("service2"
); } }

被代理類實現業務接口,並且重寫了業務方法。

  • 日誌代理
public class StaticLogProxy implements IService {

    private IService iService;

    public StaticLogProxy(IService iService) {
        this.iService = iService;
    }

    @Override
    public void service1() {
        System.out.println("service1 start!");
        iService.service1();
        System.out.println("service1 end!");

    }

    @Override
    public void service2() {
        System.out.println("service2 start!");
        iService.service2();
        System.out.println("service2 end!");
    }

}
  • 運行結果
    技術分享圖片

如上圖所示,在業務方法執行前後分別記了一條日誌,表示業務方法執行的開始和結束。我們的代理類成功對原有業務方法做了增強。但是靜態代理存在以下問題:
1.代理類和被代理類耦合,適用性差。試想如果我希望為所有的業務類添加日誌增強邏輯,那麽豈不是要為幾乎每個業務類編寫代理類?這是不現實也是開發時無法接受的。
2.代理類的增強邏輯和業務邏輯過於耦合,不利於後期維護和擴展。從service1和service2中就可以看出,除了中間的業務處理不一樣,代理類的處理邏輯是一樣的,而我們竟然沒有將其分離。
以上兩點其實反映的是同一個問題:我們希望自己編寫的代理類對所有業務類,所有業務方法都適用,而靜態代理的泛用性太差了。問題的關鍵在於編寫代理邏輯和業務邏輯分離的代理類,運行時才將其和具體的業務類綁定,對其業務方法做增強。為此,我們需要動態代理。動態代理的實現方式很多,下面以jdk自帶的代理方式做說明。

3. JDK動態代理詳解

3.1 JDK動態代理實現

  • 方法調用處理器
public class LogHandler implements InvocationHandler {

    //被代理對象
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + " start!");//前置處理
        Object res = method.invoke(this.target, args);//執行業務方法
        System.out.println(method.getName() + " end!");//後置處理
        return res;
    }
}

每一個代理對象都有一個與之關聯的方法調用處理器,該處理器實現了InvocationHandler接口並重寫了invoke方法。當我們調用代理對象的方法的時候,對該方法的調用會轉交給方法調用處理器的invoke方法來執行,所以方法調用處理器的invoke方法是動態代理的核心。該方法內是通用的代理邏輯。在我們通過反射的方式通過被代理對象target執行業務邏輯的前後,可以對其作前置和後置增強。

  • 客戶端代碼
public class Client {


    public static void main(String[] args) {

        //1.創建被代理對象
        RealService realService = new RealService();

        //2.創建動態代理的方法調用處理器
        LogHandler logHandler = new LogHandler(realService);

        //3.創建動態代理對象
        IService service=(IService)Proxy.newProxyInstance(logHandler.getClass().getClassLoader(),
                realService.getClass().getInterfaces(),logHandler);

        service.service1();
        System.out.println("---------------");
        service.service2();

    }
}

第一步我們創建了被代理對象realService;第二步我們創建了動態代理的核心:方法調用處理器。因為處理器內部需要委托被代理對象去執行真正的業務方法,所以需要傳入被代理對象作參數。第三步我們通過調用反射包下的Proxy類的靜態方法去生成真正的代理對象。該方法的方法簽名如下

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

該方法含有三個參數
ClassLoader loader:指定加載的代理類的類加載器
Class<?>[] interfaces:指定代理類要實現的接口
InvocationHandler h:指定方法調用處理器
關於第二個參數可能要說明下,因為jdk的動態代理是針對接口的動態代理,代理類需要實現指定的業務接口,這裏就是被代理類實現的那些接口。這樣就能充分利用java多態特性,代碼中所有能使用被代理對象的地方都能用代理對象進行替換。

  • 運行結果
    技術分享圖片

和靜態代理的運行結果一樣。如果我們要對其他業務類使用日誌代理,只需要修改下客戶端代碼就行,這是靜態代理辦不到的。具有良好的擴展性是動態代理相比靜態代理最大的優勢。

3.2 方法調用流程圖

技術分享圖片

  • 1.客戶端調用代理對象的業務方法,創建代理對象的時候傳入了接口數組參數,故而代理對象也實現了業務接口。
  • 2.代理對象將請求轉發給方法調用處理器的invoke方法
  • 3.方法調用處理器在invoke方法內部通過反射的方式調用被代理對象的業務方法

3.3 動態代理和靜態代理的區別

  • 靜態代理編譯期生成代理類;動態代理運行期生成代理類。
  • 靜態代理和被代理類及其業務邏輯耦合,適用性較差且代理邏輯難以擴展;動態代理可以在不知道被代理類的前提下編寫代理邏輯,運行時才決定被代理對象,適用性好且代理邏輯易於擴展。

3.4 其他實現動態代理的方式

  • cglib面向類的動態代理
  • javaassist字節碼操作庫實現
  • asm

4. 總結

代理用以控制對對象的訪問,本質上是對其功能提供某種形式的增強。按實現又可分為靜態代理和動態代理。動態代理因其代理邏輯和業務邏輯相分離的特點,具有良好的適用性和可擴展性,是Spring中AOP的底層實現。

代理模式詳解(靜態代理和動態代理的區別以及聯系)