手寫Spring---AOP面向切面程式設計(3)

說出你的願望吧~發表於2019-04-26

接上一篇《手寫Spring---DI依賴注入(2)》繼續更新

一、AOP分析

① AOP(Aspect Oriented Programming)是什麼?

在不改變類的程式碼的前提下,對類方法進行功能增強。
複製程式碼

② 我們需要達成的目標?

向使用者提供我們的AOP功能讓他們來進行對類方法的增強。
複製程式碼

③ 提取到的資訊與基本概念

1.進行功能增強:功能---Advice 通知
2.對類方法增強:可選的增強方法---PointCut 切入點
3.不改原類程式碼:與原類解耦---Weaving 織入
複製程式碼

二、AOP概念

① 請理解下方圖片:

手寫Spring---AOP面向切面程式設計(3)

Advice:通知,增強的功能
Join Point:連線點,可選的方法點
Pointcut:切入點,選擇切入的方法點
Aspect:切面,選擇的(多個)方法點+增強的功能
Introduction:引入,新增新的方法或屬性到已存在的類中
Weaving:織入,不改變原類的程式碼進行增強
複製程式碼

② 以上提到的概念哪些是需要使用者提供,又哪些需要我們完成

其中 Advice, Join Point,Pointcut,Aspect 都是使用者提供我們使用
Weaving 需要我們自己實現
Introduction 在spring中存在但是很少涉及

ps:提供者和使用者的概念在設計模式中經常涉及
複製程式碼

③ Advice,Pointcut,Weaving各自有什麼特點?

Advice:
    1.由使用者提供增強功能邏輯程式碼
    2.不同的增強需求會存在不同的邏輯
    3.可選時機,在方法前後或異常時進行增強
    4.同一個切入點,可能存在多個增強
Pointcut
    1.使用者指定切入點
    2.使用者可在多點進行增強
Weaving
    1.不改變原類程式碼
    2.在框架中已經實現
複製程式碼

三、Aspect切面的實現

Aspect 分析:

(1)Advice是由使用者提供我們使用,且是多變的,那我們如何能認識使用者提供的東西, (2)如何讓我們的程式碼隔絕使用者提供的多變?

能否由我們提供一套標準介面,使用者通過實現介面來提供他們不同的邏輯
應對變化---面向介面程式設計(比如JDBC,日誌等)
複製程式碼

此時我們作為空殼介面來編寫即可,作為增強功能的總標識

public interface Advice {
}
複製程式碼

Advice設計

首先圍繞Advice的特點3,可選時機這塊,它可以進行前置增強,後置增強,環繞增強,異常處理增強,這時我們需要定義一個標準介面方法,讓使用者做到實現它就可以進行增強。此時我們需要考慮的因素有:

四種增強所需的引數是否一樣?

(1)Advice的前置增強

Q1:前置增強可能需要的引數

目的是對方法進行增強,應該需要提供的是方法相關的資訊,我們也僅能提供有關方法的資訊
其中方法包含的資訊有:
1.方法本身:Method
2.方法所屬物件:Object
3.方法的引數:Object[]
複製程式碼

Q2:前置增強方法的返回值

在方法執行前進行增強,不需要任何的返回值
複製程式碼

(2)Advice的後置增強

Q1:後置增強所需要的引數

1.方法本身:Method
2.方法所屬物件:Object
3.方法的引數:Object[]
4.方法的返回值:Object
複製程式碼

Q2:後置增強的方法返回值?

方法執行後進行增強也不需要返回值
複製程式碼

(3)Advice的環繞增強

Q1:環繞增強所需要的引數

1.方法本身:Method
2.方法所屬物件:Object
3.方法的引數:Object[]
複製程式碼

Q2:環繞增強的方法返回值?

方法被它包裹,也就是本身類方法的執行需要這個方法的執行邏輯來帶動執行,
所以它需要返回方法的返回值Object
複製程式碼

(4)Advice的異常處理增強

Q1:異常處理增強所需的引數

異常資訊
複製程式碼

Q2:異常處理增強的返回值?

已經異常了···
複製程式碼

Q3:進行異常處理增強需要包裹方法嗎?

需要的,正常來說是使用try-catch來根據不同的異常來進行不同的處理,
就是在不同的catch中進行不同的增強處理,那其實就是可以藉助環繞增強的效果來實現
複製程式碼

(5)介面設計

經過前面的分析,我們已經可以總結出我們所需要的三個方法

Q1:三個方法是一個介面中定義還是分開三個介面更好?

分三個介面,此時還可以通過型別來區分不同的Advice
複製程式碼
MethodBeforeAdvice.java
public interface MethodBeforeAdvice extends Advice{
    /**
     * 實現方法的前置增強
     *
     * @param method    被增強的方法
     * @param args  方法的引數
     * @param target    被增強的目標物件
     * @throws Throwable
     */
    void before(Method method,Object[] args,Object target) throws Throwable;
}
複製程式碼
AfterReturnAdvice.java
public interface AfterReturnAdvice extends Advice {
    /**
     * 實現方法的後置增強
     *
     * @param returnValue   返回值
     * @param method    被增強的方法
     * @param args  方法的引數
     * @param target    方法的目標物件
     * @throws Throwable
     */
    void afterReturn(Object returnValue, Method method,Object[] args,Object target) throws Throwable;
}
複製程式碼
MethodSurroudAdvice.java
public interface MethodSurroudAdvice extends Advice {
    /**
     * 對方法進行環繞增強還有異常處理的增強
     *
     * @param method
     * @param args
     * @param target
     * @return
     */
    Object invoke(Method method,Object[] args,Object target);
}
複製程式碼

