相信我們在網上和平時學習和工作中或多或少都接觸過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() 例項化代理類,並在客戶端程式碼中直接使用。
好了,大概差不多了,最重要的是能夠在實際工作中有意識地去使用並體會其作用 —— 軟體開發是經驗驅動不是知識驅動。