1、什麼是代理
代理模式的主要作用是為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不想或者不能直接引用另一個對象,而代理對象可以在用戶端和目標對象之間起到中介的作用。
代理模式的思想是為了提供額外的處理或者不同的操作而在實際對象與調用者之間插入一個代理對象。這些額外的操作通常需要與實際對象進行通訊。
例如:假設有一組對象都實現同一個介面,實現同樣的方法,但這組對象中有一部分對象需要有單獨的方法,傳統的笨辦法是在每一個應用端都加上這個單獨的方法,但是代碼重用性低,耦合性高。
如果用代理的方法則很好的解決了這個問題。
我們來看一個例子。
2、無代理
public interface Hello {void say(String name);}上面是Hello介面,下面是實作類別
public class HelloImpl implements Hello {@Overridepublic void say(String name) {System.out.println("Hello "+name);}}
如果要在println方法前面和後面分別處理一些邏輯,怎麼做呢。
在沒有代理的模式下,我們只能將這些邏輯寫死在say方法裡面。
public class HelloImpl implements Hello {@Overridepublic void say(String name) { //println前邏輯System.out.println("Hello "+name); //println後邏輯}}
3、靜態代理
使用靜態代理模式,寫一個HelloProxy類,讓他去調用HelloImpl的say方法,在調用前和調用後分別進行邏輯處理。
public class HelloProxy implements Hello {private Hello hello;public HelloProxy() {hello = new HelloImpl();}@Overridepublic void say(String name) {before();hello.say(name);after();}private void after() {System.out.println("調用方法之前");}private void before() {System.out.println("調用方法之後");}}用HelloProxy實現了Hello介面,並且在構造方法中new了一個HelloImpl的執行個體。這樣我們就可以在HelloProxy的say方法中去調用HelloImpl的say方法。更重要的是,我們還可以在調用前後分別加上before和after方法,在這兩個方法裡面去實現前後的邏輯。
public static void main(String[] args) { Hello hello = new HelloProxy(); hello.say("Ron.Zheng");}運行結果如下:
調用方法之後Hello Ron.Zheng調用方法之前
以上就是靜態代理的簡單實現方法。 但是這有一個缺陷,在代理用的比較少的項目中,靜態代理基本就夠用了,但是如果項目中,要用到代理的地方非常多,很明顯就會產生非常非常多的代理類。用什麼辦法可以減少代理類呢,辦法就是動態代理。
4、JDK動態代理方案
寫一個DynamicProxy。
public class DynamicProxy implements InvocationHandler {private Object target;public DynamicProxy(Object target) {this.target=target;}@SuppressWarnings("unchecked")public <T> T getProxy() {return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object result = method.invoke(target, args);after();return result;}private void after() {System.out.println("調用方法之前");}private void before() {System.out.println("調用方法之後");}}在DynamicProxy中,定義了一個Object類型的target對象,它就是被代理的目標對象,通過建構函式來初始化。
DynamicProxy類實現了InvocationHandler介面,那麼必須實現該介面的invoke方法,在該方法中,直接通過反射去invoke方法,在invoke方法的前後分別處理before和after,最後將result返回。
在DynamicProxy裡添加了一個getProxy方法,用於構造並返回代理執行個體,無需添加任何參數,返回一個泛型型別。
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);*/Hello helloProxy = dynamicProxy.getProxy();helloProxy.say("Ron.Zheng");}運行結果
調用方法之後Hello Ron.Zheng調用方法之前
當然,如果在DynamicProxy裡沒有添加getProxy方法,也可以通過以上代碼中注釋的部分擷取代理對象。
用了DynamicProxy以後,介面如果發生變化,動態代理類可以不做任何修改,而靜態代理就不一樣了,介面變,實作類別要動,代理類也要動。
但是在我們需要代理一個沒有介面的類時JDK動態代理方案就不能滿足需求了,但是CGLib動態代理能解決這個問題。
5、CGLib動態代理
代理為控制要訪問的目標對象提供了一種途徑。當訪問對象時,它引入了一個間接的層。JDK自從1.3版本開始,就引入了動態代理,並且經常被用來動態地建立代理。JDK的動態代理用起來非常簡單,但它有一個限制,就是使用動態代理的對象必須實現一個或多個介面。如果想代理沒有實現介面的繼承的類,該怎麼辦。現在我們可以使用CGLIB包。
CGLIB是一個強大的高效能的代碼產生包。它廣泛的被許多AOP的架構使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。
寫一個CGLibSingleProxy
public class CGLibSingleProxy implements MethodInterceptor {private static CGLibSingleProxy instance = new CGLibSingleProxy();private CGLibSingleProxy() {}public static CGLibSingleProxy getInstance() {return instance;}@SuppressWarnings("unchecked")public <T> T getProxy(Class<T> cls) {return (T) Enhancer.create(cls, this);}@Overridepublic Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable {before();Object result = proxy.invokeSuper(arg0, arg2);after();return result;}private void after() {System.out.println("調用方法之前");}private void before() {System.out.println("調用方法之後");}}
CGLibSingleProxy需要實現CGLib給我們提供的MethodInterceptor介面,並實現intercept方法。CGLib給我們提供的是方法層級的代理,也可以理解成方法攔截器。
在intercept方法中,我們需要注意MethodProxy類型的參數,我們直接調用proxy的invokeSuper方法,將被代理的對象arg0以及方法參數arg2傳入即可。
public static void main(String[] args) {Hello helloProxy = CGLibSingleProxy.getInstance().getProxy(HelloImpl.class);helloProxy.say("Ron.zheng");}執行結果:
調用方法之後Hello Ron.zheng調用方法之前
注意:使用CGLib動態代理時,需要引入CGLib的jar包,本文使用的是cglib-nodep-2.1_3.jar