動態代理在Java中有着普遍的應用,好比Spring AOP,Hibernate數據查詢、測試框架的後端mock、RPC,Java註解對象獲取等。靜態代理的代理關係在編譯時就肯定了,而動態代理的代理關係是在編譯期肯定的。靜態代理實現簡單,適合於代理類較少且肯定的狀況,而動態代理則給咱們提供了更大的靈活性。今天咱們來探討Java中兩種常見的動態代理方式:JDK原生動態代理和CGLIB動態代理。html
先從直觀的示例提及,假設咱們有一個接口Hello
和一個簡單實現HelloImp
:java
// 接口 interface Hello{ String sayHello(String str); } // 實現 class HelloImp implements Hello{ @Override public String sayHello(String str) { return "HelloImp: " + str; } }
這是Java種再常見不過的場景,使用接口制定協議,而後用不一樣的實現來實現具體行爲。假設你已經拿到上述類庫,若是咱們想經過日誌記錄對sayHello()
的調用,使用靜態代理能夠這樣作:git
// 靜態代理方式 class StaticProxiedHello implements Hello{ ... private Hello hello = new HelloImp(); @Override public String sayHello(String str) { logger.info("You said: " + str); return hello.sayHello(str); } }
上例中靜態代理類StaticProxiedHello
做爲HelloImp
的代理,實現了相同的Hello
接口。用Java動態代理能夠這樣作:github
// Java Proxy // 1. 首先實現一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。 class LogInvocationHandler implements InvocationHandler{ ... private Hello hello; public LogInvocationHandler(Hello hello) { this.hello = hello; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("sayHello".equals(method.getName())) { logger.info("You said: " + Arrays.toString(args)); } return method.invoke(hello, args); } } // 2. 而後在須要使用Hello的時候,經過JDK動態代理獲取Hello的代理對象。 Hello hello = (Hello)Proxy.newProxyInstance( getClass().getClassLoader(), // 1. 類加載器 new Class<?>[] {Hello.class}, // 2. 代理須要實現的接口,能夠有多個 new LogInvocationHandler(new HelloImp()));// 3. 方法調用的實際處理者 System.out.println(hello.sayHello("I love you!"));
運行上述代碼輸出結果:spring
日誌信息: You said: [I love you!] HelloImp: I love you!
上述代碼的關鍵是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
方法,該方法會根據指定的參數動態建立代理對象。三個參數的意義以下:shell
loader
,指定代理對象的類加載器;interfaces
,代理對象須要實現的接口,能夠同時指定多個接口;handler
,方法調用的實際處理者,代理對象的方法調用都會轉發到這裏(*注意1)。newProxyInstance()
會返回一個實現了指定接口的代理對象,對該對象的全部方法調用都會轉發給InvocationHandler.invoke()
方法。理解上述代碼須要對Java反射機制有必定了解。動態代理神奇的地方就是:編程
InvocationHandler.invoke()
方法,在invoke()
方法裏咱們能夠加入任何邏輯,好比修改方法參數,加入日誌功能、安全檢查功能等;以後咱們經過某種方式執行真正的方法體,示例中經過反射調用了Hello對象的相應方法,還能夠經過RPC調用遠程方法。注意1:對於從Object中繼承的方法,JDK Proxy會把
hashCode()
、equals()
、toString()
這三個非接口方法轉發給InvocationHandler
,其他的Object方法則不會轉發。詳見JDK Proxy官方文檔。後端
若是對JDK代理後的對象類型進行深挖,能夠看到以下信息:api
# Hello代理對象的類型信息 class=class jdkproxy.$Proxy0 superClass=class java.lang.reflect.Proxy interfaces: interface jdkproxy.Hello invocationHandler=jdkproxy.LogInvocationHandler@a09ee92
代理對象的類型是jdkproxy.$Proxy0
,這是個動態生成的類型,類名是形如$ProxyN的形式;父類是java.lang.reflect.Proxy
,全部的JDK動態代理都會繼承這個類;同時實現了Hello
接口,也就是咱們接口列表中指定的那些接口。安全
若是你還對jdkproxy.$Proxy0
具體實現感興趣,它大體長這個樣子:
// JDK代理類具體實現 public final class $Proxy0 extends Proxy implements Hello { ... public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } ... @Override public final String sayHello(String str){ ... return super.h.invoke(this, m3, new Object[] {str});// 將方法調用轉發給invocationhandler ... } ... }
這些邏輯沒什麼複雜之處,可是他們是在運行時動態產生的,無需咱們手動編寫。更多詳情,可參考BrightLoong的Java靜態代理&動態代理筆記
Java動態代理爲咱們提供了很是靈活的代理機制,但Java動態代理是基於接口的,若是對象沒有實現接口咱們該如何代理呢?CGLIB登場。
CGLIB(Code Generation Library)是一個基於ASM的字節碼生成庫,它容許咱們在運行時對字節碼進行修改和動態生成。CGLIB經過繼承方式實現代理。
來看示例,假設咱們有一個沒有實現任何接口的類HelloConcrete
:
public class HelloConcrete { public String sayHello(String str) { return "HelloConcrete: " + str; } }
由於沒有實現接口該類沒法使用JDK代理,經過CGLIB代理實現以下:
// CGLIB動態代理 // 1. 首先實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。 class MyMethodInterceptor implements MethodInterceptor{ ... @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { logger.info("You said: " + Arrays.toString(args)); return proxy.invokeSuper(obj, args); } } // 2. 而後在須要使用HelloConcrete的時候,經過CGLIB動態代理獲取代理對象。 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HelloConcrete.class); enhancer.setCallback(new MyMethodInterceptor()); HelloConcrete hello = (HelloConcrete)enhancer.create(); System.out.println(hello.sayHello("I love you!"));
運行上述代碼輸出結果:
日誌信息: You said: [I love you!] HelloConcrete: I love you!
上述代碼中,咱們經過CGLIB的Enhancer
來指定要代理的目標對象、實際處理代理邏輯的對象,最終經過調用create()
方法獲得代理對象,對這個對象全部非final方法的調用都會轉發給MethodInterceptor.intercept()
方法,在intercept()
方法裏咱們能夠加入任何邏輯,好比修改方法參數,加入日誌功能、安全檢查功能等;經過調用MethodProxy.invokeSuper()
方法,咱們將調用轉發給原始對象,具體到本例,就是HelloConcrete
的具體方法。CGLIG中MethodInterceptor的做用跟JDK代理中的InvocationHandler
很相似,都是方法調用的中轉站。
注意:對於從Object中繼承的方法,CGLIB代理也會進行代理,如
hashCode()
、equals()
、toString()
等,可是getClass()
、wait()
等方法不會,由於它是final方法,CGLIB沒法代理。
若是對CGLIB代理以後的對象類型進行深挖,能夠看到以下信息:
# HelloConcrete代理對象的類型信息 class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52 superClass=class lh.HelloConcrete interfaces: interface net.sf.cglib.proxy.Factory invocationHandler=not java proxy class
咱們看到使用CGLIB代理以後的對象類型是cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
,這是CGLIB動態生成的類型;父類是HelloConcrete
,印證了CGLIB是經過繼承實現代理;同時實現了net.sf.cglib.proxy.Factory
接口,這個接口是CGLIB本身加入的,包含一些工具方法。
注意,既然是繼承就不得不考慮final的問題。咱們知道final類型不能有子類,因此CGLIB不能代理final類型,遇到這種狀況會拋出相似以下異常:
java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete
一樣的,final方法是不能重載的,因此也不能經過CGLIB代理,遇到這種狀況不會拋異常,而是會跳過final方法只代理其餘方法。
若是你還對代理類cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
具體實現感興趣,它大體長這個樣子:
// CGLIB代理類具體實現 public class HelloConcrete$$EnhancerByCGLIB$$e3734e52 extends HelloConcrete implements Factory { ... private MethodInterceptor CGLIB$CALLBACK_0; // ~~ ... public final String sayHello(String paramString) { ... MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0; if (tmp17_14 != null) { // 將請求轉發給MethodInterceptor.intercept()方法。 return (String)tmp17_14.intercept(this, CGLIB$sayHello$0$Method, new Object[] { paramString }, CGLIB$sayHello$0$Proxy); } return super.sayHello(paramString); } ... }
上述代碼咱們看到,當調用代理對象的sayHello()
方法時,首先會嘗試轉發給MethodInterceptor.intercept()
方法,若是沒有MethodInterceptor
就執行父類的sayHello()
。這些邏輯沒什麼複雜之處,可是他們是在運行時動態產生的,無需咱們手動編寫。如何獲取CGLIB代理類字節碼可參考Access the generated byte[] array directly。
更多關於CGLIB的介紹能夠參考Rafael Winterhalter的cglib: The missing manual,一篇很深刻的文章。
本文介紹了Java兩種常見動態代理機制的用法和原理,JDK原生動態代理是Java原生支持的,不須要任何外部依賴,可是它只能基於接口進行代理;CGLIB經過繼承的方式進行代理,不管目標對象有沒有實現接口均可以代理,可是沒法處理final的狀況。
動態代理是Spring AOP(Aspect Orient Programming, 面向切面編程)的實現方式,瞭解動態代理原理,對理解Spring AOP大有幫助。