手寫Spring---AOP面向切面編程(3)

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

1、AOP分析

① AOP(Aspect Oriented Programming)是什麼?

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

② 咱們須要達成的目標?

向用戶提供咱們的AOP功能讓他們來進行對類方法的加強。
複製代碼

③ 提取到的信息與基本概念

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

2、AOP概念

① 請理解下方圖片:

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.在框架中已經實現
複製代碼

3、Aspect切面的實現

Aspect 分析:html

(1)Advice是由用戶提供咱們使用,且是多變的,那咱們如何能認識用戶提供的東西, (2)如何讓咱們的代碼隔絕用戶提供的多變?java

可否由咱們提供一套標準接口,用戶經過實現接口來提供他們不一樣的邏輯
應對變化---面向接口編程(好比JDBC,日誌等)
複製代碼

此時咱們做爲空殼接口來編寫便可,做爲加強功能的總標識spring

public interface Advice {
}
複製代碼

Advice設計

首先圍繞Advice的特色3,可選時機這塊,它能夠進行前置加強,後置加強,環繞加強,異常處理加強,這時咱們須要定義一個標準接口方法,讓用戶作到實現它就能夠進行加強。此時咱們須要考慮的因素有:express

四種加強所需的參數是否同樣?編程

(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的特色?---用戶指定並多點指定api

咱們須要爲用戶提供一個東西讓他們來靈活指定多個方法點,切入點就是這些點。那問題來了框架

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

1.指定方法,是否以方法做爲描述信息
2.如何指定方法?---XX類的XX方法
3.方法重載如何處理?---加上參數類型
複製代碼

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

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

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來寫

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

相關文章
相關標籤/搜索