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