借鑑SpringAOP的風格,寫一個基於切面註解的AOP框架。在進行下面的步驟以前,確保已經掌了動態代理技術。java
/** * 切面註解 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Aspect { /*註解*/ Class<? extends Annotation> value(); }
經過@Target(ElementType.TYPE)來設置該註解只能應用在類上。該註解中包含一個名爲value的屬性,它是一個註解類,用來定義Controller這類註解。git
在使用切面註解以前,咱們須要先搭建一個代理框架。github
繼續在框架中添加一個名爲Proxy的接口框架
/** * 代理接口 */ public interface Proxy { /** * 執行鏈式代理 */ Object doProxy(ProxyChain proxyChain) throws Throwable; }
這個接口中包括了一個doProxy方法,傳入一個ProxyChain,用於執行「鏈式代理」操做。ide
所謂鏈式代理,也就是說,可將多個代理經過一條鏈子串起來,一個個地區執行,執行順序取決於添加到鏈上的前後順序。this
package com.autumn.aop; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; /** * 代理鏈 */ public class ProxyChain { private final Class<?> targetClass; //代理類 private final Object targetObject; //目標對象 private final Method targetMethod; //目標方法 private final MethodProxy methodProxy; //方法代理 private final Object[] methodParams; //方法參數 private List<Proxy> proxyList = new ArrayList<Proxy>(); //代理列表 private int proxyIndex = 0; //代理索引 public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, MethodProxy methodProxy, Object[] methodParams, List<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()){ //若是代理索引小於代理列表大小 //從列表中取出相應的Proxy對象,調用器doProxy方法 methodResult = proxyList.get(proxyIndex++).doProxy(this); }else { //全部代理遍歷完後 methodResult = methodProxy.invokeSuper(targetObject,methodParams); //執行目標對象業務 } return methodResult; } }
在ProxyChain類中,咱們定義了一系列的成員變量,包括targetClass(目標類)、targetObject(目標對象)、targetMethod(目標方法)、methodProxy(方法代理)、methodParams(方法參數),此外還包括proxyList(代理列表)、proxyIndex(代理索引),這些成員變量在構造器中進行初始化,並提供了幾個重要的獲值方法。spa
須要注意的是MethodProxy這個類,它是CGLib開源項目爲咱們提供的一個方法代理對象,在doProxyChain方法中被使用。debug
doProxyChain方法中,proxyIndex來充當對象的計數器,若還沒有達到proxyList的上限,則從proxyList中取出相應的Proxy對象,並調用其doProxy方法。在Proxy接口中的實現中會提供相應的橫切邏輯,並調用doProxyChain方法,隨後將再次調用當前ProxyChain對象的doProxyChain方法,直到proxyIndex達到proxyList的上限爲止,最後調用methodProxy的invokeSuper方法,執行目標對象的業務邏輯。代理
在pom.xml中添加CGLib的Maven依賴:code
<!--不能超過3.0版本,這裏用2.2--> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency>
如今咱們須要寫一個類,讓它提供一個建立代理對象的方法,輸入一個目標類和一組Proxy接口實現,輸出一個代理對象,將該類命名爲ProxyManager,讓它來建立愛你全部的代理對象,代碼以下:
/** * 代理管理器 */ public class ProxyManager { public static <T> T createProxy(final Class<T> targetClass, final List<Proxy> proxyList){ return (T) Enhancer.create(targetClass, new MethodInterceptor() { @Override public Object intercept(Object targetObject, Method targetMethod, Object[] methodParams, MethodProxy methodProxy) throws Throwable { return new ProxyChain(targetClass,targetObject,targetMethod,methodProxy,methodParams,proxyList).doProxyChain(); } }); } }
使用CGLib提供的Enhancer.create()方法來建立代理對象,將intercept的參數傳入ProxyChain的構造器中便可。
誰來調用ProxyManager呢?固然是切面類了,由於在切面類中,須要在目標方法被調用的先後增長相應的邏輯。咱們有必要寫一個抽象類,讓它提供一個模板方法,並在該抽象類的具體實現中擴展相應的抽象方法。咱們將該抽象命名爲AspectProxy。代碼以下
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; /** * 切面代理 */ public abstract class AspectProxy implements Proxy{ private static final Logger logger = LoggerFactory.getLogger(AspectProxy.class); /** * 執行鏈式代理 */ @Override public Object doProxy(ProxyChain proxyChain) throws Throwable { Object result = null; Class<?> cls = proxyChain.getTargetClass(); Method method = proxyChain.getTargetMethod(); Object[] params = proxyChain.getMethodParams(); begin(); //代理開始時執行begin方法 try { if (intercept(cls,method,params)){ //是否攔截此方法 before(cls,method,params); //目標方法執行前代理方法before執行 result = proxyChain.doProxyChain(); //執行目標方法 after(cls,method,result); //目標方法執行後代理方法after執行 }else { result = proxyChain.doProxyChain(); //執行目標方法 } }catch(Exception e){ logger.error("proxy failure",e); error(cls,method,params,e); //拋出異常時代理方法error執行 throw e; }finally{ end(); //代理結束時執行方法end } return null; } /*方法開始前執行*/ public void begin(){ } /** * 攔截 * @param cls 目標類 * @param method 目標方法 * @param params 目標方法參數 * @return 返回是否攔截 */ public boolean intercept(Class<?> cls, Method method, Object[] params) throws Throwable { return true; } /** * 前置加強 * @param cls 目標類 * @param method 目標方法 * @param params 目標方法參數 */ public void before(Class<?> cls, Method method, Object[] params) throws Throwable { } /** * 後置加強 * @param cls 目標類 * @param method 目標方法 * @param result 目標方法返回結果 */ public void after(Class<?> cls, Method method, Object result) throws Throwable { } /** * 拋出加強 * @param cls 目標類 * @param method 目標方法 * @param params 目標方法參數 * @param e 異常 * @throws Throwable */ public void error(Class<?> cls, Method method, Object[] params, Exception e) throws Throwable { } /*方法結束後執行*/ public void end(){ } }
注意這裏的AspectProxy類中的doProxy方法,咱們從proxyChain參數中獲取了目標類、目標方法與方法參數,隨後經過一個try...catch...finally代碼塊來實現調用框架,從框架中抽象出一系列的「鉤子方法」,這些抽象方法可在AspectProxy的子類中有選擇性的實現,例如ControllerAspect
/** * 攔截全部Controller方法 */ @Aspect(Controller.class) public class ControllerAspect extends AspectProxy{ private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAspect.class); private long begin; //方法開始時間 /** * 前置加強 * @param cls 目標類 * @param method 目標方法 * @param params 目標方法參數 */ @Override public void before(Class<?> cls, Method method, Object[] params) throws Throwable { LOGGER.debug("---------begin---------"); LOGGER.debug(String.format("class: %s",cls.getName())); LOGGER.debug(String.format("method: %s",method.getName())); begin = System.currentTimeMillis(); } /** * 後置加強 * @param cls 目標類 * @param method 目標方法 * @param result 目標方法返回結果 */ @Override public void after(Class<?> cls, Method method, Object result) throws Throwable { LOGGER.debug(String.format("time: %ds", System.currentTimeMillis()-begin)); LOGGER.debug("---------end---------"); } }
這裏只實現了before與after方法,就能夠在目標方法執行先後添加其餘須要執行的代碼了。
那麼這樣就結束了嗎?固然不是。咱們須要在整個框架裏使用ProxyManager來建立代理對象,並將該代理對象放入框架底層的BeanMap中,隨後才能經過IOC將被代理的對象注入到其餘對象中。
按照以前的套路,爲了加載AOP框架,咱們須要一個名爲AopHelper的類,而後將其添加到HelperLoader類中。
在AOPHelper中咱們須要獲取全部的目標類及其被攔截的切面類實例,並經過ProxyManager.createProxy方法來建立代理對象,最後將其放入BeanMap中。
首先,須要爲BeanHelper類添加一個setBean方法,用於將Bean實例放入Bean Map中(將實例替換爲代理類用),代碼以下:
package org.smart4j.framework.helper; import org.smart4j.framework.util.ReflectionUtil; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Bean助手類 * */ public class BeanHelper { /** * 定義bean映射(用於存放Bean類與Bean實例的映射關係) */ private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>(); static{//獲取全部Controller和Service將類和實例放入Map中 } /** * 獲取Bean映射 * @return */ public static Map<Class<?>, Object> getBeanMap() { return BEAN_MAP; } /** * 根據Class獲取bean實例 * @param cls bean實例所屬的類 * @param <T> 類的實例對象 * @return */ public static <T> T getBean(Class<T> cls){} /** * 設置Bean實例 - 手動將cls - obj放入到BEANMAP中去(更多用於將實例替換爲代理對象) * @param cls 類 * @param obj 實例 */ public static void setBean(Class<?> cls,Object obj){ BEAN_MAP.put(cls,obj); } }
而後,咱們須要擴展AspectProxy抽象類的全部具體類getClassSetBySuper(),此外,還須要獲取帶有Aspect註解的全部類(即切面),所以須要在ClassHelper中添加一下兩個方法:
public class ClassHelper { /** * 定義類集合 */ private static final Set<Class<?>> CLASS_SET; static { String basePackage = ConfigHelper.getAppBasePackage(); CLASS_SET = ClassUtil.getClassSet(basePackage); } /** * 獲取應用包名下的全部類 * @return */ public static Set<Class<?>> getClassSet(){} /** * 獲取全部Controller類 * @return */ public static Set<Class<?>> getControllerClassSet(){} /** * 獲取全部Service類 * @return */ public static Set<Class<?>> getServiceClassSet(){} /** * 獲取應用包名下的全部bean類(Controller和Service) * @return */ public static Set<Class<?>> getBeanClassSet(){} /** * 獲取應用包名下某父類(接口)的全部子類(或實現類) * @param superClass * @return */ public static Set<Class<?>> getClassSetBySuper(Class<?> superClass){ Set<Class<?>> classSet = new HashSet<Class<?>>(); for (Class<?> cls:CLASS_SET){ if (superClass.isAssignableFrom(cls)&&superClass.equals(cls)){ classSet.add(cls); } } return classSet; } /** * 獲取應用包名下帶有註解的全部類 * @param annotationClass * @return */ public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass){ Set<Class<?>> classSet = new HashSet<Class<?>>(); for (Class<?> cls : CLASS_SET){ if (cls.isAnnotationPresent(annotationClass)){ classSet.add(cls); } } return classSet; } }
有了上面連個方法後,createTargetClassSet方法獲取Aspect註解中設置的註解類,若該註解不是Aspect類,則可調用ClassHelper.getClassSetByAnnotation方法獲取相應的類,並把這些類放入目標類集合中,最終返回這個集合。
而後咱們用createProxyMap方法獲取代理類及其目標類直接按的映射關係,一個代理類可對應一個或多個目標類。須要強調的是,這裏所說的代理類指的是切面類。
AOP順序:
代理類須要擴展AspectProxy抽象類(經過getClassSetBySuper獲取全部子類(即切面)),還須要帶有Aspect註解,只有知足這兩個條件,才能根據Aspect註解中所定義的註解屬性去獲取該註解所對應的目標類集合(經過createTargetClassSet獲取目標類集合),而後才能創建代理類與目標類集合之間的映射關係,最終返回這個映射關係。
一旦獲取了代理類與目標類集合之間的映射關係,createTargetMap方法就能根據這個關係分析出目標類與代理對象列表之間的映射關係。
最後經過AOPHelper的靜態代碼塊初始化整個AOP框架,獲取代理類及其目標類集合的映射關係,進一步獲取目標類與代理對象列表的映射關係,進而遍歷這個映射關係,從中獲取目標類與代理對象列表,調用ProxyManager.createProxy方法獲取代理對象,調用BeanHelper.setBean方法,將該代理對象從新放入BeanMap中。
import org.smart4j.framework.aop.Aspect; import org.smart4j.framework.aop.AspectProxy; import org.smart4j.framework.aop.Proxy; import org.smart4j.framework.aop.ProxyManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; import java.util.*; /** * 方法攔截助手類 */ public class AopHelper { private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class); static{ try{ Map<Class<?>,Set<Class<?>>> proxyMap = createProxyMap(); //獲取Map<代理類,Set<目標類>> Map<Class<?>,List<Proxy>> targetMap = createTargetMap(proxyMap); //獲取Map<目標類,List<代理實例>> for (Map.Entry<Class<?>,List<Proxy>> targetEntry:targetMap.entrySet()){ //遍歷Map<目標類,List<代理實例>> Class<?> targetClass = targetEntry.getKey(); //目標類 List<Proxy> proxyList = targetEntry.getValue(); //代理類 Object proxy = ProxyManager.createProxy(targetClass,proxyList); //根據目標類和代理集合建立一個代理 BeanHelper.setBean(targetClass,proxy); //將Bean容器中目標類對應的實體替換成代理 } }catch (Exception e){ LOGGER.error("aop failure",e); } } /** * 根據Aspect註解(切點)獲取全部的代理目標類集合(目標類爲Controller等註解的類) * @param aspect 代理類註解,用來指定目標類的註解 例如:@Aspect(Controller.class) * @return 返回Aspect註解中指定value註解的目標類 例如:帶Controller註解的全部類 * 例如Aspect(Controller.class)是指獲取全部Controller註解的類 */ private static Set<Class<?>> createTargetClassSet(Aspect aspect){ Set<Class<?>> targetClassSet = new HashSet<Class<?>>(); Class<? extends Annotation> annotation = aspect.value(); //獲取值(也是註解) if (annotation!=null && !annotation.equals(Aspect.class)){ //獲取的value註解不爲null,且註解不爲Aspect targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation)); //加入全部value(切點)指定的註解的類 } return targetClassSet; //返回全部目標類 } /** * 建立全部Map<代理類,Set<代理目標類>>的映射關係 * @return Map<代理類,Set<代理目標類>> * @throws Exception */ private static Map<Class<?>,Set<Class<?>>> createProxyMap() throws Exception{ Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>(); //結果集<代理類,Set<代理目標類>> //獲取全部的AspectProxy的子類(代理類集合),即切面, /*這個是入口,根據基類來查找全部的切面(代理類),獲取全部的切面!!!*/ Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class); for (Class<?> proxyClass : proxyClassSet){ //遍歷代理類(切面),如ControllerAspect if (proxyClass.isAnnotationPresent(Aspect.class)){ //驗證基類爲AspectProxy且要有Aspect註解的才能爲切面。若是代理類的的註解爲Aspect(也就是說代理類必定要都切點(註解)才能是切面),例如ControllerAspect代理類的註解爲@Aspect(Controller.class) Aspect aspect = proxyClass.getAnnotation(Aspect.class); //獲取代理類(切面)的註解 /*根據註解獲取全部的目標類*/ Set<Class<?>> targetClassSet = createTargetClassSet(aspect); //獲取全部的代理目標類集合 proxyMap.put(proxyClass,targetClassSet); //加入到結果集Map<代理類,Set<代理目標類>>中 } } return proxyMap; } /** * 將Map<代理類,Set<目標類>> proxyMap轉爲Map<目標類,List<代理類>> targetMap * @param proxyMap Map<代理類,Set<目標類>> * @return Map<目標類,List<代理類實例>> * @throws Exception */ private static Map<Class<?>,List<Proxy>> createTargetMap(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception{ Map<Class<?>,List<Proxy>> targetMap = new HashMap<Class<?>,List<Proxy>>(); //class - list鍵值對的map for (Map.Entry<Class<?>,Set<Class<?>>> proxyEntry:proxyMap.entrySet()){ //遍歷cls - set鍵值對的map Class<?> proxyClass = proxyEntry.getKey(); //獲取代理cls Set<Class<?>> targetClassSet = proxyEntry.getValue(); //獲取目標Set for (Class<?> targetClass:targetClassSet){ //遍歷目標Set Proxy proxy = (Proxy) proxyClass.newInstance(); //實例化代理類 if (targetMap.containsKey(targetClass)){ //若是Map<Class<?>,List<Proxy>>包含該目標類 targetMap.get(targetClass).add(proxy); //直接將代理類添加到對應目標類的Map中 }else{ List<Proxy> proxyList = new ArrayList<Proxy>(); //若是沒有 proxyList.add(proxy); targetMap.put(targetClass,proxyList); } } } return targetMap; } }
最後,須要注意的是,AOPHelper要在IOCHelper以前加載,由於首先須要經過AopHelper獲取代理對象,而後才能經過IOCHelper進行依賴注入。不然的話,IOCHelper先加載就會致使Bean容器中的是代理,而Bean中注入的是本來的實例對象。因此注入必定要最後進行(IOCHelper必定要最後加載)。