Java動態代理與靜態代理以及它能為我們做什麼

XiaoH在部落格園發表於2020-05-31

  相信我們在網上和平時學習和工作中或多或少都接觸過Java的代理模式,經常聽到什麼靜態代理、動態代理的一些名詞。但我們是否真的很清楚這些呢?至少我在面試時,發現很多人並不很清楚。

  首先代理比較好理解,就是幫一個人,或者一類人做一些事情。遷移到物件導向的程式設計中,代理就是幫一個類去做一些事情,而這個代理的工具我們就稱為代理類。

  通過代理的方式去做事有什麼好處呢?這就好比工廠和分銷商做的事情一樣,工廠可以直賣一些自己的產品,分銷商同樣也可以賣工廠生產的產品,那麼為什麼還有分銷商的存在呢?因為分銷商可以提供一些額外的服務,或者在銷售的過程中能夠完成一些其他的事情,比如組合銷售、根據本地情況做活動等,而這些可能是工廠不想關心或者也管不過來的。這樣的功能和角色承包給代理商就會使得分工比較明晰,並且又能夠提供一些額外或者定製的服務。

 

靜態代理

  Java中的代理方式可以分為靜態代理和動態代理。靜態代理的含義是代理類/物件在我們關心的程式執行前就已經確定或存在。靜態代理比較好理解,我們在日常工作中也是經常用到,比如一個已經存在的介面,我們不期望去更改它,但是現在要在原邏輯上新加一些邏輯或功能,比如原介面方法呼叫完成後傳送一個訊息之類的。於是我們可以建立一個類,同樣實現原介面,並且把之前存在的介面當做成員變數注入進來,呼叫其中的方法,並新增我們需要的功能。

  靜態代理的類圖如下所示,需要被代理的實現類和代理類都實現了抽象介面AbstractInterface,而InterfaceProxy和InterfaceImpl間是聚合關係。

  

 

 

 

   來看一段示例程式碼,ProductAuditCallbackService 是我們已有的一個介面,出於某些原因,這個介面不能繼續對外使用,我們需要定義一個新的介面並且名稱還要一樣(主要是方便客戶理解和對應原介面),但是我們需要新增一點“新邏輯”。因此我們可以同樣實現 ProductAuditCallbackService,ProductAuditCallbackServiceProxy 就是我們的代理類,之後外部呼叫就可以例項化我們的代理類,呼叫同名方法就好了。

 

 1 public class ProductAuditCallbackServiceProxy implements ProductAuditCallbackService {
 2 
 3     @Resource
 4     private ProductAuditCallbackService productAuditCallbackService;
 5 
 6     @Override
 7     public Result<Void> auditProduct(ProductAuditRequest request, String auditStatus) {
 8         if (auditStatus == "DELETED") {
 9             return new Result<>();
10         }
11         return productAuditCallbackService.auditProduct(request, auditStatus);
12     }
13 
14 
15 ...
16 }

 

 

 

 

