? ? ? ?AOP全稱Aspect Oriented Programming。在OOP
(面向?qū)ο蟪绦蛟O(shè)計(jì))中,正是這種分散在各處且與對象核心功能無關(guān)的代碼(橫切代碼)的存在,使得模塊復(fù)用難度增加。AOP則將封裝好的對象剖開,找出其中對多個(gè)對象產(chǎn)生影響的公共行為,并將其封裝為一個(gè)可重用的模塊,這個(gè)模塊被命名為“切面”(Aspect),切面將那些與業(yè)務(wù)無關(guān),卻被業(yè)務(wù)模塊共同調(diào)用的邏輯提取并封裝起來,減少了系統(tǒng)中的重復(fù)代碼,降低了模塊間的耦合度,同時(shí)提高了系統(tǒng)的可維護(hù)性。
? ? ? ?AOP(Aspect-OrientedProgramming,面向方面編程),可以說是OOP(Object-Oriented Programing,面向?qū)ο缶幊蹋┑难a(bǔ)充和完善。OOP引入封裝、繼承和多態(tài)性等概念來建立一種對象層次結(jié)構(gòu),用以模擬公共行為的一個(gè)集合。當(dāng)我們需要為分散的對象引入公共行為的時(shí)候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關(guān)系,但并不適合定義從左到右的關(guān)系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關(guān)系。對于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也是如此。這種散布在各處的無關(guān)的代碼被稱為橫切(cross-cutting)代碼,在OOP設(shè)計(jì)中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用。
一 AOP編程相關(guān)名詞
切面(Aspect):在代碼體系中,對象與對象之間,方法與方法之間,模塊與模塊之間都可以認(rèn)為是一個(gè)個(gè)的切面。Spring中通過@Aspect:來描述一個(gè)切面。
連接點(diǎn)(Joinpoint):在程序執(zhí)行過程中某個(gè)特定的點(diǎn),比如某方法調(diào)用的時(shí)候或者處理異常的時(shí)候。在Spring AOP中,一個(gè)連接點(diǎn)總是表示一個(gè)方法的執(zhí)行。
通知(Advice):在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作。其中包括了“around”、“before”和“after”等不同類型的通知。許多AOP框架(包括Spring)都是以攔截器做通知模型,并維護(hù)一個(gè)以連接點(diǎn)為中心的攔截器鏈。
切入點(diǎn)(Pointcut):定義在什么時(shí)候切人方法。匹配連接點(diǎn)的斷言。通知和一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián),并在滿足這個(gè)切入點(diǎn)的連接點(diǎn)上運(yùn)行(例如,當(dāng)執(zhí)行某個(gè)特定名稱的方法時(shí))。切入點(diǎn)表達(dá)式如何和連接點(diǎn)匹配是AOP的核心:Spring缺省使用AspectJ切入點(diǎn)語法。Spring里面通過@Pointcut來引入切入點(diǎn)。
引入(Introduction):用來給一個(gè)類型聲明額外的方法或?qū)傩裕ㄒ脖环Q為連接類型聲明(inter-type declaration))。Spring允許引入新的接口(以及一個(gè)對應(yīng)的實(shí)現(xiàn))到任何被代理的對象。例如,你可以使用引入來使一個(gè)bean實(shí)現(xiàn)IsModified接口,以便簡化緩存機(jī)制。
目標(biāo)對象(Target Object):被一個(gè)或者多個(gè)切面所通知的對象。也被稱做被通知(advised)對象。既然Spring AOP是通過運(yùn)行時(shí)代理實(shí)現(xiàn)的,這個(gè)對象永遠(yuǎn)是一個(gè)被代理(proxied)對象。
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對象,用來實(shí)現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中,AOP代理可以是JDK動(dòng)態(tài)代理或者CGLIB代理。
織入(Weaving):把切面連接到其它的應(yīng)用程序類型或者對象上,并創(chuàng)建一個(gè)被通知的對象。這些可以在編譯時(shí)(例如使用AspectJ編譯器),類加載時(shí)和運(yùn)行時(shí)完成。Spring和其他純Java AOP框架一樣,在運(yùn)行時(shí)完成織入。
二 連接點(diǎn)(Joinpoint)
? ? ? ?連接點(diǎn)是在應(yīng)用執(zhí)行過程中能夠插入切面的一個(gè)點(diǎn),這個(gè)點(diǎn)可以是調(diào)用方法時(shí),拋出異常時(shí),甚至是修改一個(gè)字段時(shí),切面代碼可以利用這些連接點(diǎn)插入到應(yīng)用的正常流程中,并添加新的行為,如日志、安全、事務(wù)、緩存等。具體代碼中,連接點(diǎn)體現(xiàn)在每個(gè)通知方法的參數(shù)中。
比如如下前置@Before通知方法的第一個(gè)參數(shù)就是連接點(diǎn),通過連接點(diǎn)我們可以獲取到一些上下文的信息。
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
? ? ? ?@Before、@After、@AfterReturning、@AfterThrowing都是使用的org.aspectj.lang.JoinPoint接口表示目標(biāo)類連接點(diǎn)對象,@Around使用org.aspectj.lang.ProceedingJoinPoint表示連接點(diǎn)對象。兩個(gè)接口里面的方法也不復(fù)雜。主要方法如下。
JoinPoint接口主要方法如下
public interface JoinPoint {
/**
* 獲取代理對象本身
*/
Object getThis();
/**
* 獲取連接點(diǎn)所在的目標(biāo)對象
*/
Object getTarget();
/**
* 獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜? */
Object[] getArgs();
/** 獲取連接點(diǎn)的方法簽名對象,進(jìn)而可以獲取方法的名字,方法修飾符這些
*/
Signature getSignature();
/**
* 獲取連接點(diǎn)方法在文件中的信息,比如文件中第幾行啥的
*/
SourceLocation getSourceLocation();
/** 獲取連接點(diǎn)的方法的類型
*/
String getKind();
/**
* 獲取封裝連接點(diǎn)的一個(gè)對象
*/
JoinPoint.StaticPart getStaticPart();
}
ProceedingJoinPoint主要方法如下,ProceedingJoinPoint繼承自JoinPoint
public interface ProceedingJoinPoint extends JoinPoint {
/**
* 這個(gè)函數(shù)咱們不能直接調(diào)用,不管他
*/
void set$AroundClosure(AroundClosure arc);
/**
* P通過反射執(zhí)行目標(biāo)對象的連接點(diǎn)處的方法
*/
public Object proceed() throws Throwable;
/**
* 通過反射執(zhí)行目標(biāo)對象連接點(diǎn)處的方法,不過使用新的入?yún)⑻鎿Q原來的入?yún)? */
public Object proceed(Object[] args) throws Throwable;
}
三 通知(Advice)
? ? ? ?通知定義了切面是什么以及何時(shí)調(diào)用,何時(shí)調(diào)用。通知包含以下幾種:
| Advice | Spring注解 | 解釋 |
|---|---|---|
| Before | @Before | 前置通知,在方法被調(diào)用之前調(diào)用 |
| After | @After | 最終通知, 在方法完成之后調(diào)用,無論方法執(zhí)行是否成功 |
| After-returning | @AfterReturning | 后置通知,在方法成功執(zhí)行之后調(diào)用 |
| After-throwing | @AfterThrowing | 異常通知,在方法拋出異常后調(diào)用 |
| Around | @Around | 環(huán)繞通知, 包圍一個(gè)連接點(diǎn)的通知 |
? ? ? ?@Before、@After、@AfterReturning、@AfterThrowing這幾個(gè)通知應(yīng)該都還好理解。就@Around稍稍復(fù)雜一點(diǎn)。
? ? ? ?環(huán)繞通知:包圍一個(gè)連接點(diǎn)的通知,如方法調(diào)用。這是最強(qiáng)大的一種通知類型。環(huán)繞通知可以在方法調(diào)用前后完成自定義的行為。它也會(huì)選擇是否繼續(xù)執(zhí)行連接點(diǎn)或直接返回它自己的返回值或拋出異常來結(jié)束執(zhí)行。
? ? ? ?環(huán)繞通知最麻煩,也最強(qiáng)大,其是一個(gè)對方法的環(huán)繞,具體方法會(huì)通過代理傳遞到切面中去,切面中可選擇執(zhí)行方法與否,執(zhí)行方法幾次等。
? ? ? ?環(huán)繞通知使用一個(gè)代理ProceedingJoinPoint類型的對象來管理目標(biāo)對象,所以此通知的第一個(gè)參數(shù)必須是ProceedingJoinPoint類型,在通知體內(nèi),調(diào)用ProceedingJoinPoint的proceed()方法會(huì)導(dǎo)致后臺(tái)的連接點(diǎn)方法執(zhí)行。proceed 方法也可能會(huì)被調(diào)用并且傳入一個(gè)Object[]對象-該數(shù)組中的值將被作為方法執(zhí)行時(shí)的參數(shù)。
四 切點(diǎn)(Pointcut)
? ? ? ?切點(diǎn)定義了何處,切點(diǎn)的定義會(huì)匹配通知所要織入的一個(gè)或多個(gè)連接點(diǎn),我們通常使用明確的類的方法名稱來指定這些切點(diǎn),或是利用正則表達(dá)式定義匹配的類和方法名稱來指定這些切點(diǎn)。
? ? ? ?切點(diǎn)(Pointcut)的使用關(guān)鍵在切點(diǎn)表達(dá)式,一般由下列方式來定義或者通過 &&、 ||、 !、 的方式進(jìn)行組合:
| 切入點(diǎn)表達(dá)式指示符 | 解釋 |
|---|---|
| execution | 匹配子表達(dá)式(匹配方法執(zhí)行) |
| within | 匹配連接點(diǎn)所在的Java類或者包 |
| this | 用于向通知方法中傳入代理對象的引用 |
| target | 用于向通知方法中傳入目標(biāo)對象的引用 |
| args | 用于將參數(shù)傳入到通知方法中 |
| @within | 匹配在類一級(jí)使用了參數(shù)確定的注解的類,其所有方法都將被匹配(在類上添加注解) |
| @target | 和@within的功能類似,但必須要指定注解接口的保留策略為RUNTIME |
| @args | 傳入連接點(diǎn)的對象對應(yīng)的Java類必須被@args指定的Annotation注解標(biāo)注 |
| @annotation | 匹配當(dāng)前執(zhí)行方法持有指定注解的方法 |
咱們可以簡單的認(rèn)為,通知(Advice)定義了什么時(shí)候調(diào)用,切點(diǎn)(Pointcut)定義了哪個(gè)地方。一個(gè)指定了when,另一個(gè)指定了where。
4.1 execution
? ? ? ?"execution(方法表達(dá)式)":匹配方法執(zhí)行的連接點(diǎn)。
? ? ? ?execution方法表達(dá)式語法如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern.?name-pattern(param-pattern)throws-pattern?)
修飾符匹配(modifier-pattern?): 可選。修飾符:public、private、protected。
返回值匹配(ret-type-pattern): 必填。匹配返回值類類型。也可以為*表示任何返回值。
類路徑匹配(declaring-type-pattern.?): 可選。匹配類名。 (相當(dāng)于某個(gè)類下面的哪個(gè)方法。注意:后面是有一個(gè)點(diǎn)號(hào)的,然后才接的方法名)
方法名匹配(name-pattern): 必填。匹配方法名 也可以為表示代表所有方法, 還可以類似set的形式,代表以set開頭的所有方法。
參數(shù)匹配((param-pattern)): 必填。 匹配具體的參數(shù)類型,多個(gè)參數(shù)間用“,”隔開,各個(gè)參數(shù)也可以用“”來表示匹配任意類型的參數(shù),如(String)表示匹配一個(gè)String參數(shù)的方法;(,String) 表示匹配有兩個(gè)參數(shù)的方法,第一個(gè)參數(shù)可以是任意類型,而第二個(gè)參數(shù)是String類型;可以用(..)表示零個(gè)或多個(gè)任意參數(shù)。
異常類型匹配(throws-pattern?): 可選。函數(shù)拋出異常類型匹配。
@Pointcut execution 表達(dá)式其實(shí)是很好理解的,咱們可以把他看成一個(gè)函數(shù)就好了,函數(shù)有修飾符、返回值、方法名、參數(shù)、異常。declaring-type-pattern? 和 name-pattern 共同組合匹配方法匹配方法。 如果寫了declaring-type-pattern注意,后面要帶一個(gè)點(diǎn)號(hào)。
? ? ? ?比如如下實(shí)例,匹配com.tuacy.microservice.framework.user.manage.controller包下面直接子類所有方法
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配com.tuacy.microservice.framework.user.manage.controller包下面直接子類所有方法。
*/
@Pointcut("execution(* com.tuacy.microservice.framework.user.manage.controller.*.*(..))")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
4.2 within
? ? ? ?"within(類型表達(dá)式)":匹配連接點(diǎn)所在的Java類或者包。within()函數(shù)定義的連接點(diǎn)是針對目標(biāo)類而言的。with所指定的連接點(diǎn)最小范圍是類。所以within能實(shí)現(xiàn)的功能,execution也能實(shí)現(xiàn)。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配com.tuacy.microservice.framework.user.manage.controller包下直接子類所有方法。
*/
@Pointcut("within(com.tuacy.microservice.framework.user.manage.controller.*)")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
匹配 com.tuacy.microservice.framework.user.manage.controller包下面,子類的所有方法。(不包含子孫包,如果想包含子孫包需要改為:within(com.tuacy.microservice.framework.user.manage.controller..*))。
? ? ? ?在比如如果A繼承了接口B,則within("B")不會(huì)匹配到A,但是within("B+")可以匹配到A。
? ? ? ?在比如一個(gè),匹配 所有添加了com.tuacy.microservice.framework.user.manage.annotation.LogginAnnotation注解的方法。
@Pointcut("within(@com.tuacy.microservice.framework.user.manage.annotation.LogginAnnotation *)")
public void operateLog() {
}
4.3 this
? ? ? ?this 向通知方法中傳入代理對象的引用。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配UserController類所有的方法
*/
@Pointcut("execution(* com.tuacy.microservice.framework.user.manage.controller.UserController.*(..))")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容, 通過this傳入了代理對象的引用
*/
@Before(value = "operateLog() && this(param)")
public void beforeMethod(JoinPoint jp, UserController param) {
System.out.println(param.toString());
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
4.4 target
? ? ? ?target 向通知方法中傳入目標(biāo)對象的引用。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配UserController類所有的方法
*/
@Pointcut("execution(* com.tuacy.microservice.framework.user.manage.controller.UserController.*(..))")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容, 通過target傳入了目標(biāo)對象的引用
*/
@Before(value = "operateLog() && target(param)")
public void beforeMethod(JoinPoint jp, UserController param) {
System.out.println(param.toString());
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
4.5 args
? ? ? ?args 將參數(shù)傳入到通知方法中。如果有多個(gè)參數(shù)逗號(hào)隔開。
切面
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配UserController類所有的方法
*/
@Pointcut("execution(* com.tuacy.microservice.framework.user.manage.controller.UserController.*(..))")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容, 通過args傳入了目標(biāo)方法的參數(shù)
*/
@Before(value = "operateLog() && args(param)")
public void beforeMethod(JoinPoint jp, UserParam param) {
System.out.println(param.toString());
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
目標(biāo)
@RestController
public class UserController extends BaseController implements IUserControllerApi {
private IUserService userService;
@Autowired
public void setUserService(IUserService userService) {
this.userService = userService;
}
public ResponseDataEntity<UserInfoEntity> getUser(@RequestBody UserParam param) {
ResponseDataEntity<UserInfoEntity> responseDataEntity = new ResponseDataEntity<>();
try {
responseDataEntity.setMsg(ResponseResultType.SUCCESS.getDesc());
responseDataEntity.setStatus(ResponseResultType.SUCCESS.getValue());
responseDataEntity.setData(userService.getUserInfo());
} catch (Exception e) {
e.printStackTrace();
}
return responseDataEntity;
}
}
4.6 @within
? ? ? ?"@within(注解類型)":匹配類上添加了指定注解的該類的所有方法;注解類型也必須是全限定類型名。比如如下實(shí)例會(huì)匹配到所有添加了ClassAnnotation注解的類的所有方法。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有添加了ClassAnnotation注解的方法
*/
@Pointcut("@within(com.tuacy.microservice.framework.user.manage.annotation.ClassAnnotation)")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
4.7 @target
? ? ? ?"@target(注解類型)":@within的功能類似,但必須要指定注解接口的保留策略為RUNTIME。注解類型也必須是全限定類型名。
? ? ? ?關(guān)于 @target的使用,編寫的實(shí)例代碼一直會(huì)報(bào)錯(cuò)。不曉得為啥。代碼如下:
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有添加了ClassAnnotation注解的方法
*/
@Pointcut("@target(com.tuacy.microservice.framework.user.manage.annotation.ClassAnnotation)")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
/**
* 添加在類上的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ClassAnnotation {
public String value() default "";
}
4.8 @args
? ? ? ?"@args(注解列表)":匹配當(dāng)前執(zhí)行的方法傳入的參數(shù)持有指定注解。注解類型也必須是全限定類型名。
? ? ? ?關(guān)于@args的使用,代碼也是報(bào)錯(cuò),不曉得為啥。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有方法參數(shù)添加了RequestBody注解的方法
*/
@Pointcut("@args(org.springframework.web.bind.annotation.RequestBody)")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
4.9 @annotation
? ? ? ?"@annotation(注解類型)":匹配所有添加了指定注解類型的方法。注解類型也必須是全限定類型名。比如下面的實(shí)例會(huì)匹配到所有添加了OperateLogAnnotation注解的方法。
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有添加了LoggingAnnotation注解的方法
*/
@Pointcut("@annotation(com.tuacy.microservice.framework.user.manage.annotation.LoggingAnnotation)")
public void operateLog() {
}
/**
* 前置通知:目標(biāo)方法執(zhí)行之前執(zhí)行以下方法體的內(nèi)容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}