徒手擼框架--實現Aop

原文地址: 犀利豆的博客

上一講咱們講解了Spring 的 IoC 實現。你們能夠去個人博客查看點擊連接,這一講咱們繼續說說 Spring 的另一個重要特性 AOP。以前在看過的大部分教程,對於Spring Aop的實現講解的都不太透徹,大部分文章介紹了Spring Aop的底層技術使用了動態代理,至於Spring Aop的具體實現都語焉不詳。這類文章看之後之後,我腦子裏浮現的就是這樣一個畫面:java

個人想法就是,帶領你們,首先梳理 Spring Aop的實現,而後屏蔽細節,本身實現一個Aop框架。加深對Spring Aop的理解。在瞭解上圖1-4步驟的同時,補充 4 到 5 步驟之間的其餘細節。git

讀完這篇文章你將會了解:github

  • Aop是什麼?
  • 爲何要使用Aop?
  • Spirng 實現Aop的思路是什麼
  • 本身根據Spring 思想實現一個 Aop框架

Aop 是什麼?

面向切面的程序設計(aspect-oriented programming,AOP)。經過預編譯方式和運行期動態代理實現程序功能統一維護的一種技術。算法

爲何須要使用Aop?

面向切面編程,實際上就是經過預編譯或者動態代理技術在不修改源代碼的狀況下給原來的程序統一添加功能的一種技術。咱們看幾個關鍵詞,第一個是「動態代理技術」,這個就是Spring Aop實現底層技術。第二個「不修改源代碼」,這個就是Aop最關鍵的地方,也就是咱們平時所說的非入侵性。。第三個「添加功能」,不改變原有的源代碼,爲程序添加功能。spring

舉個例子:若是某天你須要統計若干方法的執行時間,若是不是用Aop技術,你要作的就是爲每個方法開始的時候獲取一個開始時間,在方法結束的時候獲取結束時間。兩者之差就是方法的執行時間。若是對每個須要統計的方法都作如上的操做,那代碼簡直就是災難。若是咱們使用Aop技術,在不修改代碼的狀況下,添加一個統計方法執行時間的切面。代碼就變得十分優雅。具體這個切面怎麼實現?看完下面的文章你必定就會知道。shell

Spring Aop 是怎麼實現的?

所謂:編程

計算機程序 = 數據結構 + 算法

在閱讀過Spring源碼以後,你就會對這個說法理解更深刻了。json

Spring Aop實現的代碼很是很是的繞。也就是說 Spring 爲了靈活作了很是深層次的抽象。同時 Spring爲了兼容 @AspectJ 的Aop協議,使用了不少 Adapter (適配器)模式又進一步的增長了代碼的複雜程度。
Spring 的 Aop 實現主要如下幾個步驟:設計模式

  1. 初始化 Aop 容器。
  2. 讀取配置文件。
  3. 將配置文件裝換爲 Aop 可以識別的數據結構 -- Advisor。這裏展開講一講這個advisor。Advisor對象中包又含了兩個重要的數據結構,一個是 Advice,一個是 PointcutAdvice的做用就是描述一個切面的行爲,pointcut描述的是切面的位置。兩個數據結的組合就是」在哪裏,幹什麼「。這樣 Advisor 就包含了」在哪裏幹什麼「的信息,就可以全面的描述切面了。
  4. Spring 將這個 Advisor 轉換成本身可以識別的數據結構 -- AdvicedSupport。Spirng 動態的將這些方法攔截器織入到對應的方法。
  5. 生成動態代理代理。
  6. 提供調用,在使用的時候,調用方調用的就是代理方法。也就是已經織入了加強方法的方法。

本身實現一個 Aop 框架

一樣,我也是參考了Aop的設計。只實現了基於方法的攔截器。去除了不少的實現細節。數據結構

使用上一講的 IoC 框架管理對象。使用 Cglib 做爲動態代理的基礎類。使用 maven 管理 jar 包和 module。因此上一講的 IoC 框架會做爲一個 modules 引入項目。

下面咱們就來實現咱們的Aop 框架吧。

首先來看看代碼的基本結構。

代碼結構比上一講的 IoC 複雜很多。咱們首先對包每一個包都幹了什麼作一個簡單介紹。

  • invocation 描述的就是一個方法的調用。注意這裏指的是「方法的調用」,而不是調用這個動做。
  • interceptor 你們最熟悉的攔截器,攔截器攔截的目標就是 invcation 包裏面的調用。
  • advisor 這個包裏的對象,都是用來描述切面的數據結構。
  • adapter 這個包裏面是一些適配器方法。對於"適配器"不瞭解的同窗能夠去看看"設計模式"裏面的"適配模式"。他的做用就是將 advice 包裏的對象適配爲 interceptor
  • bean 描述咱們 json 配置文件的對象。
  • core 咱們框架的核心邏輯。

