SpringBoot搭配AOP實(shí)現(xiàn)自定義注解
1.springBoot的依賴
確定項(xiàng)目中包含可以注解的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.自定義注解的步驟
在項(xiàng)目中自定義注解的步驟主要有兩步,第一步:定義注解類,第二步:定義切面
2.1定義注解類
直接創(chuàng)建 @interface的類,使用注解@Target和 @Retention指定其適用范圍及保留時(shí)長(zhǎng),如下:
@Target(ElementType.METHOD) // 指定注解的適用范圍
@Retention(RetentionPolicy.RUNTIME) //指定運(yùn)行時(shí)
public @interface ApiLog {
String desc() default "";
boolean timeSpan() default true;
}注解類的內(nèi)容一般很簡(jiǎn)單,類似于Enum類一樣,里面是簡(jiǎn)單的方法及屬性
2.2定義切面
通過(guò)@Aspect注解指定一個(gè)類,該類必須實(shí)現(xiàn)
@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();
//獲取被調(diào)用方法
Method method = signature.getMethod();
//取出被調(diào)用方法的注解,方便后續(xù)使用注解中的屬性
ApiLog loglinstener = method.getAnnotation(ApiLog.class);
log.info("----------------------method[{}]start--------------------",method.getName());
log.info("方法描述:{}",loglinstener.desc());
log.info("參數(shù) :{}",point.getArgs());
long startTime = System.currentTimeMillis();
Object proceed = point.proceed();
long endTime = System.currentTimeMillis();
log.info("耗時(shí):{}ss",endTime-startTime);
log.info("----------------------method[{}] end--------------------\n",method.getName())
return proceed;
}
}2.3使用注解
因?yàn)榇死邮褂玫念愋蜑镸ETHOD即方法級(jí)的注解,直接在方法上使用即可:
@ApiLog
public JSONObject seachEsData(String indexName, SearchSourceBuilder searchSourceBuilder) {
JSONObject resultMap = new JSONObject();
.......
return resultMap;
}
3.知識(shí)點(diǎn)補(bǔ)充
3.1 關(guān)于Target注解補(bǔ)充
注解@Target常常配合枚舉類ElementType來(lái)指定注解的作用位置,也叫合法位置,即你定義了一個(gè)注解,這個(gè)注解是類注解還是方法注解還是XX注解等,具體作用的范圍,取決于@Target({ElementType.TYPE})中,ElementType的枚舉值,在進(jìn)行自定義枚舉時(shí),根據(jù)自己的需求,決定定義的注解是哪類層級(jí)使用的注解,例如上面的例子中,@ApiLog這個(gè)自定義的注解就是方法級(jí)的注解
ElementType的枚舉值有
| 枚舉值 | 含義 |
|---|---|
| TYPE | 類, 接口 (包括注解類型), 或 枚舉 聲明 |
| FIELD | 字段、包括枚舉常量 |
| METHOD | 方法聲明 |
| PARAMETER | 正式的參數(shù)聲明 |
| CONSTRUCTOR | 構(gòu)造函數(shù)的聲明 |
| LOCAL_VARIABLE | 局部變量的聲明 |
| ANNOTATION_TYPE | 注解類型的聲明 |
| PACKAGE | 包聲明 |
3.2 關(guān)于Retention注解補(bǔ)充
注解@Retention常常配合枚舉類RetentionPolic來(lái)指定注解的各種策略,注解的保留時(shí)間,也就是何時(shí)生效,即你定義了一個(gè)注解,這個(gè)注解是編譯時(shí)生效還是僅僅只是在代碼中標(biāo)記等等,具體作用的范圍,取決于@Retention({RetentionPolic.TYPE})中,RetentionPolic的枚舉值,在進(jìn)行自定義枚舉時(shí),大多數(shù)都是使用RUNTIME(編譯時(shí)生效)
RetentionPolic的枚舉值
| 枚舉值 | 含義 |
|---|---|
| SOURCE | 解只在源代碼級(jí)別保留,編譯時(shí)被忽略 |
| CLASS | 注解將被編譯器在類文件中記錄 , 但在運(yùn)行時(shí)不需要JVM保留。這是默認(rèn)的 |
| RUNTIME | 注解將被編譯器記錄在類文件中,在運(yùn)行時(shí)保留VM,也是使用最多的(一般自定義均使用這個(gè)) |
3.3 關(guān)于AOP的一些概念補(bǔ)充
這種在運(yùn)行時(shí),動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程
切面
切面是一個(gè)橫切關(guān)注點(diǎn)的模塊化,一個(gè)切面能夠包含同一個(gè)類型的不同增強(qiáng)方法,比如說(shuō)事務(wù)處理和日志處理可以理解為兩個(gè)切面。切面由切入點(diǎn)和通知組成,它既包含了橫切邏輯的定義,也包括了切入點(diǎn)的定義。 Spring AOP就是負(fù)責(zé)實(shí)施切面的框架,它將切面所定義的橫切邏輯織入到切面所指定的連接點(diǎn)中。簡(jiǎn)單點(diǎn)理解,在SpringBoot中使用了Aspect注解的類就是切面
@Component
@Aspect
public class LogAspect {
}
目標(biāo)對(duì)象
目標(biāo)對(duì)象指將要被增強(qiáng)的對(duì)象,即包含主業(yè)務(wù)邏輯的類對(duì)象。或者說(shuō)是被一個(gè)或者多個(gè)切面所通知的對(duì)象。
在我們的例子中,即是使用了@ApiLog注解的地方
連接點(diǎn)
程序執(zhí)行過(guò)程中明確的點(diǎn),如方法的調(diào)用或特定的異常被拋出。連接點(diǎn)由兩個(gè)信息確定:
方法(表示程序執(zhí)行點(diǎn),即在哪個(gè)目標(biāo)方法)
相對(duì)點(diǎn)(表示方位,即目標(biāo)方法的什么位置,比如調(diào)用前,后等)
簡(jiǎn)單來(lái)說(shuō),連接點(diǎn)就是被攔截到的程序執(zhí)行點(diǎn),因?yàn)镾pring只支持方法類型的連接點(diǎn),所以在Spring中連接點(diǎn)就是被攔截到的方法。
切入點(diǎn)
切入點(diǎn)是對(duì)連接點(diǎn)進(jìn)行攔截的條件定義。切入點(diǎn)表達(dá)式如何和連接點(diǎn)匹配是AOP的核心,Spring缺省使用AspectJ切入點(diǎn)語(yǔ)法。 一般認(rèn)為,所有的方法都可以認(rèn)為是連接點(diǎn),但是我們并不希望在所有的方法上都添加通知,而切入點(diǎn)的作用就是提供一組規(guī)則(使用 AspectJ pointcut expression language 來(lái)描述) 來(lái)匹配連接點(diǎn),給滿足規(guī)則的連接點(diǎn)添加通知。
//此處的匹配規(guī)則是 com.remcarpediem.test.aop.service包下的所有類的所有函數(shù)。
@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void pointcut() {
}
這里切入點(diǎn)的概念其實(shí)就是確定對(duì)哪些目標(biāo)對(duì)象進(jìn)行切面插入功能,一開(kāi)始的例子是采用注解的方式來(lái)達(dá)到切入**點(diǎn)的作用
@Around("@annotation(com.gcc.ApiLog)")
通知
通知是指攔截到連接點(diǎn)之后要執(zhí)行的代碼,包括了“around”、“before”和“after”等不同類型的通知。Spring AOP框架以攔截器來(lái)實(shí)現(xiàn)通知模型,并維護(hù)一個(gè)以連接點(diǎn)為中心的攔截器鏈。
// @Before說(shuō)明這是一個(gè)前置通知,log函數(shù)中是要前置執(zhí)行的代碼,JoinPoint是連接點(diǎn),
@Before("pointcut()")
public void log(JoinPoint joinPoint) {
}
???????//@After 為后置通知
//@Around 為環(huán)繞通知
織入(Weaving)
這里的織入概念是個(gè)動(dòng)作,即Spring將前面的切面、連接點(diǎn)、切入點(diǎn)關(guān)聯(lián)起來(lái)并創(chuàng)建通知代理的過(guò)程??椚肟梢栽诰幾g時(shí),類加載時(shí)和運(yùn)行時(shí)完成。在編譯時(shí)進(jìn)行織入就是靜態(tài)代理,而在運(yùn)行時(shí)進(jìn)行織入則是動(dòng)態(tài)代理。
增強(qiáng)器(Advisor)
Advisor是切面的另外一種實(shí)現(xiàn),能夠?qū)⑼ㄖ愿鼮閺?fù)雜的方式織入到目標(biāo)對(duì)象中,是將通知包裝為更復(fù)雜切面的裝配器。Advisor由切入點(diǎn)和Advice組成。 Advisor這個(gè)概念來(lái)自于Spring對(duì)AOP的支撐,在AspectJ中是沒(méi)有等價(jià)的概念的。Advisor就像是一個(gè)小的自包含的切面,這個(gè)切面只有一個(gè)通知。切面自身通過(guò)一個(gè)Bean表示,并且必須實(shí)現(xiàn)一個(gè)默認(rèn)接口。
簡(jiǎn)單來(lái)講,整個(gè) aspect 可以描述為: 滿足 pointcut 規(guī)則的 joinpoint 會(huì)被添加相應(yīng)的 advice 操作。
將上方通過(guò)注解使用切面的方式改寫(xiě)一下:
@Component
@Aspect
@Sl4fj
public class ElasticSearchExecuteLog {
// 不使用注解,而通過(guò)基礎(chǔ)的規(guī)則配置選擇切入點(diǎn),表達(dá)式是指com.gcc.controller
// 包下的所有類的所有方法
@Pointcut("execution(* com.gcc.controller..*(..))")
public void aspect() {}
// 通知,在符合aspect切入點(diǎn)的方法前插入如下代碼,并且將連接點(diǎn)作為參數(shù)傳遞
@Before("aspect()")
public void log(JoinPoint joinPoint) { //連接點(diǎn)作為參數(shù)傳入
// 獲得類名,方法名,參數(shù)和參數(shù)名稱。
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關(guān)于AOP中一些類及函數(shù)的使用
JoinPoint對(duì)象
JoinPoint對(duì)象封裝了SpringAop中切面方法的信息,在切面方法中添加JoinPoint參數(shù),就可以獲取到封裝了該方法信息的JoinPoint對(duì)象.
| 方法 | 作用 | 返回對(duì)象 |
|---|---|---|
| getSignature() | 獲取封裝了署名信息的對(duì)象,在該對(duì)象中可以獲取到目標(biāo)方法名,所屬類的Class等信息 | Signature |
| getArgs() | 獲取 獲取傳入目標(biāo)方法的參數(shù)對(duì)象 | Object[] |
| getTarget() | 獲取被代理的對(duì)象 | Object |
proceedingJoinPoin對(duì)象
proceedingJoinPoin對(duì)象是JoinPoint的子類,在原本JoinPoint的基礎(chǔ)上,放開(kāi)了Proceeed()的使用,一般在環(huán)繞通知@Around時(shí)使用:
Object proceed() throws Throwable //執(zhí)行目標(biāo)方法 Object proceed(Object[] var1) throws Throwable //傳入的新的參數(shù)去執(zhí)行目標(biāo)方法
以上就是SpringBoot搭配AOP實(shí)現(xiàn)自定義注解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot AOP自定義注解的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java程序開(kāi)發(fā)環(huán)境配置圖文教程
這篇文章主要為大家詳細(xì)介紹了Java程序開(kāi)發(fā)環(huán)境配置圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07
jdk中動(dòng)態(tài)代理異常處理分析:UndeclaredThrowableException
最近在工作中遇到了報(bào)UndeclaredThrowableException的錯(cuò)誤,通過(guò)查找相關(guān)的資料,終于解決了,所以這篇文章主要給大家介紹了關(guān)于jdk中動(dòng)態(tài)代理異常處理分析:UndeclaredThrowableException的相關(guān)資料,需要的朋友可以參考下2018-04-04
SpringBoot 整合WebSocket 前端 uniapp 訪問(wèn)的詳細(xì)方法
這篇文章主要介紹了SpringBoot 整合WebSocket 前端 uniapp 訪問(wèn)的詳細(xì)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
聊聊ResourceBundle和properties讀取配置文件的區(qū)別
這篇文章主要介紹了ResourceBundle和properties讀取配置文件的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java如何重寫(xiě)object類的equals方法詳解
這篇文章主要給大家介紹了關(guān)于Java如何重寫(xiě)object類的equals方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
java Springboot實(shí)現(xiàn)多文件上傳功能
這篇文章主要為大家詳細(xì)介紹了java Springboot實(shí)現(xiàn)多文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08