Pointcut的分析

Pointcut的特點?---使用者指定並多點指定

我們需要為使用者提供一個東西讓他們來靈活指定多個方法點,切入點就是這些點。那問題來了

如何來完成對這個切入點的判斷呢?

1.指定方法,是否以方法作為描述資訊
2.如何指定方法?---XX類的XX方法
3.方法過載如何處理?---加上引數型別
複製程式碼

此時是否有感覺,這些東西剛好就組成了一個完整的方法簽名呢!

如何做到多點性與靈活性?在一個描述中指定一類類的某些方法

ps:一類類的某些方法,比如說,某個包或者某個類的所有方法,所有類中do開頭的方法,所有類中帶有service的方法等等

我們需要一個表示式來描述這些資訊
包名:有父子特點,要能模糊匹配
類名:模糊匹配
方法名與引數型別:模糊匹配,引數可以多個

我們常見的表示式,比如正則(其實也是可行),Ant Path,
AspectJ裡面的pointcut(首選,因為AspectJ本身就是面向切面程式設計的元件)
複製程式碼

補充:AspectJ的語法

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) 
throws-pattern?)
複製程式碼

通過spring的官網可以進行學習:
docs.spring.io/spring/docs…


Pointcut的設計分析

① 切點Pointcut應該具備哪些屬性?

切點定義表示式
複製程式碼

② 切點Pointcut應該對外提供什麼行為?

③ 我們如何來使用切點Pointcut?

對類,方法進行匹配
切點應提供匹配類,匹配方法的行為
複製程式碼

④ 如果在我們的設計框架中要能靈活擴充套件切點的實現方式,我們該如何設計?

支援可變性問題需要由我們來定義一個標準介面,定義好基本行為,面向介面程式設計
遮蔽掉具體的實現.(像實現Advice一樣)
複製程式碼

所以我們設計一個Pointcut的標準介面

public interface Pointcut {
    //提供兩個方法,匹配類和匹配方法
    boolean matchClass(Class<?> targetClass);
    boolean matchMethod(Method method,Class<?> targetClass);
}
複製程式碼

⑤ 基於AspectJ的pointcut實現---AspectJExpressionPointcut.java

public class AspectJExpressionPointcut implements Pointcut{

    private String expression;

    public AspectJExpressionPointcut(String expression){
        this.expression = expression;
    }

    public String getExpression(){
        return this.expression;
    }

    @Override
    public boolean matchClass(Class<?> targetClass) {
        return false;
    }

    @Override
    public boolean matchMethod(Method method, Class<?> targetClass) {
        return false;
    }
}
複製程式碼

此時我們完成了一個空殼實現,還需引入AspectJ的jar包來完成切點表示式的實現,Spring AOP也僅僅使用了AspectJ的表示式api

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.10</version>
</dependency>
複製程式碼

大致理解下AspectJ的簡單應用,我們應該執行的步驟是:

(1) 獲得切點直譯器 org.aspectj.weaver.tools.PointcutParser
PointcutParser pp = PointcutParser
	.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
複製程式碼
(2) 解析表示式,得到org.aspectj.weaver.tools.PointcutExpression
PointcutExpression pe = 
    pp.parsePointcutExpression(expression)
複製程式碼
(3) 用PointcutExpression匹配類是不可靠的,所以需要匹配方法
pe.couldMatchJoinPointsInType(targetClass)為匹配類的方法,但有匹配不準確的問題
所以我們需要匹配方法的api
pe.matchesMethodExecution(method)
然後使用ShadowMatch類中的alwaysMatches()方法即可
複製程式碼

⑥ 加入AspectJ的api後的實現

public class AspectJExpressionPointcut implements Pointcut{

    //得到了一個全域性的切點解析器
    private static PointcutParser pp = PointcutParser
            .getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();

    private String expression;

    private PointcutExpression pe;

    public AspectJExpressionPointcut(String expression) {
        super();
        this.expression = expression;
        //解析成對應的表示式物件
        pe = pp.parsePointcutExpression(expression);
    }

    @Override
    public boolean matchClass(Class<?> targetClass) {
        return pe.couldMatchJoinPointsInType(targetClass);
    }

    @Override
    public boolean matchMethod(Method method, Class<?> targetClass) {
        ShadowMatch sm = pe.matchesMethodExecution(method);
        return sm.alwaysMatches();
    }

    public String getExpression() {
        return expression;
    }
}
複製程式碼

Aspect 的設計

為了給使用者提供操作優化,我們設計一個Advisor把Advice和Pointcut組合起來,當使用者使用aspectJ來指定他的切入點時,就只需指定adviceBeanName,expression即可

① 通用Advisor

    public interface Advisor {
        String getAdviceBeanName();
        String getExpression();
    }
複製程式碼

② 基於AspectJ語法的切面實現

public class AspectJPointcutAdvisor implements Advisor{
    private String adviceBeanName;
    private String expression;
    private AspectJExpressionPointcut pointcut;
    
    public AspectJPointcutAdvisor(String adviceBeanName, String expression) {
        super();
        this.adviceBeanName = adviceBeanName;
        this.expression = expression;
        this.pointcut = new AspectJExpressionPointcut(this.expression);
    }

    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    public String getAdviceBeanName() {
        return this.adviceBeanName;
    }

    @Override
    public String getExpression() {
        return this.expression;
    }
}
複製程式碼

由於AOP的內容比較多而上一篇DI的篇幅過長的問題,所以分2P來寫

比較純理論,程式碼不多且簡單,更多地還是要理清楚一些概念性的問題.不足之處請在評論處留言...望共同進步,望能在點滴積累中慢慢收穫成長

相關文章