九、Spring Boot 中的切面 AOP 處理

前言:上一節對 SpringBoot 的Spring Boot 中的全局異常處理做了一個介紹,本節主要對Spring Boot 中的切面 AOP 處理講解和分析。

乾貨奉上

1. 什麼是 AOP

AOP:Aspect Oriented Programming 的縮寫,意爲:面向切面編程。面向切面編程的目標就是分離關注點。什麼是關注點呢?就是關注點,就是你要做的事情。

假如你是一 位公子哥,沒啥人生目標,每天衣來伸手,飯來張口,整天只知道一件事:玩(這就是 你的關注點,你只要做這一件事)!但是有個問題,你在玩之前,你還需要起牀、穿衣服、穿鞋子、疊被子、做早飯等等等等,但是這些事情你不想關注,也不用關注,你只想想玩,那麼怎麼辦呢?

對!這些事情通通交給下人去幹。你有一個專門的僕人 A 幫你穿衣服,僕人 B 幫你穿 鞋子,僕人 C 幫你疊好被子,僕人 D 幫你做飯,然後你就開始喫飯、去玩(這就是你 一天的正事),你幹完你的正事之後,回來,然後一系列僕人又開始幫你幹這個幹那 個,然後一天就結束了!

這就是 AOPAOP 的好處就是你只需要幹你的正事,其它事情別人幫你幹。也許有一 天,你想裸奔,不想穿衣服,那麼你把僕人 A 解僱就是了!也許有一天,出門之前你 還想帶點錢,那麼你再僱一個僕人 E 專門幫你幹取錢的活!這就是 AOP。每個人各司 其職,靈活組合,達到一種可配置的、可插拔的程序結構。

2. Spring Boot 中的 AOP 處理

2.1 AOP 依賴

使用 AOP,首先需要引入 AOP 的依賴

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>

2.2 實現 AOP 切面

Spring Boot 中使用 AOP 非常簡單,假如我們要在項目中打印一些 log,在引入了上面 的依賴之後,我們新建一個類 LogAspectHandler,用來定義切面和處理方法。只要在類上加個@Aspect 註解即可。@Aspect 註解用來描述一個切面類,定義切面類的時候 需要打上這個註解。@Component 註解讓該類交給 Spring 來管理。

[@Aspect](https://my.oschina.net/aspect)
[@Component](https://my.oschina.net/u/3907912)
public class LogAspectHandler {

}

這裏主要介紹幾個常用的註解及使用:

1.@Pointcut:定義一個切面,即上面所描述的關注的某件事入口。 

2.@Before:在做某件事之前做的事。 

3.@After:在做某件事之後做的事。

4.@AfterReturning:在做某件事之後,對其返回值做增強處理。

5.@AfterThrowing:在做某件事拋出異常時,處理。

2.2.1 @Pointcut 註解

@Pointcut 註解:用來定義一個切面(切入點),即上文中所關注的某件事情的入 口。切入點決定了連接點關注的內容,使得我們可以控制通知什麼時候執行。

@Aspect
@Component
public class LogAspectHandler {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	/**
	 * 定義一個切面,攔截com.itcodai.course09.controller包下的所有方法
	 */
	@Pointcut("execution(* com.itcodai.course09.controller..*.*(..))")
	public void pointCut() {
	}

}

@Pointcut 註解指定一個切面,定義需要攔截的東西,這裏介紹兩個常用的表達式: 一個是使用 execution(),另一個是使用 annotation()。 以 execution(* com.itcodai.course09.controller...(..))) 表達式爲例, 語法如下:

execution() 爲表達式主體 
第一個 * 號的位置:表示返回值類型,* 表示所有類型 
包名:表示需要攔截的包名,後面的兩個句點表示當前包和當 
前包的所有子包,com.itcodai.course09.controller 包、子 包下所有類的方法 
第二個 * 號的位置:表示類名,* 表示所有類 
*(..) :這個星號表示方法名,* 表示所有的方法,後面括弧 
裏面表示方法的參數,兩個句點表示任何參數

annotation() 方式是針對某個註解來定義切面,比如我們對具有@GetMapping 註解 的方法做切面,可以如下定義切面:

@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMap ping)")
public void annotationCut() {}

然後使用該切面的話,就會切入註解是 @GetMapping 的方法。因爲在實際項目中,可 能對於不同的註解有不同的邏輯處理,比如 @GetMapping、@PostMapping、 @DeleteMapping 等。所以這種按照註解的切入方式在實際項目中也很常用。

2.2.2 @Before 註解

@Before 註解指定的方法在切面切入目標方法之前執行,可以做一些 log 處理,也可以 做一些信息的統計,比如獲取用戶的請求 url 以及用戶的 ip 地址等等,這個在做個人站 點的時候都能用得到,都是常用的方法。例如:

