寫在前面
今年寒假已經(jīng)接近尾聲夹厌,寒假給自己定下的目標--完成一個簡單的MVC框架也已經(jīng)快完成了列疗,在實現(xiàn)一些進階的功能,比如AOP時俺附,逐漸有了自己的理解肥卡,特此寫下,幫助自己日后復習事镣。
本文的代碼參考 <<架構(gòu)探險 從零開始寫javaweb框架>> 這本書召调。
完整代碼請移步我的 github
框架功能
使用注解形式對切面進行標記,支持前置增強蛮浑,后置增強唠叛,拋出增強,不支持類似于AspectJ切點表達式沮稚。
框架結(jié)構(gòu)
├── helper
│ └── AopHelper.java
├── proxy
│ ├── AspectProxy.java
│ ├── Proxy.java
│ ├── ProxyChain.java
│ └── ProxyManager.java
如上圖框架圖所示艺沼,在proxy包中分別有一下四個類:
-
Proxy: 自定義的接口,所有的代理類都需要實現(xiàn)這個接口蕴掏,該接口只定義了一個方法 doProxy(),即執(zhí)行橫切邏輯障般。
以下為該類的聲明:
public interface Proxy { Object doProxy(ProxyChain proxyChain); }
-
AspectProxy: 抽象類,實現(xiàn)Proxy接口盛杰,該類中定義了多個鉤子方法用于子類去重載(比如前置增強方法before(),后置增強方法after(),是否對方法進行攔截intercept())挽荡。實際上,繼承自該類的子類也就是我們自定義的切面了即供。
以下為該類的聲明:
public abstract class AspectProxy implements Proxy{ public final Object doProxy(ProxyChain proxyChain) { Object result = null; //通過ProxyChain拿到目標類的各種信息 Class<?> targetClass = proxyChain.getTargetClass(); Method method = proxyChain.getTargetMethod(); Object[] params = proxyChain.getMethodParams(); begin(); try { //如果符合切點定義的條件定拟,對該方法進行攔截 if(intercept(targetClass,method,params)) { //前置增強 before(targetClass,method,params); //執(zhí)行代理鏈中的代理方法,稍后會詳細講解代理鏈的實現(xiàn) result = proxyChain.doProxyChain(); //后置增強 after(targetClass,method,params,result); //不符合切點定義的條件逗嫡,直接執(zhí)行目標類的原方法 } else { result = proxyChain.doProxyChain(); } } catch (Throwable throwable) { throwable.printStackTrace(); } finally { end(); } return result; } /** * 以下方法可以被子類(也就是自定義的切面類)選擇性的重載 */ //不受切點的約束青自,目標類的任一方法執(zhí)行前都要執(zhí)行該方法 public void begin(){ } //切點定義,與AspectJ框架使用切點表達式不同驱证,需自己重載這個方法定義切點 public boolean intercept(Class<?> cls,Method method,Object[] params) { return true; } //前置增強 public void before(Class<?> cls,Method method,Object[] params) { } //后置增強 public void after(Class<?> cls,Method method,Object[] params,Object result) { } //拋出增強 public void error(Class<?> cls,Method method,Object[] params) { } //不受切點的約束延窜,目標類任意方法執(zhí)行之后都要執(zhí)行該方法 public void end() { } }
-
ProxyChain: 普通類,聲明的doProxyChain()方法用于執(zhí)行主要的切面邏輯抹锄。
以下是該類的聲明:
public class ProxyChain { private Class<?> targetClass; private Object targetObject; private Method targetMethod; //使用CGLib框架進行動態(tài)代理 private MethodProxy methodProxy; private Object[] methodParams; //代理鏈逆瑞,目標類可能被多個切面橫切荠藤,所有的切面類都將保存在這個集合中 private List<babyframework.proxy.Proxy> proxyList = new ArrayList<>(); //代理鏈的索引,用來記錄代理鏈的執(zhí)行情況获高,當一個所有的代理鏈都被執(zhí)行過后哈肖,執(zhí)行目標類的原方法 private int proxyIndex = 0; public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, MethodProxy methodProxy, Object[] methodParams, List<babyframework.proxy.Proxy> proxyList) { this.targetClass = targetClass; this.targetObject = targetObject; this.targetMethod = targetMethod; this.methodProxy = methodProxy; this.methodParams = methodParams; this.proxyList = proxyList; } public Class<?> getTargetClass() { return targetClass; } public Method getTargetMethod() { return targetMethod; } public Object[] getMethodParams() { return methodParams; } public Object doProxyChain() throws Throwable { Object methodResult; if(proxyIndex < proxyList.size()) { //調(diào)用代理方法 methodResult = proxyList.get(proxyIndex++).doProxy(this); } else { //調(diào)用原方法 methodResult = methodProxy.invokeSuper(targetObject,methodParams); } return methodResult; } }
-
ProxyManager: 普通類,對CGLib框架的Enhancer.create(cls,callback)進行了二次封裝谋减,以便適用于本AOP框架牡彻。
以下是該類的聲明:
public class ProxyManager { //使用CGLib動態(tài)創(chuàng)建代理類 @SuppressWarnings("unchecked") public static <T> T createProxy(final Class<?> targetClass, List<Proxy> proxyList) { return (T) Enhancer.create(targetClass, new MethodInterceptor() { //調(diào)用目標類任意原方法之前都會調(diào)用intercept()方法 @Override public Object intercept(Object targetObject, Method targetMethod, Object[] methodParams, MethodProxy methodProxy) throws Throwable { return new ProxyChain(targetClass,targetObject,targetMethod,methodProxy,methodParams,proxyList).doProxyChain(); } }); } }
對代理鏈的理解
我們首先從 ProxyManager.createProxy()方法入手,該方法的目的就是創(chuàng)建代理類出爹,使用CGLib框架創(chuàng)建代理類時庄吼,需要傳入一個callback,這個callback會攔截目標類原方法的執(zhí)行,轉(zhuǎn)而執(zhí)行intercept()方法严就。
進一步查看intercept()方法总寻,該方法創(chuàng)建了一個ProxyChain()對象并調(diào)用了doProxyChain()方法,查看ProxyChain代碼可知梢为,當我們拿到一個目標類代理鏈時(即如下代碼中的proxyList對象)渐行,判斷代理鏈中的切面是否全部執(zhí)行完畢,如果沒有執(zhí)行完畢铸董,則執(zhí)行切面中的doProxy()方法祟印。
public Object doProxyChain() throws Throwable {
Object methodResult;
if(proxyIndex < proxyList.size()) {
//調(diào)用代理方法
methodResult = proxyList.get(proxyIndex++).doProxy(this);
} else {
//調(diào)用原方法
methodResult = methodProxy.invokeSuper(targetObject,methodParams);
}
return methodResult;
}
- 查看切面類中的doProxy()方法,發(fā)現(xiàn)不管有沒有符合切點定義粟害,都會執(zhí)行proxyChain.doProxyChain()方法蕴忆。
if(intercept(targetClass,method,params)) {
before(targetClass,method,params);
result = proxyChain.doProxyChain();
after(targetClass,method,params,result);
} else {
result = proxyChain.doProxyChain();
}
舉例子來說,假如我們代理鏈中有兩個切面A和B悲幅,首先執(zhí)行切面A的doProxy(),執(zhí)行完前置增強(假如沒有定義切點套鹅,默認對所有方法進行攔截)后,執(zhí)行proxyChain.doProxyChain()方法汰具,這時代理鏈中還有一個切面B卓鹿,繼續(xù)執(zhí)行切面B的增強,切面B完成前置之后留荔,仍然調(diào)用proxyChain.doProxyChain()方法吟孙,這時代理鏈中所有切面都執(zhí)行了一遍,開始執(zhí)行目標類的原方法(即doProxyChain()的else分支)存谎,然后依次執(zhí)行各個切面的后置增強拔疚。
總之,執(zhí)行過程就是等待所有切面的所有前置增強執(zhí)行完畢之后執(zhí)行目標類的原方法既荚,原方法執(zhí)行完畢之后,依次執(zhí)行切面的后置增強栋艳。
框架的初始化
上文為我們介紹了框架是如何進行代理的,簡單來說就是拿著一個目標類和其對應的代理鏈蛛壳,然后使用CGLib框架為我們提供的方法進行代理慧邮。
那么初始化的工作就是返回一個目標類和代理鏈的映射。
我們將這塊工作交個 AOPHelper 來完成凿宾。
以下為AOPHelper類的聲明:
public final class AopHelper {
static {
try {
Map<Class<?>,Set<Class<?>>> proxyMap = createProxyMap();
Map<Class<?>,List<Proxy>> targetMap = createTargetMap(proxyMap);
for(Map.Entry<Class<?>,List<Proxy>> targetEntry : targetMap.entrySet()) {
Class<?> targetClass = targetEntry.getKey();
List<Proxy> proxyList = targetEntry.getValue();
//通過CGLib框架生成代理類
Object proxy = ProxyManager.createProxy(targetClass,proxyList);
//使用代理類替換容器中的普通類
BeanHelper.setBean(targetClass,proxy);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
/**
* 返回目標類的集合,根據(jù)@Aspect中的注解查找目標類
* @param aspect
* @return
*/
private static Set<Class<?>> createTargetClassSet(Aspect aspect) {
Set<Class<?>> targetClassSet = new HashSet<>();
//獲取Aspect注解中的值(被代理的類)
Class<? extends Annotation> annotation = aspect.value();
if(annotation != null && !annotation.equals(Aspect.class)) {
//獲取被代理的類的集合
targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
}
return targetClassSet;
}
/**
* 返回切面類和目標類的映射關(guān)系
* key->繼承自AspectProxy的類(切面)兼蕊,value->目標類集合
* @return
*/
private static Map<Class<?>,Set<Class<?>>> createProxyMap() {
Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<>();
Set<Class<?>> classSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
for(Class<?> proxyClass : classSet) {
if(proxyClass.isAnnotationPresent(Aspect.class)) {
Aspect aspect = proxyClass.getAnnotation(Aspect.class);
Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
//key->切面類初厚,value->切面要切入的類的集合(目標類的集合)
proxyMap.put(proxyClass,targetClassSet);
}
}
return proxyMap;
}
/**
* 返回目標類和代理類的映射關(guān)系,一個目標類可能由多個代理類
* key->目標類,value->代理類的集合
* @param proxyMap
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
private static Map<Class<?>,List<Proxy>> createTargetMap(Map<Class<?>,Set<Class<?>>> proxyMap) throws IllegalAccessException, InstantiationException {
Map<Class<?>,List<Proxy>> targetMap = new HashMap<>();
for(Map.Entry<Class<?>,Set<Class<?>>> proxyEntry : proxyMap.entrySet()) {
//切面類
Class<?> proxyClass = proxyEntry.getKey();
//目標類的集合
Set<Class<?>> targetClassSet = proxyEntry.getValue();
for(Class<?> targetClass : targetClassSet) {
Proxy proxy = (Proxy) proxyClass.newInstance();
if(targetMap.containsKey(targetClass)) {
//targetMap中含有目標類,直接增加proxy至proxyList中
targetMap.get(targetClass).add(proxy);
} else {
//targetMap中沒有目標類孙技,新建proxyList产禾,并將proxy添加至proxyList中
List<Proxy> proxyList = new ArrayList<>();
proxyList.add(proxy);
targetMap.put(targetClass,proxyList);
}
}
}
return targetMap;
}
}
然后將AopHelper設(shè)置為隨著tomcat的啟動而加載即可。
如上代碼注解較為詳細牵啦,這里不再詳細講解亚情。
框架的使用
框架的使用很簡單,只需定義我們想要的切面和要代理的目標類即可哈雏。如下為自定義切面示例楞件。
//代理所有被@Controller注解標記的類
@Aspect(Controller.class)
public class ControllerAspect extends AspectProxy {
private static final Logger logger = LoggerFactory.getLogger(ControllerAspect.class);
private long begin;
public void before(Class<?> cls, Method method,Object[] params) {
logger.debug("--------------");
logger.debug(cls.getName());
logger.debug(method.getName());
begin = System.currentTimeMillis();
}
public void after(Class<?> cls,Method method,Object[] params,Object result) {
logger.debug("time : " + (System.currentTimeMillis() - begin));
logger.debug("-----end------");
}
public boolean intercept(Class<?> cls,Method method,Object[] params) {
//對controller類中的所有被@Action注解標注的方法進行攔截
if(method.isAnnotationPresent(Action.class)) {
return true;
}
return false;
}
}