這個時候宏觀的看咱們大概梳理出了一條路線, adaperadvisor 適配爲 interceptor 去攔截 invoction

下面咱們從這個鏈條的最末端講起:

invcation

首先 MethodInvocation 做爲全部方法調用的接口。要描述一個方法的調用包含三個方法,獲取方法自己getMethod,獲取方法的參數getArguments,還有執行方法自己proceed()

public interface MethodInvocation {
    Method getMethod();
    Object[] getArguments();
    Object proceed() throws Throwable;
}

ProxyMethodInvocation 看名字就知道,是代理方法的調用,增長了一個獲取代理的方法。

public interface ProxyMethodInvocation extends MethodInvocation {
    Object getProxy();
}

interceptor

AopMethodInterceptor 是 Aop 容器全部攔截器都要實現的接口:

public interface AopMethodInterceptor {
    Object invoke(MethodInvocation mi) throws Throwable;
}

同時咱們實現了兩種攔截器BeforeMethodAdviceInterceptorAfterRunningAdviceInterceptor,顧名思義前者就是在方法執行之前攔截,後者就在方法運行結束之後攔截:

public class BeforeMethodAdviceInterceptor implements AopMethodInterceptor {
    private BeforeMethodAdvice advice;
    public BeforeMethodAdviceInterceptor(BeforeMethodAdvice advice) {
        this.advice = advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        advice.before(mi.getMethod(),mi.getArguments(),mi);
        return mi.proceed();
    }
}
public class AfterRunningAdviceInterceptor implements AopMethodInterceptor {
    private AfterRunningAdvice advice;

    public AfterRunningAdviceInterceptor(AfterRunningAdvice advice) {
        this.advice = advice;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object returnVal = mi.proceed();
        advice.after(returnVal,mi.getMethod(),mi.getArguments(),mi);
        return returnVal;
    }
}

看了上面的代碼咱們發現,實際上 mi.proceed()纔是執行原有的方法。而advice咱們上文就說過,是描述加強的方法」幹什麼「的數據結構,因此對於這個before攔截器,咱們就把advice對應的加強方法放在了真正執行的方法前面。而對於after攔截器而言,就放在了真正執行的方法後面。

這個時候咱們過頭來看最關鍵的 ReflectioveMethodeInvocation

public class ReflectioveMethodeInvocation implements ProxyMethodInvocation {
    public ReflectioveMethodeInvocation(Object proxy, Object target, Method method, Object[] arguments, List<AopMethodInterceptor> interceptorList) {
        this.proxy = proxy;
        this.target = target;
        this.method = method;
        this.arguments = arguments;
        this.interceptorList = interceptorList;
    }

    protected final Object proxy;

    protected final Object target;

    protected final Method method;

    protected Object[] arguments = new Object[0];

    //存儲全部的攔截器
    protected final List<AopMethodInterceptor> interceptorList;

    private int currentInterceptorIndex = -1;

    @Override
    public Object getProxy() {
        return proxy;
    }

    @Override
    public Method getMethod() {
        return method;
    }

    @Override
    public Object[] getArguments() {
        return arguments;
    }

    @Override
    public Object proceed() throws Throwable {

        //執行完全部的攔截器後,執行目標方法
        if(currentInterceptorIndex == this.interceptorList.size() - 1) {
            return invokeOriginal();
        }

        //迭代的執行攔截器。回顧上面的講解,咱們實現的攔擊都會執行 im.proceed() 實際上又會調用這個方法。實現了一個遞歸的調用,直到執行完全部的攔截器。
        AopMethodInterceptor interceptor = interceptorList.get(++currentInterceptorIndex);
        return interceptor.invoke(this);

    }

    protected Object invokeOriginal() throws Throwable{
        return ReflectionUtils.invokeMethodUseReflection(target,method,arguments);
    }

}


在實際的運用中,咱們的方法極可能被多個方法的攔截器所加強。因此咱們,使用了一個list來保存全部的攔截器。因此咱們須要遞歸的去增長攔截器。當處理完了全部的攔截器以後,纔會真正調用調用被加強的方法。咱們能夠認爲,前文所述的動態的織入代碼就發生在這裏。

public class CglibMethodInvocation extends ReflectioveMethodeInvocation {

    private MethodProxy methodProxy;

    public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, List<AopMethodInterceptor> interceptorList, MethodProxy methodProxy) {
        super(proxy, target, method, arguments, interceptorList);
        this.methodProxy = methodProxy;
    }

    @Override
    protected Object invokeOriginal() throws Throwable {
        return methodProxy.invoke(target,arguments);
    }
}

