SpringBoot中搭配AOP實現自定義注解
1 springBoot的依賴
確定項目中包含可以注解的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2 自定義注解的步驟
在項目中自定義注解的步驟主要有兩步,第一步:定義注解類,第二步:定義切面
2.1 定義注解類
直接創建 @interface 的類,使用注解@Target和 @Retention指定其適用范圍及保留時長,如下:
@Target(ElementType.METHOD) // 指定注解的適用范圍
@Retention(RetentionPolicy.RUNTIME) //指定運行時
public @interface ApiLog {
String desc() default "";
boolean timeSpan() default true;
}
? 注解類的內容一般很簡單,類似于Enum類一樣,里面是簡單的方法及屬性
2.2 定義切面
通過@Aspect注解指定一個類,該類必須實現
@Component
@Aspect
@Slf4j(topic = "ApiLogNote")
public class ElasticSearchExecuteLog {
@Around("@annotation(com.gcc.ApiLog)")
public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
//獲取被調用方法
Method method = signature.getMethod();
//取出被調用方法的注解,方便后續使用注解中的屬性
ApiLog loglinstener = method.getAnnotation(ApiLog.class);
log.info("----------------------method[{}]start--------------------",method.getName());
log.info("方法描述:{}",loglinstener.desc());
log.info("參數 :{}",point.getArgs());
long startTime = System.currentTimeMillis();
Object proceed = point.proceed();
long endTime = System.currentTimeMillis();
log.info("耗時:{}ss",endTime-startTime);
log.info("----------------------method[{}] end--------------------\n",method.getName())
return proceed;
}
}
2.3 使用注解
因為此例子使用的類型為METHOD即方法級的注解,直接在方法上使用即可:
@ApiLog
public JSONObject seachEsData(String indexName, SearchSourceBuilder searchSourceBuilder) {
JSONObject resultMap = new JSONObject();
.......
return resultMap;
}
3 知識點補充
3.1 關于Target注解補充
注解@Target常常配合枚舉類ElementType來指定注解的作用位置,也叫合法位置,即你定義了一個注解,這個注解是類注解還是方法注解還是XX注解等,具體作用的范圍,取決于@Target({ElementType.TYPE})中,ElementType的枚舉值,在進行自定義枚舉時,根據自己的需求,決定定義的注解是哪類層級使用的注解,例如上面的例子中,@ApiLog這個自定義的注解就是方法級的注解
ElementType的枚舉值有
| 枚舉值 | 含義 |
|---|---|
| TYPE | 類, 接口 (包括注解類型), 或 枚舉 聲明 |
| FIELD | 字段、包括枚舉常量 |
| METHOD | 方法聲明 |
| PARAMETER | 正式的參數聲明 |
| CONSTRUCTOR | 構造函數的聲明 |
| LOCAL_VARIABLE | 局部變量的聲明 |
| ANNOTATION_TYPE | 注解類型的聲明 |
| PACKAGE | 包聲明 |
3.2 關于Retention注解補充
注解@Retention常常配合枚舉類RetentionPolic來指定注解的各種策略,注解的保留時間,也就是何時生效,即你定義了一個注解,這個注解是編譯時生效還是僅僅只是在代碼中標記等等,具體作用的范圍,取決于@Retention({RetentionPolic.TYPE})中,RetentionPolic的枚舉值,在進行自定義枚舉時,大多數都是使用RUNTIME(編譯時生效)
RetentionPolic的枚舉值
| 枚舉值 | 含義 |
|---|---|
| SOURCE | 解只在源代碼級別保留,編譯時被忽略 |
| CLASS | 注解將被編譯器在類文件中記錄 , 但在運行時不需要JVM保留。這是默認的 |
| RUNTIME | 注解將被編譯器記錄在類文件中,在運行時保留VM,也是使用最多的(一般自定義均使用這個) |
3.3 關于AOP的一些概念補充
這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程
切面(Aspect)
切面是一個橫切關注點的模塊化,一個切面能夠包含同一個類型的不同增強方法,比如說事務處理和日志處理可以理解為兩個切面。切面由切入點和通知組成,它既包含了橫切邏輯的定義,也包括了切入點的定義。 Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯織入到切面所指定的連接點中。簡單點理解,在SpringBoot中使用了Aspect注解的類就是切面
@Component
@Aspect
public class LogAspect {
}
目標對象(target)
目標對象指將要被增強的對象,即包含主業務邏輯的類對象。或者說是被一個或者多個切面所通知的對象。
在我們的例子中,即是使用了@ApiLog注解的地方
連接點(JoinPoint)
程序執行過程中明確的點,如方法的調用或特定的異常被拋出。連接點由兩個信息確定:
- 方法(表示程序執行點,即在哪個目標方法)
- 相對點(表示方位,即目標方法的什么位置,比如調用前,后等)
簡單來說,連接點就是被攔截到的程序執行點,因為Spring只支持方法類型的連接點,所以在Spring中連接點就是被攔截到的方法。
切入點(PointCut)
切入點是對連接點進行攔截的條件定義。切入點表達式如何和連接點匹配是AOP的核心,Spring缺省使用AspectJ切入點語法。 一般認為,所有的方法都可以認為是連接點,但是我們并不希望在所有的方法上都添加通知,而切入點的作用就是提供一組規則(使用 AspectJ pointcut expression language 來描述) 來匹配連接點,給滿足規則的連接點添加通知。
//此處的匹配規則是 com.remcarpediem.test.aop.service包下的所有類的所有函數。
@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void pointcut() {
}
這里切入點的概念其實就是確定對哪些目標對象進行切面插入功能,一開始的例子是采用注解的方式來達到切入**點的作用
@Around("@annotation(com.gcc.ApiLog)")
通知(Advice)
通知是指攔截到連接點之后要執行的代碼,包括了“around”、“before”和“after”等不同類型的通知。Spring AOP框架以攔截器來實現通知模型,并維護一個以連接點為中心的攔截器鏈。
// @Before說明這是一個前置通知,log函數中是要前置執行的代碼,JoinPoint是連接點,
@Before("pointcut()")
public void log(JoinPoint joinPoint) {
}
//@After 為后置通知
//@Around 為環繞通知
織入(Weaving)
這里的織入概念是個動作,即Spring將前面的切面、連接點、切入點關聯起來并創建通知代理的過程。織入可以在編譯時,類加載時和運行時完成。在編譯時進行織入就是靜態代理,而在運行時進行織入則是動態代理。
增強器(Adviser)
Advisor是切面的另外一種實現,能夠將通知以更為復雜的方式織入到目標對象中,是將通知包裝為更復雜切面的裝配器。Advisor由切入點和Advice組成。 Advisor這個概念來自于Spring對AOP的支撐,在AspectJ中是沒有等價的概念的。Advisor就像是一個小的自包含的切面,這個切面只有一個通知。切面自身通過一個Bean表示,并且必須實現一個默認接口。
簡單來講,整個 aspect 可以描述為: 滿足 pointcut 規則的 joinpoint 會被添加相應的 advice 操作。
將上方通過注解使用切面的方式改寫一下:
@Component
@Aspect
@Sl4fj
public class ElasticSearchExecuteLog {
// 不使用注解,而通過基礎的規則配置選擇切入點,表達式是指com.gcc.controller
// 包下的所有類的所有方法
@Pointcut("execution(* com.gcc.controller..*(..))")
public void aspect() {}
// 通知,在符合aspect切入點的方法前插入如下代碼,并且將連接點作為參數傳遞
@Before("aspect()")
public void log(JoinPoint joinPoint) { //連接點作為參數傳入
// 獲得類名,方法名,參數和參數名稱。
Signature signature = joinPoint.getSignature();
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] argumentNames = methodSignature.getParameterNames();
StringBuilder sb = new StringBuilder(className + "." + methodName + "(");
for (int i = 0; i< arguments.length; i++) {
Object argument = arguments[i];
sb.append(argumentNames[i] + "->");
sb.append(argument != null ? argument.toString() : "null ");
}
sb.append(")");
log.info(sb.toString());
}
}
3.4 關于AOP中一些類及函數的使用
JoinPoint對象
JoinPoint對象封裝了SpringAop中切面方法的信息,在切面方法中添加JoinPoint參數,就可以獲取到封裝了該方法信息的JoinPoint對象.
| 方法 | 作用 | 返回對象 |
|---|---|---|
| getSignature() | 獲取封裝了署名信息的對象,在該對象中可以獲取到目標方法名,所屬類的Class等信息 | Signature |
| getArgs() | 獲取 獲取傳入目標方法的參數對象 | Object[] |
| getTarget() | 獲取被代理的對象 | Object |
ProceedingJoinPoint對象
proceedingJoinPoin對象是JoinPoint的子類,在原本JoinPoint的基礎上,放開了Proceeed()的使用,一般在環繞通知@Around時使用:
Object proceed() throws Throwable //執行目標方法
Object proceed(Object[] var1) throws Throwable //傳入的新的參數去執行目標方法

浙公網安備 33010602011771號