一些開(kāi)源框架中AOP的實(shí)現方法
首先說(shuō)說(shuō)Webwork中的Interceptor。
下面是XWork中Interceptor接口的src
package com.opensymphony.xwork.interceptor;
import com.opensymphony.xwork.ActionInvocation;
public interface Interceptor {
void destroy();
void init();
String intercept(ActionInvocation invocation) throws Exception;
}
一般情況下如果要自己定義攔截器只需要實(shí)現Intercptor這個(gè)接口即可。在覆寫(xiě)intercept方法的時(shí)候注意要對invocation進(jìn)行invoke調用。這個(gè)方法的作用是執行攔截器鏈上的下一個(gè)環(huán)節,如果沒(méi)有攔截器那么就執行Action。何servlet里面的filter非常相似。在攔截器中可以通過(guò)ServletActionContext獲得相關(guān)的數據比如Session里面的數據或者Paramater里面的數據等等。XWork中ActionContext這個(gè)類(lèi)里面是以Map的形式來(lái)存儲各類(lèi)數據,所以是和容器解耦的。在WW里面的ServletActionContext則是負責把web容器里面的request,session之類(lèi)的東西綁到Map上面,讓整個(gè)鏈上的每個(gè)環(huán)節都可以使用參數。
當然也可以繼承XWork的AroundInterceptor,利用模版模式(Template Design Pattern)來(lái)實(shí)現前攔截和后攔截。
public abstract class AroundInterceptor implements Interceptor {
public String intercept(ActionInvocation invocation) throws Exception {
String result = null;
before(invocation);
result = invocation.invoke();
after(invocation, result);
return result;
}
protected abstract void after(ActionInvocation dispatcher, String result) throws Exception;
protected abstract void before(ActionInvocation invocation) throws Exception;
}
上面這種純粹利用類(lèi)的繼承來(lái)解決方法的攔截。
下面說(shuō)說(shuō)利用Proxy這個(gè)類(lèi)來(lái)實(shí)現動(dòng)態(tài)代理。
首先回顧一下設計模式中的Proxy Design Pattern。有一個(gè)業(yè)務(wù)接口,有一個(gè)業(yè)務(wù)實(shí)現類(lèi),然后業(yè)務(wù)實(shí)現類(lèi)實(shí)現這個(gè)接口,這時(shí)我自己人工添加一個(gè)代理類(lèi)也實(shí)現業(yè)務(wù)接口,同時(shí)聚合一個(gè)業(yè)務(wù)實(shí)現類(lèi)的對象,在實(shí)現業(yè)務(wù)接口方法的時(shí)候委派被代理類(lèi)(業(yè)務(wù)是實(shí)現類(lèi))執行,同時(shí)在執行前后干些額外的事情,現在就說(shuō)說(shuō)這個(gè)代理類(lèi)的生成方法。
首先Person接口中有love方法,然后PersonImpl實(shí)現類(lèi)。我需要在Person調用love方法的前后加入日志記錄的功能,同時(shí)必須保證透明性。即Person接口的使用者對logger一無(wú)所知。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LoggingProxy implements InvocationHandler {
//下面這個(gè)是生成一個(gè)動(dòng)態(tài)代理的類(lèi)的方法,注意接口可以隨自己制定,但是必須有被代理對象obj作為參數,返回值是代理//對象。
public static Object newLogginPorxyAround(Logger logger, Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), new LoggingProxy(logger, obj));
}
private Object proxiedObject;
private Logger logger;
private LoggingProxy(Logger l, Object obj) {
logger = l;
proxiedObject = obj;
}
//這個(gè)是實(shí)現接口的時(shí)候必須覆寫(xiě)的方法,可以告訴代理類(lèi)被攔截方法的Method對象和方法的參數列表Object[] args
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
try {
logger.info("Entering method " + method.getName());
result = method.invoke(proxiedObject, args);
} catch (Exception x) {
logger.warn("Unexpected exception " + x + " invoking "
+ method.getName());
} finally {
logger.info("Exiting method " + method.getName());
}
return result;
}
}
下面看一下Proxy.newProxyInstance的src
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
{
//注意省略了一些try catch
Class cl = getProxyClass(loader, interfaces);
//constructorParams的定義
// private final static Class[] constructorParams = { InvocationHandler.class };
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
}
可以看到一個(gè)代理的Object就這樣被制造出來(lái)了。
下面看看客戶(hù)端使用情況
Logger personLogger = new Logger();
Person p = new PersonImpl();
p=(Person)LoggingProxy.newLogginPorxyAround(personLogger,p);
p.love();
獲得代理對象后需要轉型為接口類(lèi)型,當然可以利用泛型,減少類(lèi)型轉換,不過(guò)用了泛型static方法返回T是無(wú)法通過(guò)編譯的。在運行的結果中可以看到方法的前后都會(huì )有日志輸出。
首先必須明確一點(diǎn),使用動(dòng)態(tài)代理必須有一個(gè)業(yè)務(wù)接口的存在,否則一切無(wú)從談起,如果把這個(gè)例子修改成PersonImpl p = new PersonImpl();然后去生成動(dòng)態(tài)代理運行的時(shí)候是會(huì )出錯的。
那么假設我有一個(gè)類(lèi),沒(méi)有對應的接口,我想對他的方法進(jìn)行AOP如何實(shí)現呢?一般來(lái)說(shuō)都是采用修改字節碼的方法。在這方面CGLib是首選,當然還有威力更加強大的AspectJ,利用代碼的織入實(shí)現AOP。
下面是一個(gè)利用CGLib來(lái)運行時(shí)修改字節碼的例子
e.g.
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class AOPInstrumenter implements MethodInterceptor {
private static Log logger = LogFactory.getLog(AOPInstrumenter.class);
private Enhancer enhancer = new Enhancer();
public Object getInstrumentedClass(Class clz) {
enhancer.setSuperclass(clz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] methodParameters,
MethodProxy methodProxy) throws Throwable {
logger.debug("Before Method =>" + method.getName());
Object result = methodProxy.invokeSuper(o, methodParameters);
logger.debug("After Method =>" + method.getName());
return result;
}
public static void main(String args[]) {
AOPInstrumenter aopInst = new AOPInstrumenter();
PersonImpl pi = (PersonImpl) aopInst
.getInstrumentedClass(PersonImpl.class);
pi.love();
}
}
AspectJ 的用法就不多說(shuō)了。上面的動(dòng)態(tài)代理和CGLib修改字節碼就是SpringFramework中AOP框架的實(shí)現原理,同時(shí)也是申明式事務(wù)管理的基礎。Rod曾經(jīng)拿Spirng AOP和AspectJ做比較(詳見(jiàn)Without EJB),兩者的缺點(diǎn)和優(yōu)點(diǎn)他都說(shuō)了,但是他更加傾向于使用Spring AOP,畢竟是自己的東西。一般項目中如果沒(méi)有特殊需求應該不會(huì )用到AspectJ。一般來(lái)說(shuō)AOP用來(lái)做權限和事務(wù)管理效果比較好,這樣在業(yè)務(wù)代碼中就可以少掉很多重復冗余的代碼,而且可以在后期一次性加上。對于權限一般簡(jiǎn)單的應用可以采用ww的interceptor,復雜一點(diǎn)的推薦使用基于Spring的Acegi框架,這個(gè)東西的配置實(shí)在是繁瑣,而且使用和理解也頗有難度。他的官方demo把權限配置在XML文件中對于企業(yè)級應用來(lái)說(shuō)是毫無(wú)意義的,等于是玩具,因為權限一旦膨脹維護這個(gè)XML文件簡(jiǎn)直是噩夢(mèng)一般。但是可以自己進(jìn)行數據庫擴展,遵循RBAC模型resource(function)/permission/role/user.它可以實(shí)現3種級別的攔截分別是 URL,function,acl(粒度是domain的方法,很細很細了).一般來(lái)說(shuō)到url和function就可以完成對所有資源的控制了.關(guān)于Acegi的設計說(shuō)明可以參考Professional.Java.Development.with.the.Spring.Framework一書(shū).
聯(lián)系客服