面向切面編程
AOP = 面向切面編程
AOP並不是Spring框架特有的功能,不依賴於Spring也可以實現AOP,只是Spring很好的支持了AOP!
常規的數據處理流程大致是:
註冊:前端頁面 -----> Controller -----> Service -----> Mapper
登錄:前端頁面 -----> Controller -----> Service -----> Mapper
改密:前端頁面 -----> Controller -----> Service -----> Mapper
假設,需要在處理每種/每次請求時,都需要執行相同的某個任務,例如“統計業務層代碼的執行耗時”,在傳統的做法中,可以將“統計耗時”的代碼封裝在某個方法中,然後,在業務層的reg()
、login()
、changePassword()
中均調用這個“統計耗時”的方法即可!但是,在比較複雜的項目中,涉及的業務可能有成百上千個,就需要在成百上千個業務方法中都添加調用“統計耗時”的方法,不便於統一管理!
面向切面編程,是在數據處理流程中,假設存在某個切面,這個切面可以在任何位置,例如在Controller
與Service
之間,或在Service
與Mapper
之間,甚至可以同時存在多個切面,每個切面中都可以添加方法,當數據處理流程執行到切面時,就會執行切面中的方法!
在使用面向切面的編程時,只需要確定切面的位置,及切面中需要執行的代碼即可,對原有的Controller
、Service
等組件中的代碼不需要進行任何調整!
首先,需要添加aspectjtools
和aspectjweaver
依賴:(Springboot中居然沒有自帶這兩個依賴? 是否說明AOP並不常用?)
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
暫定目標爲“統計業務層代碼的執行耗時”。
在cn.tedu.store
包下創建aop
包,用於存放切面類(當然,也可以使用其它包名,例如aspect
),並在這個包中創建TimerAspect
類(切面類推薦使用Aspect
作爲類名的最後一個單詞)。作爲一個切面類,必須添加@Aspect
註解,同時,是由Spring管理的,所以,還需要添加@Component
註解:
package cn.tedu.store.aop;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TimerAspect {
}
然後,在切面類中,自定義方法,實現切面中的功能,並通過註解配置切面的位置:
@Around("execution(* cn.tedu.store.service.impl.*.*(..))")
public Object aaaaa(ProceedingJoinPoint pjp) throws Throwable {
// 記錄開始時間
long start = System.currentTimeMillis();
// 執行業務方法,例如註冊業務、登錄業務等
Object obj = pjp.proceed();
// 記錄結束時間
long end = System.currentTimeMillis();
System.err.println("執行耗時:" + (end - start) + "毫秒。");
// 返回
return obj;
}
以上代碼中,註解的配置值execution(* cn.tedu.store.service.impl.*.*(..))
將確定切面的位置,這段值表示“在cn.tedu.store.service.impl
包下的所有類(包名右側的第1個星號)的所有方法(包名右側的第2個星號),且無視方法的參數數量(括號中的2個小數)和方法的返回值類型(包名左側的星號)”,也就是當前項目中所有的業務方法都會匹配上!
以上代碼中,使用的@Around
註解表示切面的連接方法,@Around
表示“包裹”,也就是在應用切面的業務方法之前和之後都存在!也可以使用@Before
表示“之前”,則切面在某位置之前,也可以使用@After
表示“之後”。
以上代碼中,自定義的方法必須添加ProceedingJoinPoint
接口類型的參數,該參數的proceed()
方法就相當於切面所包裹的位置(業務)需要執行方法,所以,可以通過pjp.proceed()
表示“註冊業務”,也可以表示“登錄業務”或任何其它業務!
需要注意的是:該方法將拋出異常,用於表示具體的業務方法可能拋出的異常,例如“註冊”時拋出的UsernameDuplicateException
,在切面中,必須將該異常拋出,否則,就必須使用try...catch
進行捕獲,相當於自行處理了異常,後續控制器類就不會發現這個異常了!另外,調用pjp.proceed()
方法將得到一個Object
類型的返回值,這個返回值就相當於業務方法的返回值,例如“登錄”成功後將返回一個User
對象,加載收貨地址列表時將返回一個List<Address>
,在自定義切面方法時,必須獲取該返回值,並且作爲切面方法的返回值,否則,就相當於業務方法返回了null
!