接上一篇《手寫Spring---DI依賴注入(2)》繼續更新
一、AOP分析
① AOP(Aspect Oriented Programming)是什麼?
在不改變類的程式碼的前提下,對類方法進行功能增強。
複製程式碼
② 我們需要達成的目標?
向使用者提供我們的AOP功能讓他們來進行對類方法的增強。
複製程式碼
③ 提取到的資訊與基本概念
1.進行功能增強:功能---Advice 通知
2.對類方法增強:可選的增強方法---PointCut 切入點
3.不改原類程式碼:與原類解耦---Weaving 織入
複製程式碼
二、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.在框架中已經實現
複製程式碼
三、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來寫
比較純理論,程式碼不多且簡單,更多地還是要理清楚一些概念性的問題.不足之處請在評論處留言...望共同進步,望能在點滴積累中慢慢收穫成長