CglibMethodInvocation 只是重寫了 invokeOriginal 方法。使用代理類來調用被加強的方法。

advisor

這個包裏面都是一些描述切面的數據結構,咱們講解兩個重要的。

@Data
public class Advisor {
    //幹什麼
    private Advice advice;
    //在哪裏
    private Pointcut pointcut;

}

如上文所說,advisor 描述了在哪裏,幹什麼。

@Data
public class AdvisedSupport extends Advisor {
    //目標對象
    private TargetSource targetSource;
    //攔截器列表
    private List<AopMethodInterceptor> list = new LinkedList<>();

    public void addAopMethodInterceptor(AopMethodInterceptor interceptor){
        list.add(interceptor);
    }

    public void addAopMethodInterceptors(List<AopMethodInterceptor> interceptors){
        list.addAll(interceptors);
    }

}

這個AdvisedSupport就是 咱們Aop框架可以理解的數據結構,這個時候問題就變成了--對於哪一個目標,增長哪些攔截器。

core

有了上面的準備,咱們就開始講解核心邏輯了。

@Data
public class CglibAopProxy implements AopProxy{
    private AdvisedSupport advised;
    private Object[] constructorArgs;
    private Class<?>[] constructorArgTypes;
    public CglibAopProxy(AdvisedSupport config){
        this.advised = config;
    }

    @Override
    public Object getProxy() {
        return getProxy(null);
    }
    @Override
    public Object getProxy(ClassLoader classLoader) {
        Class<?> rootClass = advised.getTargetSource().getTagetClass();
        if(classLoader == null){
            classLoader = ClassUtils.getDefultClassLoader();
        }
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(rootClass.getSuperclass());
        //增長攔截器的核心方法
        Callback callbacks = getCallBack(advised);
        enhancer.setCallback(callbacks);
        enhancer.setClassLoader(classLoader);
        if(constructorArgs != null && constructorArgs.length > 0){
            return enhancer.create(constructorArgTypes,constructorArgs);
        }
        return enhancer.create();
    }
    private Callback getCallBack(AdvisedSupport advised) {
        return new DynamicAdvisedIcnterceptor(advised.getList(),advised.getTargetSource());
    }
}

CglibAopProxy就是咱們代理對象生成的核心方法。使用 cglib 生成代理類。咱們能夠與以前ioc框架的代碼。比較發現區別就在於:

Callback callbacks = getCallBack(advised);
    enhancer.setCallback(callbacks);

callback與以前不一樣了,而是寫了一個getCallback()的方法,咱們就來看看 getCallback 裏面的 DynamicAdvisedIcnterceptor到底幹了啥。

篇幅問題,這裏不會介紹 cglib 的使用,對於callback的做用,不理解的同窗須要自行學習。

public class DynamicAdvisedInterceptor implements MethodInterceptor{

    protected final List<AopMethodInterceptor> interceptorList;
    protected final TargetSource targetSource;

    public DynamicAdvisedInterceptor(List<AopMethodInterceptor> interceptorList, TargetSource targetSource) {
        this.interceptorList = interceptorList;
        this.targetSource = targetSource;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        MethodInvocation invocation = new CglibMethodInvocation(obj,targetSource.getTagetObject(),method, args,interceptorList,proxy);
        return invocation.proceed();
    }
}

這裏須要注意,DynamicAdvisedInterceptor這個類實現的 MethodInterceptor 是 gclib的接口,並不是咱們以前的 AopMethodInterceptor。

咱們近距離觀察 intercept 這個方法咱們看到:

MethodInvocation invocation = new CglibMethodInvocation(obj,targetSource.getTagetObject(),method, args,interceptorList,proxy);

經過這行代碼,咱們的整個邏輯終於連起來了。也就是這個動態的攔截器,把咱們經過 CglibMethodInvocation 織入了加強代碼的方法,委託給了 cglib 來生成代理對象。

至此咱們的 Aop 的核心功能就實現了。

AopBeanFactoryImpl

public class AopBeanFactoryImpl extends BeanFactoryImpl{

    private static final ConcurrentHashMap<String,AopBeanDefinition> aopBeanDefinitionMap = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String,Object> aopBeanMap = new ConcurrentHashMap<>();

    @Override
    public Object getBean(String name) throws Exception {
        Object aopBean = aopBeanMap.get(name);

        if(aopBean != null){
            return aopBean;
        }
        if(aopBeanDefinitionMap.containsKey(name)){
            AopBeanDefinition aopBeanDefinition = aopBeanDefinitionMap.get(name);
            AdvisedSupport advisedSupport = getAdvisedSupport(aopBeanDefinition);
            aopBean = new CglibAopProxy(advisedSupport).getProxy();
            aopBeanMap.put(name,aopBean);
            return aopBean;
        }

        return super.getBean(name);
    }
    protected void registerBean(String name, AopBeanDefinition aopBeanDefinition){
        aopBeanDefinitionMap.put(name,aopBeanDefinition);
    }