動態代理

  動態代理的作用和靜態代理一樣,主要的區別就在於需要在執行時生成代理類。在使用動態代理時,我們還需要定義一個在代理類和委託類之間的中介類,並且中介類需要實現 java.lang.reflect.InvocationHandler 介面。

 1 package java.lang.reflect;
 2 
 3 /**
 4  * {@code InvocationHandler} is the interface implemented by
 5  * the <i>invocation handler</i> of a proxy instance.
 6  *
 7  * <p>Each proxy instance has an associated invocation handler.
 8  * When a method is invoked on a proxy instance, the method
 9  * invocation is encoded and dispatched to the {@code invoke}
10  * method of its invocation handler.
11  *
12  * @author      Peter Jones
13  * @see         Proxy
14  * @since       1.3
15  */
16 public interface InvocationHandler {
17 
18     public Object invoke(Object proxy, Method method, Object[] args)
19         throws Throwable;
20 }

  

  動態代理在框架類的程式碼中用到的頻率並不低,而且能夠使我們的程式碼看起來更高階一些,所以何樂而不為呢? 讓我們來看一些實際的例子。

  MethodInvocationHandler是一箇中介類,實現了InvocationHandler介面,MethodMonitor 這個類的功能就是要統計我們的委託類的物件business中的方法被呼叫的次數和耗時,由於其主要功能不是我們關注的主要內容,所以忽略其實現。

 1 public class MethodInvocationHandler implements InvocationHandler {
 2 
 3     //被代理物件
 4     private Object business;
 5 
 6     private final MethodMonitor methodMonitor;
 7 
 8     public MethodInvocationHandler(MethodMonitor methodMonitor) {
 9         this.methodMonitor = methodMonitor;
10     }
11 
12     /**
13      * 代理方法
14      */
15     @Override
16     public Object invoke(Object proxy, Method method, Object[] args)
17             throws Throwable {
18 
19         long startTime = System.currentTimeMillis();
20 
21         Object result = method.invoke(this.business, args);
22 
23         //方法呼叫統計
24         this.methodMonitor.methodCount(this.business.getClass().getSimpleName() + POINT + method.getName(), startTime);
25         return result;
26     }
27 
28 }

  其餘示例程式碼及外部呼叫示例如下,我們的Business類裡面擁有三個方法。MethodSampleClient 則是一個封裝起來的客戶端。我們不想讓外部客戶端感知我們的實現以及和Business的關係,於是我們在MethodSampleClient中定義了一個成員變數proxy,當外部需要Business提供的一些功能時,我們通過proxy為其提供。Proxy.newProxyInstance() 則是我們例項化一個代理類的方式,喲,這還是個工廠模式,可以閱讀一些這個方法的說明,需要傳入的三個引數依次是:需要被代理的類的ClassLoader,被代理類需要被代理的介面的集合,中介處理類的例項。

  這裡Business我寫的是一個確定的類,其實真正在實際開發工作中,我們往往定義的抽象的介面或抽象類,知道執行時才會確定到底是哪個實現類的例項,這樣可能更容易理解一些:執行時確定委託類的實現類,執行時生成代理類,並呼叫對應的委託類的方法。

 

 1 public class Business {
 2 
 3     public void createJob() {
 4         System.out.println("test createJob");
 5     }
 6 
 7 
 8     public void processJob() {
 9         System.out.println("test processJob");
10     }
11 
12     public void closeJob() {
13         System.out.println("test closeJob");
14     }
15 
16 }
17 
18 
19 
20 public class MethodSampleClient {
21 
22     private Business business;
23 
24     @Getter
25     private Object proxy;
26 
27     private InvocationHandler invocationHandler;
28 
29 
30     public void init() {
31         this.business = new Business();
32         this.invocationHandler = new MethodInvocationHandler(new MethodMonitor());
33         this.proxy = bind(this.business, invocationHandler);
34     }
35 
36     /**
37      * 繫結物件, 直接初始化並返回代理類供客戶端使用
38      */
39     public Object bind(Object business, InvocationHandler invocationHandler) {
40         return Proxy.newProxyInstance(
41                 //被代理類的ClassLoader
42                 business.getClass().getClassLoader(),
43                 //要被代理的介面,本方法返回物件會自動聲稱實現了這些介面
44                 business.getClass().getInterfaces(),
45                 //代理處理器物件
46                 invocationHandler);
47     }
48     
49 }    
50 
51 
52 /**
53 *  A simple client test class
54 */
55 public class Test {
56 
57     public void main(String[] args) {
58         MethodSampleClient methodSampleClient = new MethodSampleClient();
59         methodSampleClient.init();
60 
61         methodSampleClient.getProxy().createJob();
62         methodSampleClient.getProxy().processJob();
63         methodSampleClient.getProxy().closeJob();
64     }
65 
66 }

   

  為了說清楚這個過程,竟然還真的寫了不少程式碼,看起來比較繁瑣。總結一下,動態代理無非按照下面的步驟來編寫程式碼:

  • 首先明確需要被代理的委託類。
  • 實現 InvocationHandler 介面,定義一箇中介類。
  • 用 Proxy.newProxyInstance() 例項化代理類,並在客戶端程式碼中直接使用。

  好了,大概差不多了,最重要的是能夠在實際工作中有意識地去使用並體會其作用 —— 軟體開發是經驗驅動不是知識驅動。

 

 

 

 

  

 

相關文章