代理模式、動態代理和面向方面
代理的意思很好理解,它借鑒了我們日常所用的代理的意思:就是本來該自己親自去做的某件事,由于某種原因不能直接做,而只能請人代替你做,這個被你請來做事的人就是代理。比如過春節要回家,由于你要上班,沒時間去買票,就得票務中介代你購買,這就是一種代理模式。這個情景可以形象的描述如下:
class:火車站
{
賣票:
{……}
}
火車站是賣票的地方,我們假設只能在火車站買到票。賣票的動作實質是火車站類完成的。
Class:票務中介
{
賣票:
{
收中介費;
火車站.賣票;
}
}
顧客找票務中介買票的時候,調用票務中介.賣票。票務中介其實做了兩件事,一是去火車站買票,二是不能白幫你賣票,肯定要收中介費。而你得到的好處是不用直接去火車站買票,節省了買票的時間用來上班。
以上我們簡單模擬了代理模式的情景和為什么要使用代理模式,下面我們以一個例子來具體分析一下JAVA中的代理模式。
假設有一個信息管理系統,用些用戶有瀏覽信息的權限,有些用戶有瀏覽、添加和修改信息的權限,還有些用戶有除了上述的權限,還有刪除信息的權限,那么我們最容易想到的做法如下:
public class ViewAction
{
//由userId計算權限
……
String permission = ……;
if(permission.equals(Constants.VIEW))
{
System.out.println(“You could view the information……”);
……
}
}
其他的動作都和瀏覽信息的動作差不多。我們來看這樣的類,很容易看出它的一些缺點來:第一、它把權限計算和動作執行都放在一個類里,兩者的功能相互混在一起,容易造成思路的混亂,而且修改維護和測試都不好;一句話來說,它不滿足單一職責原則。第二是客戶調用的時候依賴具體的類,造成擴展和運行期內的調用的困難,不滿足依賴顛倒原則。
既然有這么多的問題,我們有必要對該類進行重新設計。其實大家早已想到,這個類應該使用代理模式。是啊,和我們買火車票的動作一樣,動作類不能直接執行那個動作,而是要先檢查權限,然后才能執行;先檢查權限,后執行的那各類其實就是一個代理類,修改后的代碼如下:
public interface Action
{
public void doAction();
}
首先是設計一個接口,用來滿足依賴顛倒原則。
Public class ViewAction implements Action
{
public void doAction()
{
//做View的動作
System.out.println(“You could view the information……”);
……
}
}
這個類跟火車站一樣,是動作的真實執行者。
Public class ProxyViewAction implements Action
{
private Action action = new ViewAction();
public void doAction()
{
//調用權限類的方法取得用戶權限
if(Permission.getPermission(userId).equals(Constants.VIEW))
{
action.doAction();
}
}
}
這是代理類,很容易理解。在我們的ProxyViewAction類中,除了做了客戶真正想要做的動作:doAction()以外,還進行了額外的動作檢查用戶的權限。而作核心動作doAction()是在一個干干凈凈的類:ViewAction中進行,這個類只做核心動作,對其他的不關心,滿足了單一職責原則。
客戶端通過調用代理類來執行動作,而代理類一是將權限判斷和動作的執行分離開來,滿足了單一職責原則;二是實現了一個接口,從而滿足了依賴顛倒原則。比第一個思路好了很多。
代理又被稱為委派,說的是代理類并不真正的執行那個核心動作,而是委派給另外一個類去執行,如ProxyView類中,ProxyView類并沒有真正執行doAction()方法,而是交給ViewAction類去執行。
我們再來看代理類ProxyViewAction,可以看到它不僅依賴于接口Action,而且依賴于具體的實現ViewAction。這樣對我們的系統擴展很不利,比如我們有Add動作、Delete動作、Modify動作等等,我們需要對每一個動作都寫一個代理類,而這些代理類都做同樣的事情,先進行權限判斷,然后再委派。所以我們需要對這些代理再進行一次抽象,讓它只依賴接口Action,而不依賴于具體的實現。
要實現這樣的想法,我們需要將代理類中的具體實現提走,讓代理的使用者在運行期提供具體的實現類,即所謂的依賴注入,如下:
Public class ProxyAction implements Action
{
private Action action;
public ProxyAction(Action action)
{
this.action = action;
}
public void doAction()
{
//調用權限類的方法取得用戶權限
if(Permission.getPermission(userId).equals(action.getClass().getName()))
{
action.doAction();
}
}
}
這樣,我們就將所有實現了Action接口的實現使用一個代理類來代理它們。除了ViewAction類能用,以后擴展的AddAction、 ModifyAction、DeleteAction類等等,都可以使用一個代理類:ProxyAction。
而我們的客戶端類似如下:
Action action = ProxyAction(new ViewAction);
Action.doAction();
通過對代理類的依賴注入,我們使得代理類初步有了一定擴展性。但是我們還要看到,這個代理類依賴于某一個確定的接口。這仍然不能滿足我們的實際要求,如我們的系統的權限控制一般是整個系統級的,這樣系統級的權限控制,我們很難在整個系統里抽象出一個統一的接口,可能會有多個接口,按照上面的代理模式,我們需要對每一個接口寫一個代理類,同樣,這些類的功能都是一樣的。這顯然不是一個好地解決辦法。
基于上面的原因,我們需要解決一個系統在沒有統一的接口的情況下,對一些零散的對象的某一些動作使用代理模式的問題。JAVA API為我們引入了動態代理或動態委派的技術。
動態代理的核心是InvocationHandler接口,要使用動態代理就必須實現該接口。這個接口的委派任務是在invoke(Object proxy, Method m, Object[] args)方法里面實現的:
//在調用核心功能之前作一些動作
……
//調用核心功能
m.invoke(obj, args);
//在調用核心功能以后做一些動作
……
我們可以看到動態代理其實用的是反射機制來調用核心功能的:m.invoke(obj, args);正是這種反射機制的使用使得我們調用核心功能更加靈活,而不用依賴于某一個具體的接口,而是依賴于Object對象。
下面我們來具體看看動態代理或動態委派如何使用:
public class ProxyAction implements InvocationHandler {
private Object action;
public ProxyAction(Object action)
{
this.action = action;
}
public static Object getInstance(Object action)
{
return Proxy.newProxyInstance(action.getClass().getClassLoader(),
action.getClass().getInterfaces(),new ProxyAction(action));
}
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
Object result;
try {
//在委派之前作動作,如權限判斷等
System.out.println("before method " + m.getName());
//進行委派
result = m.invoke(action, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: "
+ e.getMessage());
} finally {
//在委派之后做動作
System.out.println("after method " + m.getName());
}
return result;
}
}
這個代理類,首先是實現了InvocationHandler接口;然后在getInstance()方法里得到了代理類的實例;在invoke()方法里實現代理功能,也很簡單。
下面我們來看客戶端:
Action action = (Action)ProxyAction.getInstance(new ViewAction());
Action.doAction();
我們可以看到代理類對接口的依賴也轉移到了客戶端上,這樣,代理類不依賴于某個接口。對于同樣的代理類ProxyAction,我們也可以有如下的客戶端調用:
Engine engine = (Engine)ProxyAction.getInstance(new EngineImpl());
Engine.execute();
只要engineImpl類實現了Engine接口,就可以像上面那樣使用。
現在我們可以看到,動態代理的確是擁有相當的靈活性。但我們同時也看到了,這個代理類寫起來比較麻煩,而且也差不多每次都寫這樣千篇一律的東西,只有委派前的動作和委派后的動作在不同的代理里有著不同,其他的東西都需要照寫。如果這樣的代理類寫多了,也會有一些冗余代理。需要我們進一步優化,這里我們使用模板方法模式來對這個代理類進行優化,如下:
public abstract class BaseProxy implements InvocationHandler {
private Object obj;
protected BaseProxy(Object obj)
{
this.obj = obj;
}
public static Object getInstance(Object obj,InvocationHandler instance)
{
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),instance);
}
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
Object result;
try {
System.out.println("before method " + m.getName());
this.doBegin();
result = m.invoke(obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: "
+ e.getMessage());
} finally {
System.out.println("after method " + m.getName());
this.doAfter();
}
return result;
}
public abstract void doBegin();
public abstract void doAfter();
}
這樣,代理的實現類只需要關注實現委派前的動作和委派后的動作就行,如下:
public class ProxyImpl extends BaseProxy {
protected ProxyImpl(Object o)
{
super(o);
}
public static Object getInstance(Object foo)
{
return getInstance(foo,new ProxyImpl(foo));
}
//委派前的動作
public void doBegin() {
// TODO Auto-generated method stub
System.out.println("begin doing....haha");
}
//委派后的動作
public void doAfter() {
// TODO Auto-generated method stub
System.out.println("after doing.....yeah");
}
}
從上面的代碼,我們可以看出代理實現類的確是簡單多了,只關注了委派前和委派后的動作,這是我們作為一個代理真正需要關心的。
至此,代理模式和動態代理已經告一段落。我們將動態代理引申一點說開去,來作為這篇文章的蛇足。
這個話題就是面向方面的編程,或者說AOP。我們看上面的ProxyImpl類,它的兩個方法doBegin()和doAfter(),這是做核心動作之前和之后的兩個截取段。正是這兩個截取段,卻是我們AOP的基礎。在OOP里,doBegin(),核心動作,doAfter()這三個動作在多個類里始終在一起,但他們所要完成的邏輯卻是不同的,如doBegin()可能做的是權限,在所有的類里它都做權限;而在每個類里核心動作卻各不相同;doAfter()可能做的是日志,在所有的類里它都做日志。正是因為在所有的類里,doBegin()或doAfter()都做的是同樣的邏輯,因此我們需要將它們提取出來,單獨分析、設計和編碼,這就是我們的AOP的思想。
這樣說來,我們的動態代理就能作為實現AOP的基礎了。好了,就說這么多,關于AOP技術,我們可以去關注關于這方面的知識。