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

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

1. 前言

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

2. 代理模式詳解

2.1 定義

爲另外一個對象提供一個替身或佔位符以控制對這個對象的訪問
定義簡單明瞭。但仍是有些沒有解釋清楚的地方,控制對對象的訪問是爲了幹什麼?實際上是爲了對真實的業務方法做某種形式的加強,好比在業務方法調用前做前置處理,在方法調用後做後置處理,而這些對客戶端都是透明的。java

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");
    }
}

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

  • 日誌代理
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自帶的代理方式作說明。this

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執行業務邏輯的先後,能夠對其做前置和後置加強。spa

  • 客戶端代碼
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的底層實現。

相關文章
相關標籤/搜索