@Aspect
@Component
public class LogAspectHandler {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	 /**
	 * 在上面定義的切面方法之前執行該方法
	 * @param joinPoint jointPoint
	 */
	@Before("pointCut()")
	public void doBefore(JoinPoint joinPoint) {
		logger.info("====doBefore方法進入了====");

		// 獲取簽名
		Signature signature = joinPoint.getSignature();
		// 獲取切入的包名
		String declaringTypeName = signature.getDeclaringTypeName();
		// 獲取即將執行的方法名
		String funcName = signature.getName();
		logger.info("即將執行方法爲: {},屬於{}包", funcName, declaringTypeName);

		// 也可以用來記錄一些信息,比如獲取請求的url和ip
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		// 獲取請求url
		String url = request.getRequestURL().toString();
		// 獲取請求ip
		String ip = request.getRemoteAddr();
		logger.info("用戶請求的url爲:{},ip地址爲:{}", url, ip);
	}
}

JointPoint 對象很有用,可以用它來獲取一個簽名,然後利用簽名可以獲取請求的包 名、方法名,包括參數(通過 joinPoint.getArgs() 獲取)等等。

2.2.3 @After 註解

@After 註解和 @Before 註解相對應,指定的方法在切面切入目標方法之後執行,也可 以做一些完成某方法之後的 log 處理

@Aspect
@Componentpublic
class LogAspectHandler {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	/**
	 * 定義一個切面,攔截 com.itcodai.course09.controller 包下的所有方法
	 */
	@Pointcut("execution(* com.itcodai.course09.controller..*.*(..))")
	public void pointCut() {
	}

	/**
	 * 在上面定義的切面方法之後執行該方法 * @param joinPoint jointPoint
	 */
	@After("pointCut()")
	public void doAfter(JoinPoint joinPoint) {
		logger.info("====doAfter 方法進入了====");
		Signature signature = joinPoint.getSignature();
		String method = signature.getName();
		logger.info("方法{}已經執行完", method);
	}
}

到這裏,我們來寫一個 Controller 來測試一下執行結果,新建一個 AopController 如下

@RestController
@RequestMapping("/aop")
public class AopController {

	@GetMapping("/{name}")
	public String testAop(@PathVariable String name) {
		return "Hello " + name;
	}

}

啓動項目,在瀏覽器中輸入 localhost:8080/aop/CSDN,觀察一下控制檯的輸出信息:

從打印出來的 log 中可以看出程序執行的邏輯與順序,可以很直觀的掌握 @Before@After 兩個註解的實際作用。

2.2.4 @AfterReturning 註解

@AfterReturning 註解和 @After 有些類似,區別在於 @AfterReturning 註解可以 用來捕獲切入方法執行完之後的返回值,對返回值進行業務邏輯上的增強處理,例如:

/**
	 * 在上面定義的切面方法返回後執行該方法,可以捕獲返回對象或者對返回對象進行增強
	 * @param joinPoint joinPoint
	 * @param result result
	 */
	@AfterReturning(pointcut = "pointCut()", returning = "result")
	public void doAfterReturning(JoinPoint joinPoint, Object result) {

		Signature signature = joinPoint.getSignature();
		String classMethod = signature.getName();
		logger.info("方法{}執行完畢,返回參數爲:{}", classMethod, result);
		// 實際項目中可以根據業務做具體的返回值增強
		logger.info("對返回參數進行業務上的增強:{}", result + "增強版");
	}

需要注意的是:在 @AfterReturning 註解中,屬性 returning 的值必須要和參數保 持一致,否則會檢測不到。該方法中的第二個入參就是被切方法的返回值,在 doAfterReturning 方法中可以對返回值進行增強,可以根據業務需要做相應的封裝。 我們重啓一下服務,再測試一下(多餘的 log 我不貼出來了):

方法testAop執行完畢,返回參數爲:Hello CSDN
對返回參數進行業務上的增強:Hello CSDN增強版

2.2.5 @AfterThrowing 註解

顧名思義,@AfterThrowing 註解是當被切方法執行時拋出異常時,會進入 @AfterThrowing 註解的方法中執行,在該方法中可以做一些異常的處理邏輯。要注意 的是 throwing 屬性的值必須要和參數一致,否則會報錯。該方法中的第二個入參即爲拋出的異常。

/**
	 * 在上面定義的切面方法執行拋異常時,執行該方法
	 * @param joinPoint jointPoint
	 * @param ex ex
	 */
	@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
	public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
		Signature signature = joinPoint.getSignature();
		String method = signature.getName();
		// 處理異常的邏輯
		logger.info("執行方法{}出錯,異常爲:{}", method, ex.getMessage());
	}

該方法我就不測試了,大家可以自行測試一下

3. 總結

本節課針對 Spring Boot 中的切面 AOP 做了詳細的講解,主要介紹了 Spring BootAOP 的引入,常用註解的使用,參數的使用,以及常用 api 的介紹。AOP 在實際項目 中很有用,對切面方法執行前後都可以根據具體的業務,做相應的預處理或者增強處 理,同時也可以用作異常捕獲處理,可以根據具體業務場景,合理去使用 AOP


最後,希望能和大家開啓一段充實的學習歷程,願大家都能突破職場瓶頸,提升競爭力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章