1.無代理程序員
凡是都要由淺入深,學習也不例外。先來一個 Hello World 吧: 架構
public interface Hello { void say(String name); }
這是一個 Hello 接口,不用解釋了,你們都懂的。趕忙上實現類吧:框架
public class HelloImpl implements Hello { @Override public void say(String name) { System.out.println("Hello! " + name); } }
可是若是要在 println() 方法前面和後面分別須要處理一些邏輯,怎麼作呢?把這些邏輯寫死在 say() 方法裏面嗎 ?這時就須要代理了ide
2.靜態代理模式函數
我要用代理!寫一個 HelloProxy 類,讓它去調用 HelloImpl 的 say() 方法,在調用的先後分別進行邏輯處理不就好了嗎?趕忙搞一個吧:工具
public class HelloProxy implements Hello { private HelloImpl helloImpl; public HelloProxy() { helloImpl = new HelloImpl(); } @Override public void say(String name) { before(); helloImpl.say(name); after(); } private void before() { System.out.println("Before"); } private void after() { System.out.println("After"); } }
我將 HelloProxy 類實現了 Hello 接口(和 HelloImpl 實現相同的接口),而且在構造方法中 new 出一個 HelloImpl 類的實例。這樣一來,我就能夠在 HelloProxy 的 say() 方法裏面去調用 HelloImpl 的 say() 方法了。更重要的是,我還能夠在調用的先後分別加上 before() 與 after() 方法,在這兩個方法裏去實現那些先後邏輯。學習
用一個 main 方法來測試一下吧:測試
public static void main(String[] args) { Hello helloProxy = new HelloProxy(); helloProxy.say("Jack"); }
運行後,打印出:this
Before
Hello! Jack
After.net
3.動態代理模式
因而我就是用 JDK 給咱們提供的動態代理方案,寫了一個 DynamicProxy:
public class DynamicProxy implements InvocationHandler { private Object target; public DynamicProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); after(); return result; } ... }
在 DynamicProxy 類中,我定義了一個 Object 類型的 target 變量,它就是被代理的目標對象,經過構造函數來初始化(如今流行叫「注入」了,我以爲叫「射入」也不錯哦!構造函數初始化叫「正着射」,因此 reflect 方式就叫「反着射」,簡稱「反射」)。
言歸正傳,DynamicProxy 實現了 InvocationHandler 接口,那麼必須實現該接口的 invoke 方法,參數不作解釋,望文生義吧,是 JRE 給咱們「射」進來的。在該方法中,直接經過反射去 invoke method,在調用先後分別處理 before 與 after,最後將 result 返回。
寫一個 main() 方法看看實際怎麼用吧:
public static void main(String[] args) { Hello hello = new HelloImpl(); DynamicProxy dynamicProxy = new DynamicProxy(hello); Hello helloProxy = (Hello) Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), dynamicProxy ); helloProxy.say("Jack"); }
沒錯,意思就是,用我寫的這個通用的 DynamicProxy 類去包裝 HelloImpl 實例,而後再調用 JDK 給咱們提供的 Proxy 類的工廠方法 newProxyInstance() 去動態地建立一個 Hello 接口的代理類,最後調用這個代理類的 say() 方法。
運行一下,結果和之前同樣,動態代理成功了。其實,動態代理就是幫咱們自動生成 XxxProxy 類的法寶啊!
要注意的是,Proxy.newProxyInstance() 方法的參數實在是讓我「蛋碎一地」!
參數1:ClassLoader
參數2:該實現類的全部接口
參數3:動態代理對象
調用完了還要來一個強制類型轉換一下。
wocao!這一坨 shi 必定要想辦法封裝一下,避免再次發生處處都是 Proxy.newProxyInstance(),這樣架構師又要罵我了。因而我將這個 DynamicProxy 重構了:
public class DynamicProxy implements InvocationHandler { ... @SuppressWarnings("unchecked") public <T> T getProxy() { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } ... }
我在 DynamicProxy 裏添加了一個 getProxy() 方法,無需傳入任何參數,將剛纔所說的那一坨 shi,放在這個方法中,而且該方法返回一個泛型類型,就不會強制類型轉換了。方法頭上加那個 @SuppressWarnings("unchecked") 註解表示忽略編譯時的警告(由於 Proxy.newProxyInstance() 方法返回的是一個 Object,這裏我強制轉換爲 T 了,這是向下轉型,IDE 中就會有警告,編譯時也會出現提示,很煩)。
好了,這下子使用 DynamicProxy 就簡單了吧:
public static void main(String[] args) { DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl()); Hello helloProxy = dynamicProxy.getProxy(); helloProxy.say("Jack"); }
確實簡單用 2 行代理就去掉了前面的 7 行代碼(省了 5 行),架構師看到了這樣的代碼確定會表揚我!
通過一番代碼重構後,我提交了全部的代碼,架構師看到了,沒有吱聲…… 可我總算學會了動態代理。
用了這個 DynamicProxy 之後,我以爲它仍是很是爽的,爽的地方是,接口變了,這個動態代理類不用動。而靜態代理就不同了,接口變了,實現類還要動,代理類也要動。但我也發現動 態代理並非「萬靈丹」,它也有搞不定的時候,好比說,我要代理一個沒有任何接口的類,它就沒有勇武之地了!這就是 JDK 給咱們提供的動態代理,讓我不知道該說什麼了。
因而我又開始調研,可否代理沒有接口的類呢?終於讓我找到了這顆「銀彈」!那就是 CGLib 這個類庫。雖然它看起來不太起眼,但 Spring、Hibernate 這樣牛逼的開源框架都用到了它。它就是一個在運行期間動態生成字節碼的工具,也就是動態生成代理類了。提及來好高深,實際用起來一點都不難。我再搞一個 CGLibProxy 吧:
4. CGLib 動態代理
public class CGLibProxy implements MethodInterceptor { public <T> T getProxy(Class<T> cls) { return (T) Enhancer.create(cls, this); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { before(); Object result = proxy.invokeSuper(obj, args); after(); return result; } ... }
須要實現 CGLib 給咱們提供的 MethodInterceptor 實現類,並填充 intercept() 方法。方法中最後一個 MethodProxy 類型的參數 proxy,值得注意!CGLib 給咱們提供的是方法級別的代理,也能夠理解爲對方法的攔截(這不就是傳說中的「方法攔截器」嗎?)。這個功能對於咱們這羣屌絲程序員而言,如同雪中送炭 啊,此乃神器也!咱們直接調用 proxy 的 invokeSuper() 方法,將被代理的對象 obj 以及方法參數 args 傳入其中便可。
與 DynamicProxy 相似,我在 CGlibProxy 中也添加了一個泛型的 getProxy() 方法,便於咱們能夠快速地獲取自動生成的代理對象。仍是用一個 main() 方法來描述吧:
public static void main(String[] args) { CGLibProxy cgLibProxy = new CGLibProxy(); HelloImpl helloProxy = cgLibProxy.getProxy(HelloImpl.class); helloProxy.say("Jack"); }
仍然經過 2 行代碼就能夠返回代理對象了,與 JDK 動態代理不一樣的是,這裏不須要任何的接口信息,對誰均可以生成動態代理對象(無論它是「屌絲」仍是「高富帥」)。說它是神器,過度嗎?
我一貫都是以追求完美而著稱,2 行代碼返回代理對象,我以爲仍是有些多餘,我不想老是去 new 這個 CGLibProxy 對象,最好 new 一次,之後隨時拿隨時用。因而我想到了「單例模式」:
public class CGLibProxy implements MethodInterceptor { private static CGLibProxy instance = new CGLibProxy(); private CGLibProxy() { } public static CGLibProxy getInstance() { return instance; } ... }
我加了以上幾行代碼,就搞定了!須要說明的是:這裏有一個 private 的構造方法,就是爲了限制外界不能再去 new 它了,換句話說,我在這裏把它給「閹」了。
用一個 main() 方法來證實個人簡單主義思想:
public static void main(String[] args) { HelloImpl helloImpl = CGLibProxy.getInstance().getProxy(HelloImpl.class); helloImpl.say("Jack"); }
沒錯吧?只需 1 行代碼就能夠獲取代理對象了!
總結一下,咱們今天談到了無代理、靜態代理、JDK 動態代理、CGLib 動態代理。