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


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

1. 前言

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

2. 代理模式詳解

2.1 定義

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

2.2 普通代理模式類結構

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

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

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

  • 業務接口
public interface IService {
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">service1</span><span class="hljs-params">()</span></span>;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">service2</span><span class="hljs-params">()</span></span>;

}

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

  • 被代理類
public class RealService implements IService {
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service1</span><span class="hljs-params">()</span> </span>{
    System.out.println(<span class="hljs-string">"service1"</span>);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service2</span><span class="hljs-params">()</span> </span>{
    System.out.println(<span class="hljs-string">"service2"</span>);
}

}

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

  • 日志代理
public class StaticLogProxy implements IService {
<span class="hljs-keyword">private</span> IService iService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">StaticLogProxy</span><span class="hljs-params">(IService iService)</span> </span>{
    <span class="hljs-keyword">this</span>.iService = iService;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service1</span><span class="hljs-params">()</span> </span>{
    System.out.println(<span class="hljs-string">"service1 start!"</span>);
    iService.service1();
    System.out.println(<span class="hljs-string">"service1 end!"</span>);

}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service2</span><span class="hljs-params">()</span> </span>{
    System.out.println(<span class="hljs-string">"service2 start!"</span>);
    iService.service2();
    System.out.println(<span class="hljs-string">"service2 end!"</span>);
}

}

  • 運行結果

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

3. JDK動態代理詳解

3.1 JDK動態代理實現

  • 方法調用處理器
public class LogHandler implements InvocationHandler {
<span class="hljs-comment">//被代理對象</span>
<span class="hljs-keyword">private</span> Object target;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">LogHandler</span><span class="hljs-params">(Object target)</span> </span>{
    <span class="hljs-keyword">this</span>.target = target;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable </span>{
    System.out.println(method.getName() + <span class="hljs-string">" start!"</span>);<span class="hljs-comment">//前置處理</span>
    Object res = method.invoke(<span class="hljs-keyword">this</span>.target, args);<span class="hljs-comment">//執行業務方法</span>
    System.out.println(method.getName() + <span class="hljs-string">" end!"</span>);<span class="hljs-comment">//后置處理</span>
    <span class="hljs-keyword">return</span> res;
}

}

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

  • 客戶端代碼
public class Client {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(<span class="hljs-params">String[] args</span>) </span>{

    <span class="hljs-comment">//1.創建被代理對象</span>
    RealService realService = <span class="hljs-keyword">new</span> RealService();

    <span class="hljs-comment">//2.創建動態代理的方法調用處理器</span>
    LogHandler logHandler = <span class="hljs-keyword">new</span> LogHandler(realService);

    <span class="hljs-comment">//3.創建動態代理對象</span>
    IService service=(IService)Proxy.newProxyInstance(logHandler.getClass().getClassLoader(),
            realService.getClass().getInterfaces(),logHandler);

    service.service1();
    System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"---------------"</span>);
    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的底層實現。


免責聲明!

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



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