? ? ? ?AOP全稱Aspect Oriented Programming馏鹤。在OOP
(面向對象程序設計)中米绕,正是這種分散在各處且與對象核心功能無關的代碼(橫切代碼)的存在垢村,使得模塊復用難度增加割疾。AOP則將封裝好的對象剖開,找出其中對多個對象產(chǎn)生影響的公共行為肝断,并將其封裝為一個可重用的模塊,這個模塊被命名為“切面”(Aspect)驰凛,切面將那些與業(yè)務無關胸懈,卻被業(yè)務模塊共同調用的邏輯提取并封裝起來,減少了系統(tǒng)中的重復代碼恰响,降低了模塊間的耦合度趣钱,同時提高了系統(tǒng)的可維護性。
? ? ? ?AOP(Aspect-OrientedProgramming胚宦,面向方面編程)首有,可以說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善枢劝。OOP引入封裝井联、繼承和多態(tài)性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合您旁。當我們需要為分散的對象引入公共行為的時候烙常,OOP則顯得無能為力。也就是說鹤盒,OOP允許你定義從上到下的關系蚕脏,但并不適合定義從左到右的關系侦副。例如日志功能。日志代碼往往水平地散布在所有對象層次中驼鞭,而與它所散布到的對象的核心功能毫無關系秦驯。對于其他類型的代碼,如安全性挣棕、異常處理和透明的持續(xù)性也是如此译隘。這種散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中穴张,它導致了大量代碼的重復细燎,而不利于各個模塊的重用。
一 AOP編程相關名詞
切面(Aspect):在代碼體系中皂甘,對象與對象之間玻驻,方法與方法之間,模塊與模塊之間都可以認為是一個個的切面偿枕。Spring中通過@Aspect:來描述一個切面璧瞬。
連接點(Joinpoint):在程序執(zhí)行過程中某個特定的點,比如某方法調用的時候或者處理異常的時候渐夸。在Spring AOP中嗤锉,一個連接點總是表示一個方法的執(zhí)行。
通知(Advice):在切面的某個特定的連接點上執(zhí)行的動作墓塌。其中包括了“around”瘟忱、“before”和“after”等不同類型的通知。許多AOP框架(包括Spring)都是以攔截器做通知模型苫幢,并維護一個以連接點為中心的攔截器鏈访诱。
切入點(Pointcut):定義在什么時候切人方法。匹配連接點的斷言韩肝。通知和一個切入點表達式關聯(lián)触菜,并在滿足這個切入點的連接點上運行(例如,當執(zhí)行某個特定名稱的方法時)哀峻。切入點表達式如何和連接點匹配是AOP的核心:Spring缺省使用AspectJ切入點語法涡相。Spring里面通過@Pointcut來引入切入點。
引入(Introduction):用來給一個類型聲明額外的方法或屬性(也被稱為連接類型聲明(inter-type declaration))剩蟀。Spring允許引入新的接口(以及一個對應的實現(xiàn))到任何被代理的對象催蝗。例如,你可以使用引入來使一個bean實現(xiàn)IsModified接口育特,以便簡化緩存機制记劝。
目標對象(Target Object):被一個或者多個切面所通知的對象痕囱。也被稱做被通知(advised)對象阱当。既然Spring AOP是通過運行時代理實現(xiàn)的盆赤,這個對象永遠是一個被代理(proxied)對象。
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對象,用來實現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中,AOP代理可以是JDK動態(tài)代理或者CGLIB代理截酷。
織入(Weaving):把切面連接到其它的應用程序類型或者對象上,并創(chuàng)建一個被通知的對象乾戏。這些可以在編譯時(例如使用AspectJ編譯器)迂苛,類加載時和運行時完成。Spring和其他純Java AOP框架一樣鼓择,在運行時完成織入三幻。
二 連接點(Joinpoint)
? ? ? ?連接點是在應用執(zhí)行過程中能夠插入切面的一個點,這個點可以是調用方法時呐能,拋出異常時念搬,甚至是修改一個字段時,切面代碼可以利用這些連接點插入到應用的正常流程中摆出,并添加新的行為朗徊,如日志、安全偎漫、事務爷恳、緩存等。具體代碼中象踊,連接點體現(xiàn)在每個通知方法的參數(shù)中温亲。
比如如下前置@Before通知方法的第一個參數(shù)就是連接點,通過連接點我們可以獲取到一些上下文的信息。
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容
*/
@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接口表示目標類連接點對象菊碟,@Around使用org.aspectj.lang.ProceedingJoinPoint表示連接點對象节芥。兩個接口里面的方法也不復雜在刺。主要方法如下逆害。
JoinPoint接口主要方法如下
public interface JoinPoint {
/**
* 獲取代理對象本身
*/
Object getThis();
/**
* 獲取連接點所在的目標對象
*/
Object getTarget();
/**
* 獲取連接點方法運行時的入?yún)⒘斜? */
Object[] getArgs();
/** 獲取連接點的方法簽名對象,進而可以獲取方法的名字蚣驼,方法修飾符這些
*/
Signature getSignature();
/**
* 獲取連接點方法在文件中的信息魄幕,比如文件中第幾行啥的
*/
SourceLocation getSourceLocation();
/** 獲取連接點的方法的類型
*/
String getKind();
/**
* 獲取封裝連接點的一個對象
*/
JoinPoint.StaticPart getStaticPart();
}
ProceedingJoinPoint主要方法如下,ProceedingJoinPoint繼承自JoinPoint
public interface ProceedingJoinPoint extends JoinPoint {
/**
* 這個函數(shù)咱們不能直接調用颖杏,不管他
*/
void set$AroundClosure(AroundClosure arc);
/**
* P通過反射執(zhí)行目標對象的連接點處的方法
*/
public Object proceed() throws Throwable;
/**
* 通過反射執(zhí)行目標對象連接點處的方法纯陨,不過使用新的入?yún)⑻鎿Q原來的入?yún)? */
public Object proceed(Object[] args) throws Throwable;
}
三 通知(Advice)
? ? ? ?通知定義了切面是什么以及何時調用,何時調用。通知包含以下幾種:
Advice | Spring注解 | 解釋 |
---|---|---|
Before | @Before | 前置通知翼抠,在方法被調用之前調用 |
After | @After | 最終通知, 在方法完成之后調用咙轩,無論方法執(zhí)行是否成功 |
After-returning | @AfterReturning | 后置通知,在方法成功執(zhí)行之后調用 |
After-throwing | @AfterThrowing | 異常通知,在方法拋出異常后調用 |
Around | @Around | 環(huán)繞通知, 包圍一個連接點的通知 |
? ? ? ?@Before阴颖、@After活喊、@AfterReturning、@AfterThrowing這幾個通知應該都還好理解量愧。就@Around稍稍復雜一點钾菊。
? ? ? ?環(huán)繞通知:包圍一個連接點的通知,如方法調用偎肃。這是最強大的一種通知類型煞烫。環(huán)繞通知可以在方法調用前后完成自定義的行為。它也會選擇是否繼續(xù)執(zhí)行連接點或直接返回它自己的返回值或拋出異常來結束執(zhí)行累颂。
? ? ? ?環(huán)繞通知最麻煩滞详,也最強大,其是一個對方法的環(huán)繞喘落,具體方法會通過代理傳遞到切面中去茵宪,切面中可選擇執(zhí)行方法與否,執(zhí)行方法幾次等瘦棋。
? ? ? ?環(huán)繞通知使用一個代理ProceedingJoinPoint類型的對象來管理目標對象稀火,所以此通知的第一個參數(shù)必須是ProceedingJoinPoint類型,在通知體內赌朋,調用ProceedingJoinPoint的proceed()方法會導致后臺的連接點方法執(zhí)行凰狞。proceed 方法也可能會被調用并且傳入一個Object[]對象-該數(shù)組中的值將被作為方法執(zhí)行時的參數(shù)。
四 切點(Pointcut)
? ? ? ?切點定義了何處沛慢,切點的定義會匹配通知所要織入的一個或多個連接點赡若,我們通常使用明確的類的方法名稱來指定這些切點,或是利用正則表達式定義匹配的類和方法名稱來指定這些切點团甲。
? ? ? ?切點(Pointcut)的使用關鍵在切點表達式逾冬,一般由下列方式來定義或者通過 &&、 ||躺苦、 !身腻、 的方式進行組合:
切入點表達式指示符 | 解釋 |
---|---|
execution | 匹配子表達式(匹配方法執(zhí)行) |
within | 匹配連接點所在的Java類或者包 |
this | 用于向通知方法中傳入代理對象的引用 |
target | 用于向通知方法中傳入目標對象的引用 |
args | 用于將參數(shù)傳入到通知方法中 |
@within | 匹配在類一級使用了參數(shù)確定的注解的類,其所有方法都將被匹配(在類上添加注解) |
@target | 和@within的功能類似匹厘,但必須要指定注解接口的保留策略為RUNTIME |
@args | 傳入連接點的對象對應的Java類必須被@args指定的Annotation注解標注 |
@annotation | 匹配當前執(zhí)行方法持有指定注解的方法 |
咱們可以簡單的認為嘀趟,通知(Advice)定義了什么時候調用,切點(Pointcut)定義了哪個地方愈诚。一個指定了when,另一個指定了where她按。
4.1 execution
? ? ? ?"execution(方法表達式)":匹配方法執(zhí)行的連接點牛隅。
? ? ? ?execution方法表達式語法如下:
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.?): 可選授霸。匹配類名巡验。 (相當于某個類下面的哪個方法。注意:后面是有一個點號的碘耳,然后才接的方法名)
方法名匹配(name-pattern): 必填显设。匹配方法名 也可以為表示代表所有方法, 還可以類似set的形式,代表以set開頭的所有方法辛辨。
參數(shù)匹配((param-pattern)): 必填捕捂。 匹配具體的參數(shù)類型,多個參數(shù)間用“,”隔開斗搞,各個參數(shù)也可以用“”來表示匹配任意類型的參數(shù)指攒,如(String)表示匹配一個String參數(shù)的方法;(,String) 表示匹配有兩個參數(shù)的方法僻焚,第一個參數(shù)可以是任意類型允悦,而第二個參數(shù)是String類型;可以用(..)表示零個或多個任意參數(shù)虑啤。
異常類型匹配(throws-pattern?): 可選隙弛。函數(shù)拋出異常類型匹配。
@Pointcut execution 表達式其實是很好理解的狞山,咱們可以把他看成一個函數(shù)就好了全闷,函數(shù)有修飾符、返回值萍启、方法名总珠、參數(shù)、異常勘纯。declaring-type-pattern? 和 name-pattern 共同組合匹配方法匹配方法局服。 如果寫了declaring-type-pattern注意,后面要帶一個點號屡律。
? ? ? ?比如如下實例腌逢,匹配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() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
4.2 within
? ? ? ?"within(類型表達式)":匹配連接點所在的Java類或者包超埋。within()函數(shù)定義的連接點是針對目標類而言的搏讶。with所指定的連接點最小范圍是類。所以within能實現(xiàn)的功能霍殴,execution也能實現(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() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容
*/
@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")不會匹配到A,但是within("B+")可以匹配到A帽衙。
? ? ? ?在比如一個菜皂,匹配 所有添加了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() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容, 通過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 向通知方法中傳入目標對象的引用恍飘。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配UserController類所有的方法
*/
@Pointcut("execution(* com.tuacy.microservice.framework.user.manage.controller.UserController.*(..))")
public void operateLog() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容, 通過target傳入了目標對象的引用
*/
@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ù)傳入到通知方法中。如果有多個參數(shù)逗號隔開谴垫。
切面
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配UserController類所有的方法
*/
@Pointcut("execution(* com.tuacy.microservice.framework.user.manage.controller.UserController.*(..))")
public void operateLog() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容, 通過args傳入了目標方法的參數(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 + "】");
}
}
目標
@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(注解類型)":匹配類上添加了指定注解的該類的所有方法章母;注解類型也必須是全限定類型名。比如如下實例會匹配到所有添加了ClassAnnotation注解的類的所有方法翩剪。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有添加了ClassAnnotation注解的方法
*/
@Pointcut("@within(com.tuacy.microservice.framework.user.manage.annotation.ClassAnnotation)")
public void operateLog() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容
*/
@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。注解類型也必須是全限定類型名前弯。
? ? ? ?關于 @target的使用舞肆,編寫的實例代碼一直會報錯。不曉得為啥博杖。代碼如下:
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有添加了ClassAnnotation注解的方法
*/
@Pointcut("@target(com.tuacy.microservice.framework.user.manage.annotation.ClassAnnotation)")
public void operateLog() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容
*/
@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(注解列表)":匹配當前執(zhí)行的方法傳入的參數(shù)持有指定注解椿胯。注解類型也必須是全限定類型名。
? ? ? ?關于@args的使用剃根,代碼也是報錯哩盲,不曉得為啥。
/**
* 日志AOP 切面類
*/
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有方法參數(shù)添加了RequestBody注解的方法
*/
@Pointcut("@args(org.springframework.web.bind.annotation.RequestBody)")
public void operateLog() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}
4.9 @annotation
? ? ? ?"@annotation(注解類型)":匹配所有添加了指定注解類型的方法狈醉。注解類型也必須是全限定類型名廉油。比如下面的實例會匹配到所有添加了OperateLogAnnotation注解的方法。
@Aspect
@Component("logAspect")
public class LoggingAspect {
/**
* 匹配所有添加了LoggingAnnotation注解的方法
*/
@Pointcut("@annotation(com.tuacy.microservice.framework.user.manage.annotation.LoggingAnnotation)")
public void operateLog() {
}
/**
* 前置通知:目標方法執(zhí)行之前執(zhí)行以下方法體的內容
*/
@Before(value = "operateLog()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】");
}
}