    private AdvisedSupport getAdvisedSupport(AopBeanDefinition aopBeanDefinition) throws Exception {

        AdvisedSupport advisedSupport = new AdvisedSupport();
        List<String> interceptorNames = aopBeanDefinition.getInterceptorNames();
        if(interceptorNames != null && !interceptorNames.isEmpty()){
            for (String interceptorName : interceptorNames) {

                Advice advice = (Advice) getBean(interceptorName);

                Advisor advisor = new Advisor();
                advisor.setAdvice(advice);

                if(advice instanceof BeforeMethodAdvice){
                    AopMethodInterceptor interceptor = BeforeMethodAdviceAdapter.getInstants().getInterceptor(advisor);
                    advisedSupport.addAopMethodInterceptor(interceptor);
                }

                if(advice instanceof AfterRunningAdvice){
                    AopMethodInterceptor interceptor = AfterRunningAdviceAdapter.getInstants().getInterceptor(advisor);
                    advisedSupport.addAopMethodInterceptor(interceptor);
                }

            }
        }

        TargetSource targetSource = new TargetSource();
        Object object = getBean(aopBeanDefinition.getTarget());
        targetSource.setTagetClass(object.getClass());
        targetSource.setTagetObject(object);
        advisedSupport.setTargetSource(targetSource);
        return advisedSupport;

    }

}

AopBeanFactoryImpl是咱們產生代理對象的工廠類,繼承了上一講咱們實現的 IoC 容器的BeanFactoryImpl。重寫了 getBean方法,若是是一個切面代理類,咱們使用Aop框架生成代理類,若是是普通的對象,咱們就用原來的IoC容器進行依賴注入。
getAdvisedSupport就是獲取 Aop 框架認識的數據結構。

剩下沒有講到的類都比較簡單,你們看源碼就行。與核心邏輯無關。

寫個方法測試一下

咱們須要統計一個方法的執行時間。面對這個需求咱們怎麼作?

public class StartTimeBeforeMethod implements BeforeMethodAdvice{
    @Override
    public void before(Method method, Object[] args, Object target) {
        long startTime = System.currentTimeMillis();
        System.out.println("開始計時");
        ThreadLocalUtils.set(startTime);
    }
}
public class EndTimeAfterMethod implements AfterRunningAdvice {
    @Override
    public Object after(Object returnVal, Method method, Object[] args, Object target) {
        long endTime = System.currentTimeMillis();
        long startTime = ThreadLocalUtils.get();
        ThreadLocalUtils.remove();
        System.out.println("方法耗時:" + (endTime - startTime) + "ms");
        return returnVal;
    }
}

方法開始前,記錄時間,保存到 ThredLocal裏面,方法結束記錄時間,打印時間差。完成統計。

目標類:

public class TestService {
    public void testMethod() throws InterruptedException {
        System.out.println("this is a test method");
        Thread.sleep(1000);
    }
}

配置文件:

[
  {
    "name":"beforeMethod",
    "className":"com.xilidou.framework.aop.test.StartTimeBeforeMethod"
  },
  {
    "name":"afterMethod",
    "className":"com.xilidou.framework.aop.test.EndTimeAfterMethod"
  },
  {
    "name":"testService",
    "className":"com.xilidou.framework.aop.test.TestService"
  },
  {
    "name":"testServiceProxy",
    "className":"com.xilidou.framework.aop.core.ProxyFactoryBean",
    "target":"testService",
    "interceptorNames":[
      "beforeMethod",
      "afterMethod"
    ]
  }
]

測試類:

public class MainTest {
    public static void main(String[] args) throws Exception {
        AopApplictionContext aopApplictionContext = new AopApplictionContext("application.json");
        aopApplictionContext.init();
        TestService testService = (TestService) aopApplictionContext.getBean("testServiceProxy");
        testService.testMethod();
    }
}

最終咱們的執行結果:

開始計時
this is a test method
方法耗時:1015ms

Process finished with exit code 0

至此 Aop 框架完成。

後記

Spring 的兩大核心特性 IoC 與 Aop 兩大特性就講解完了,但願你們經過我寫的兩篇文章可以深刻理解兩個特性。

Spring的源碼實在是複雜,閱讀起來經常給人極大的挫敗感,可是隻要可以堅持,並採用一些行之有效的方法。仍是可以理解Spring的代碼。而且從中汲取養分。

下一篇文章,我會給你們講講閱讀開源代碼的一些方法和我本身的體會,敬請期待。

最後

github:https://github.com/diaozxin00...

相關文章
相關標籤/搜索