代理模式(Proxy Pattern)是一種比較常見的設計模式,在很多場合都會被用到。
所謂代理指的是讓其他的類代替完成一些任務(執行一些方法等),在軟件開發中代理模式具有非常重要的作用,面向切面編程(AOP)便是基於代理模式運作的編程范式。
下面介紹一下其中的靜態代理與動態代理,基於Java語言。
靜態代理:
首先由一個HelloWorld接口,其中有一個方法,print
public interface HelloWorld { void print(); }
接下來是實現了HelloWorld接口的HelloWorldImpl類
public class HelloWorldImpl implements HelloWorld { @Override public void print() { System.out.println("Hello World"); } }
如果想要在方法的開頭或者結尾做一些事情,例如打印日志之類或者處理一些校驗邏輯之類的的,如果將其也寫入print方法中會使得代碼中業務代碼與非業務代碼交織在一起,這樣不是一種很好的做法。
我們可以使用靜態代理來做一些"手腳",完成這樣的需求。
public class HelloWorldProxy implements HelloWorld { private final HelloWorld helloWorld;
public HelloWorldProxy(HelloWorld helloWorld) { this.helloWorld = helloWorld; }
@Override public void print() { before(); helloWorld.print(); after(); }
private void before() { System.out.println("Begin"); }
private void after() { System.out.println("End"); } }
最后,我們編寫代碼,測試一下
public class HelloWorldTest { public static void main(String[] args) { HelloWorld helloWorld = new HelloWorldProxy(); helloWorld.print(); } }
程序將打印
Begin
Hello World
End
之所以上面的代理代碼被稱為靜態代理是因為這個是在編譯階段就已經能夠確定的代理關系。
靜態代理具有代理模式的優點就是可以做到隔離業務代碼與非業務代碼
靜態代理的主要缺點是一個委托類對應於一個代理類,並且需要為每一個需要委托的方法編寫相應的代理方法,對於項目中需要大量用到代理模式的情況,靜態代理會增加非常多的代碼量
此外,由於代理類與委托類都實現了同樣的接口,假設接口需要變動,代理類也需要同步變動,這樣對於軟件項目維護也會增加不少的工作量與難度
接下來介紹上面的例子如何改成使用動態代理
動態代理:
首先原來的HelloWorldProxy類可以刪掉了
編寫HelloWorldInvocationHandler類,實現java.lang.reflect.InvocationHandler類,作為HelloWorld的調用處理器
public class HelloWorldInvocationHandler implements InvocationHandler { private Object obj; public HelloWorldInvocationHandler(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(obj, args); after(); return result; } private void before() { System.out.println("Begin"); } private void after() { System.out.println("End"); } }
接下來,改一下原來的HelloWorldTest類
public class HelloWorldTest { public static void main(String[] args) { HelloWorld helloWorld = new HelloWorldImpl(); InvocationHandler handler = new HelloWorldInvocationHandler(helloWorld); HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance( helloWorld.getClass().getClassLoader(), helloWorld.getClass().getInterfaces(), handler); proxy.print(); } }
運行HelloWorldTest,可以得到與上面靜態代理同樣的結果
Begin
Hello World
End
上面的代碼仍然有些繁瑣,可以有兩種方式簡化一下獲取代理的代碼,一種是做一個HelloWorld代理的工廠,每一次從工廠中拿上面代碼中的proxy,還有一種是在HelloWorldInvocationHandler類中增加一個getProxy方法。
稍稍總結一下動態代理的優缺點
相比靜態代理,一個非常顯著的優點是動態代理可以在自定義調用處理器統一處理委托類的方法,而不必一個個編寫。
而動態代理有一個缺點,那就是只能代理基於接口的類,而無法代理沒有接口的委托類。關於這一點,可以使用CGLib類庫來做到代理無接口類。
本文參考鏈接:
