Java 动态代理解析
引言
本博客總結自《Java 編程思想》第十四章
一、實現第一個動態代理程序
代理是軟件設計中重要的設計思想,它允許我們在調用實際操作之前或之后解耦式地編寫額外的操作,而一旦不需要這些操作了,就可以輕易的移除它們。
瀏覽了《編程思想》中對動態代理的解釋,我發現動態代理的實現也是非常簡單的。
想要實現動態代理,除了要借助于 Java 的運行時類型信息( RTTI :Run-Time Type Identification),即 Class 對象,還需要反射包(java.lang.reflect.*)下的一些 API? 的幫助。
1.1 完成原始調用
動態代理的本質還是代理,其最根本的目的就是充當中介者的角色,將請求轉發,并在轉發的過程中添加操作。因此,我們還是需要先按部就班地完成最原始的調用。
以最常見的 web 調用為例,我們寫一個 Service 接口,并實現它。再寫一個 Controller 來進行接口的調用。
RequestService 接口:
public interface RequestService {public void processRequest(Object request); }RequestServiceImpl 接口實現:
public class RequestServiceImpl implements RequestService {@Overridepublic void processRequest(Object request) {System.out.println("執行請求處理邏輯...");System.out.println("請求對象:" + request);System.out.println("處理請求完成!");} }MyController :
public class MyController {private RequestService service;// 模擬依賴注入public MyController(RequestService service) {this.service = service;}// 模擬@RequestMapping接口方法public void receiveRequest(String httpRequest) {service.processRequest(httpRequest);} }然后我們寫一個 main 方法,來模擬瀏覽器的調用,直接向 MyController 傳遞一個請求:
public class GoogleBrowser {// 模擬瀏覽器的調用public static void main(String[] args) {// 初始化service組件RequestService service = new RequestServiceImpl();// 初始化controller組件MyController clr = new MyController(service);// 向controller發送請求clr.receiveRequest("Morty");} }上面幾段代碼是最最簡單的 web 調用的層級關系,Controller 調用 Service 的方法完成業務邏輯。
執行結果:
1.2 實現調用處理器
實現動態代理的關鍵是要自定義一個代理類,也即調用處理器,它必須實現一個叫作 InvocationHandler 的接口。
public class MyDynamicProxy implements InvocationHandler {private Object proxied;public MyDynamicProxy(Object proxied) {this.proxied = proxied;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);return method.invoke(proxied, args);} }invoke 方法接收三個參數,第一個 proxy 是代理對象本身,一般我們不需要關心,第二個和第三個參數是請求轉發的必須參數。我們在調用一個實例方法的時候,必須要知道方法的簽名和實例對象,方法的簽名又包括方法名和參數列表。method 參數就是接口中的抽象方法映射,而 args 就是方法所需的參數列表,而 proxied 就是能夠調用方法的實際對象。
1.3 獲得動態代理對象
這是動態代理調用的最關鍵一步,我們雖然定義好了一個動態代理類,但是通常情況下,我們并不是直接通過 new 關鍵字來創建動態代理對象,而是通過一個靜態方法。改造后的 main 如下所示:
public class GoogleBrowser {// 模擬瀏覽器的調用public static void main(String[] args) {RequestService service = new RequestServiceImpl();// 獲取動態代理對象RequestService serviceProxy = (RequestService) Proxy.newProxyInstance(RequestService.class.getClassLoader(),new Class[] { RequestService.class }, new MyDynamicProxy(service));MyController clr = new MyController(serviceProxy);// 調用 controller 接口clr.receiveRequest("Morty");} }執行結果:
二、Proxy.newProxyInstance(...) 解析
這個靜態方法會返回一個指定接口的代理類實例,這個代理類實例可以將方法調用轉發給指定的調用處理器。
該靜態方法需要傳遞三個參數:
1、ClassLoader loader :通常可以直接將被代理接口 Class 對象的類加載器傳遞進來。
2、Class<?>[] interfaces :一個你希望代理對象實現的接口列表(注意不是類或抽象類)。
3、InvocationHandler h :InvocationHandler 接口的一個實現對象(就是剛剛定義的調用處理器對象)。
通過該方法生成的代理對象,實際上也是接口的子類對象,在調用者只有接口引用的情況下,代理對象將自動接管發送給接口的請求,并在處理后轉發給被代理對象。
三、對動態代理的理解
代理其實并不復雜,它是基于對接口調用的一種內部處理技巧。
代理類必須要偽裝成被代理接口的子類,并封裝被代理對象,才能騙過調用者的眼睛。
從編碼的過程不難看出,動態代理類本身就是一個調用處理器,也就是說,動態代理的本質就是請求轉發。由于使用了接口引用調用這種動態綁定的方式(多態),因此在發生真正調用行為之前,JVM 會進行類型檢查,當發現接口引用指向的對象同時也是一個 InvocationHandler 接口的子類時,就明白了一切,從而自動調用 invoke() 方法,然后將必要的反射信息傳遞進去。這樣就完成了運行時動態的請求處理。
動態代理的工作過程可以這樣來描述:
在 controller 中,只有一個 RequestService 接口的引用。
當 controller 調用接口中的?processRequest() 方法時,JVM 會通過類型檢查檢測到 service 對象真正的類型是(通過 Proxy.newProxyInstance()方法獲得的)MyDynamicProxy 類型。
那么 JVM 內部就會將請求直接轉發給?MyDynamicProxy 對象內部的 invoke() 方法。
invoke() 方法收到請求后,可以通過傳來的 Method 參數(實際上就是被調用的抽象接口方法信息)以及 args 參數做額外的處理。
處理完成后,將被代理對象、參數列表傳遞給?method.invoke() 方法,完成請求的 “轉發”(其中被代理對象是在Proxy.newProxyInstance() 創建代理對象的時候傳遞給代理對象的構造器,參數列表則是在調用者進行方法調用的時候傳遞給調用處理器的)。
動態代理是普通代理的進一步擴展和應用,代理類在定義之初并不知道將會代理哪個接口以及哪個被代理對象,但通過 newProxyInstance 靜態方法,我們可以讓代理類代理多個接口,這種變化讓程序顯得更加自由。
動態的創建代理,以及動態的處理所代理方法的調用都是動態代理這項技術的優秀之處。
?
總結
以上是生活随笔為你收集整理的Java 动态代理解析的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Linux进阶之路———Shell 编程
- 下一篇: 一篇博客读懂设计模式之---工厂模式
