【朝花夕拾】寫一個動態代理類
什麼是動態代理:不管我們的物件是什麼,我都可以通過這樣一個類去生成一個相應的代理物件,幫助開發者去完成功能,和目標物件(被代理者)沒有任何關係
準備步驟:
1.建立有一個介面,裡面定義了一個或多個抽象方法
package com.atguigu.spring.proxy; public interface Math { int add(int a,int b); int sub(int a,int b); int mul(int a,int b); int div(int a,int b); }
2.建立一個實現類(即我們的被代理類),實現這個介面,並重寫了方法
package com.atguigu.spring.proxy; public class MathImpl implements Math { @Override public int add(int a, int b) { return a+b; } @Override public int sub(int a, int b) { return a-b; } @Override public int mul(int a, int b) { return a*b; } @Overridepublic int div(int a, int b) { return a/b; } }
開始寫代理類步驟:
1.建立一個能夠獲取代理類的工具類
思路分析:動態代理的目的是獲取一個代理類來完成工作
獲取代理類java有一個通用的方法就是使用Proxy類的靜態方法newProxyInstance
該方法返回一個代理類,所以是有返回值的,一般為Object,和本文首行的動態代理的定義相呼應,但是需要傳入三個引數
引數1:類載入器 引數2:被代理者介面的Class物件陣列 引數3:一個代理類處理器
引數1分析:我們要載入一個類需要類載入器,而代理類就是我們需要載入的類,所以我們需要傳入一個類載入器
那麼這個類載入器傳什麼比較好,因為工具類也是由類載入器載入的,所以我們直接this.getClass().getClassLoader()來獲取類載入器,
其實這個類載入器屬於應用程式類載入器,如果你使用工具類中存在的被代理者物件的getClass().getClassLoader()也是一樣的效果
引數2分析:代理類需要完成的是和被代理者相同的事,只不過加了一些其他的業務邏輯,那麼被代理者繼承了介面,重寫了方法,同理,我代理類
也需要繼承它繼承過的介面,這樣被代理者重寫過的方法,代理類也需要重寫,就能達到呼叫相同業務邏輯的目的
引數3分析:一個代理類處理器,這個處理器就是幫助我們完成業務邏輯的,我們一般使用匿名內部類來完成傳參,匿名內部類中需要重寫一個invoke
方法,該方法有三個形參,分別會傳入代理類物件,重寫的方法物件和方法的引數。可能會疑問為什麼這裡的方法物件不是陣列呢,因為
我們呼叫一個方法是一個個呼叫的,不能一下子把所有方法全呼叫了,就比如使用方法是mathImpl.add(),總不能mathimpl.add[]()或者
mathimpl.add().sub().div()這樣的傳。第三個引數就是介面方法中可能需要傳遞的引數了。invoke()方法的形參傳完之後,invoke()方法的方法體
就是進行方法呼叫,這裡是反射的知識,即method.invoke(被呼叫的方法所屬的物件,引數),有返回值或者沒返回值都return一下
迴歸以上,在上面這個方法呼叫獲取代理類的時候,我們有一個點沒有提到,那就是被代理者,在上面的newProxyInstance()的引數二和method.invoke()裡面的引數
都會用到被代理者的物件,這也說明了工具類中需要持有被代理者的物件,用於呼叫它的方法,因為動態代理的核心業務邏輯仍是被代理類的方法完成的。所以工具
類中需要定義一個被代理物件的屬性,並寫出有參構造,才能在建立代理工具類的同時賦值一個被代理者物件
程式碼如下
package com.atguigu.spring.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyUtil { private MathImpl mathImpl; public ProxyUtil(MathImpl mathImpl) { this.mathImpl = mathImpl; } public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), mathImpl.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(mathImpl,args); } }); } }
2.測試類
package com.atguigu.spring.proxy; public class ProxyTest { public static void main(String[] args) { ProxyUtil proxyUtil = new ProxyUtil(new MathImpl()); Math proxy = (Math)proxyUtil.getProxy(); System.out.println(proxy.add(1,1)); } }
程式碼分析,首先建立了一個代理類物件,給物件中的屬性:被代理者物件 賦值,之後呼叫獲取代理類物件的方法,這個代理類物件是實現了和被代理類物件MathImpl相同的介面Math的,因此需要進行型別轉換,之後呼叫方法就可以
原理如下,看上去有些亂,其實理解了很簡單,畫的不好見諒