使用代理鏈自己實現(xiàn)AOP框架

寫在前面

今年寒假已經(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();
              }
          });
      }
    }
    

對代理鏈的理解

  1. 我們首先從 ProxyManager.createProxy()方法入手,該方法的目的就是創(chuàng)建代理類出爹,使用CGLib框架創(chuàng)建代理類時庄吼,需要傳入一個callback,這個callback會攔截目標類原方法的執(zhí)行,轉(zhuǎn)而執(zhí)行intercept()方法严就。

  2. 進一步查看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;
  }
  1. 查看切面類中的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;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市裳瘪,隨后出現(xiàn)的幾起案子土浸,更是在濱河造成了極大的恐慌,老刑警劉巖彭羹,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄伊,死亡現(xiàn)場離奇詭異,居然都是意外死亡皆怕,警方通過查閱死者的電腦和手機毅舆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愈腾,“玉大人憋活,你說我怎么就攤上這事∈疲” “怎么了悦即?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長橱乱。 經(jīng)常有香客問我辜梳,道長,這世上最難降的妖魔是什么泳叠? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任作瞄,我火速辦了婚禮,結(jié)果婚禮上危纫,老公的妹妹穿的比我還像新娘宗挥。我一直安慰自己乌庶,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布契耿。 她就那樣靜靜地躺著瞒大,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搪桂。 梳的紋絲不亂的頭發(fā)上透敌,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音踢械,去河邊找鬼酗电。 笑死,一個胖子當著我的面吹牛裸燎,可吹牛的內(nèi)容都是我干的顾瞻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼德绿,長吁一口氣:“原來是場噩夢啊……” “哼荷荤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起移稳,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蕴纳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后个粱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體古毛,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年都许,在試婚紗的時候發(fā)現(xiàn)自己被綠了稻薇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胶征,死狀恐怖塞椎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睛低,我是刑警寧澤案狠,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站钱雷,受9級特大地震影響骂铁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罩抗,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一拉庵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧套蒂,春花似錦名段、人聲如沸阱扬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馍刮,卻和暖如春信夫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卡啰。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工静稻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匈辱。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓振湾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亡脸。 傳聞我的和親對象是個殘疾皇子押搪,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354