AOP主要實現(xiàn)的目的是針對業(yè)務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。其與設計模式完成的任務差不多,是提供另一種角度來思考程序的結構,來彌補面向對象編程的不足,這個可能是面試中經(jīng)常提到的問題,同時它也是Spring框架中一個重大的特性,對于我們開發(fā)中最常見的可能就是日志記錄,事務處理,異常處理等等。。。

本文將給大家介紹如何在SpringBoot中使用以及AOP的相關知識點
一、AOP知識點
1、原理
AOP是通過動態(tài)代理實現(xiàn)的,動態(tài)代理又分為兩個部分:JDK動態(tài)代理和CGLIB動態(tài)代理,AOP功能的使用還是比較簡單的,把相關bean注入到Spring容器中,編寫好相應的Aspect類即可,以下兩點需要記?。?/p>
1、AOP基于
動態(tài)代理模式
2、AOP是方法級別的
對代理模式不熟悉的可以參考我的這篇文章:JAVA代理模式
2、AOP術語
- 切面(Aspect)
一個關注點的模塊化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關于橫切關注點的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于
@Aspect注解的方式來實現(xiàn)
- 連接點(JoinPoint)
在程序執(zhí)行過程中某個特定的點,比如某方法調(diào)用的時候或者處理異常的時候。在SpringAOP中,一個連接點總是表示一個方法的執(zhí)行
- 通知(Advice)
在切面的某個特定的連接點上執(zhí)行的動作。其中包括了
Around、Before和After等不同類型的通知。許多AOP框架(包括Spring)都是以攔截器做通知模型,并維護一個以連接點為中心的攔截器鏈
- 切入點(PointCut)
匹配連接點的斷言。通知和一個切入點表達式關聯(lián),并在滿足這個切入點的連接點上運行(例如,當執(zhí)行某個特定名稱的方法時),切入點表達式如何和連接點匹配是AOP的核心:Spring
缺省使用AspectJ切入點語法
- 引入(Intorduction)
用來給一個類型聲明額外的方法或屬性(也被稱為連接類型聲明)。Spring允許引入新的接口(以及一個對應的實現(xiàn))到任何被代理的對象。例如,你可以使用引入來使一個bean實現(xiàn)接口,以便簡化緩存機制
- 目標對象(Target Object)
被一個或者多個切面所通知的對象。也被稱做被通知對象。既然Spring AOP是通過運行時代理實現(xiàn)的,這個對象永遠是一個被代理對象
- AOP代理(Aop proxy)
AOP框架創(chuàng)建的對象,用來實現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中,AOP代理可以是JDK動態(tài)代理或者CGLIB代理
- 植入(weaving)
把切面連接到其它的應用程序類型或者對象上,并創(chuàng)建一個被通知的對象。這些可以在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其他純Java AOP框架一樣,在運行時完成織入
二、常用切入點指示符
1、execution表達式
用于匹配方法執(zhí)行的連接點,屬于方法級別
語法:
execution(修飾符 返回值類型 方法名(參數(shù))異常)
| 語法參數(shù) | 描述 |
|---|---|
| 修飾符 | 可選,如public,protected,寫在返回值前,任意修飾符填*號就可以 |
| 返回值類型 |
必選,可以使用*來代表任意返回值 |
| 方法名 |
必選,可以用*來代表任意方法 |
| 參數(shù) |
()代表是沒有參數(shù),(..)代表是匹配任意數(shù)量,任意類型的參數(shù),當然也可以指定類型的參數(shù)進行匹配,如要接受一個String類型的參數(shù),則(java.lang.String), 任意數(shù)量的String類型參數(shù):(java.lang.String..)等等。。。 |
| 異常 | 可選,語法:throws 異常,異常是完整帶包名,可以是多個,用逗號分隔 |
符號:
| 符號 | 描述 |
|---|---|
| * | 匹配任意字符 |
| .. | 匹配多個包或者多個參數(shù) |
| + | 表示類及其子類 |
條件符:
| 符號 | 描述 |
|---|---|
| &&、and | 與 |
| || | 或 |
| ! | 非 |
案例:
-
攔截com.gj.web包下的所有子包里的任意類的任意方法
execution(* com.gj.web..*.*(..)) -
攔截com.gj.web.api.Test2Controller下的任意方法
execution(* com.gj.web.api.Test2Controller.*(..)) -
攔截任何修飾符為public的方法
execution(public * * (..)) -
攔截com.gj.web下的所有子包里的以ok開頭的方法
execution(* com.gj.web..*.ok*(..))
更多用法大家可以根據(jù)語法自行設計,本文不在進行舉例
2、@annotation
根據(jù)所應用的注解對方法進行過濾
語法:
@annotation(注解全路徑)
實例:
對用了com.gj.annotations.Test注解的所有方法進行攔截
@annotation(com.gj.annotations.Test)
3、Within
根據(jù)類型(比如接口、類名或者包名過濾方法)進行攔截
語法:
within(typeName)
示例:
-
對com.gj.web下的所有子包的所有方法進行攔截
within(com.gj.web..*)
更多用法可以根據(jù)語法自行設計
4、@Within
用于匹配所有持有指定注解類型內(nèi)的方法,與within是有區(qū)別的,within是用于匹配指定類型內(nèi)的方法執(zhí)行,而@within是指定注解類型內(nèi)的方法
5、bean
Spring AOP擴展的,AspectJ沒有對于它的指示符,用于匹配特定名稱的bean對象的執(zhí)行方法
語法:
bean(beanName)
示例:
bean(testController)
三、AOP通知
在切面類中需要定義切面方法用于響應響應的目標方法,切面方法即為通知方法,通知方法需要用注解標識,AspectJ支持5種類型的通知注解
| 注解 | 描述 |
|---|---|
| @Before | 前置通知, 在方法執(zhí)行之前執(zhí)行 |
| @After | 后置通知, 在方法執(zhí)行之后執(zhí)行 |
| @AfterReturn | 返回通知, 在方法返回結果之后執(zhí)行 |
| @AfterThrowing | 異常通知, 在方法拋出異常之后 |
| @Around | 環(huán)繞通知,圍繞方法的執(zhí)行 |
1、@Before
示例:
@Before("testCut()")
public void cutProcess(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP開始攔截, 當前攔截的方法名: " + method.getName());
}
2、@After
示例:
@After("testCut()")
public void after(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP執(zhí)行的方法 :"+method.getName()+" 執(zhí)行完了");
}
3、@AfterReturn
其中value表示切點方法,returning表示返回的結果放到result這個變量中
示例:
/**
* returning屬性指定連接點方法返回的結果放置在result變量中
* @param joinPoint 連接點
* @param result 返回結果
*/
@AfterReturning(value = "testCut()",returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP攔截的方法執(zhí)行成功, 進入返回通知攔截, 方法名為: "+method.getName()+", 返回結果為: "+result.toString());
}
4、@AfterThrowing
其中value表示切點方法,throwing表示異常放到e這個變量
示例:
@AfterThrowing(value = "testCut()", throwing = "e")
public void afterThrow(JoinPoint joinPoint, Exception e) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP進入方法異常攔截, 方法名為: " + method.getName() + ", 異常信息為: " + e.getMessage());
}
5、Around
示例:
@Around("testCut()")
public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("注解方式AOP攔截開始進入環(huán)繞通知.......");
Object proceed = joinPoint.proceed();
System.out.println("準備退出環(huán)繞......");
return proceed;
}
四、SpringBoot中使用
1、導入依賴
common和swagger是附加的,讀者不用必須的哈
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.gjing</groupId>
<artifactId>tools-common</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>cn.gjing</groupId>
<artifactId>tools-starter-swagger</artifactId>
<version>1.0.9</version>
</dependency>
2、創(chuàng)建注解
注解用于進行AOP攔截
/**
* @author Gjing
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
2、創(chuàng)建一個接口
/**
* @author Gjing
**/
@RestController
@RequestMapping("test1")
public class TestController {
@GetMapping("/ok")
@ApiOperation(value = "測試1", httpMethod = "GET")
@ApiImplicitParam(name = "id", value = "id值", dataType = "int", paramType = "query")
@Test
@NotNull
public String test2(Integer id) {
return "ok";
}
}
3、創(chuàng)建切面類
/**
* @author Gjing
**/
@Aspect
@Component
public class TestAspect {
@Pointcut("@annotation(com.gj.annotations.Test)")
public void testCut() {
}
@Before("testCut()")
public void cutProcess(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP開始攔截, 當前攔截的方法名: " + method.getName());
}
@After("testCut()")
public void after(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP執(zhí)行的方法 :"+method.getName()+" 執(zhí)行完了");
}
@Around("testCut()")
public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("注解方式AOP攔截開始進入環(huán)繞通知.......");
Object proceed = joinPoint.proceed();
System.out.println("準備退出環(huán)繞......");
return proceed;
}
/**
* returning屬性指定連接點方法返回的結果放置在result變量中
* @param joinPoint 連接點
* @param result 返回結果
*/
@AfterReturning(value = "testCut()",returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP攔截的方法執(zhí)行成功, 進入返回通知攔截, 方法名為: "+method.getName()+", 返回結果為: "+result.toString());
}
@AfterThrowing(value = "testCut()", throwing = "e")
public void afterThrow(JoinPoint joinPoint, Exception e) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP進入方法異常攔截, 方法名為: " + method.getName() + ", 異常信息為: " + e.getMessage());
}
}
4、啟動運行
-
正常情況:
y.png -
異常情況:
error.png
本文到此就結束了,篇幅過長,如果有哪里有些錯的地方歡迎各位評論區(qū)留言,項目Demo源碼地址:SpringBoot-Demo

