SpringBoot 自定義注解異步記錄復(fù)雜日志詳解
1、背景
最近接手一個(gè)任務(wù),需要給當(dāng)前項(xiàng)目加一個(gè)較為復(fù)雜的日志。有多復(fù)雜呢? 要有日志類(lèi)型、不同日志類(lèi)型要有不同的操作和備注等。作為小白的我最開(kāi)始的做法是在業(yè)務(wù)層寫(xiě)代碼記錄日志,好處就是方便,壞處就是這種做法直接侵襲Service層,Service層無(wú)法做到職責(zé)單一了。
經(jīng)導(dǎo)師點(diǎn)撥,改用自定義注解+AOP切面異步日志
2、技術(shù)方案-自定義注解
注解(Annotation),也叫元數(shù)據(jù)。
2.1 注解介紹
注解其實(shí)就是代碼里的特殊標(biāo)記,這些標(biāo)記可以在編譯、類(lèi)加載、運(yùn)行時(shí)被讀取,并執(zhí)行相應(yīng)的處理。通過(guò)使用注解,程序員可以在不改變?cè)羞壿嫷那闆r下,在源文件中嵌入一些補(bǔ)充信息。
2.2 元注解
元注解用于修飾其他注解的注解,在JDK5.0中提供了四種元注解:Retention、Target、Documented、Inherited
(1) Retention介紹: 用于修飾注解,用于指定修飾的哪個(gè)注解的聲明周期。@Rentention包含一個(gè)RetentionPolicy枚舉類(lèi)型的成員變量,使用@Rentention時(shí)必須為該value成員變量指定值
- RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),編譯器直接丟棄這種策略的注釋?zhuān)?class文件中不會(huì)保留注解信息
- RetentionPolicy.CLASS:在class文件中有效(即class保留),保留在.class文件中,但是當(dāng)運(yùn)行java程序時(shí),他就不會(huì)繼續(xù)加載了,不會(huì)保留在內(nèi)存中,JVM不會(huì)保留注解。如果注解沒(méi)有加Retention元注解,那么相當(dāng)于默認(rèn)的注解就是這種狀態(tài)。
- RetentionPolicy.RUNTIME:在運(yùn)行有效(即運(yùn)行時(shí)保留),當(dāng)運(yùn)行Java程序時(shí),JVM會(huì)保留注釋?zhuān)虞d在內(nèi)存中,那么程序可以通過(guò)反射獲取該注釋。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
(2)Target 介紹: 用于修飾注解的注解,定義當(dāng)前注解能夠修飾程序中的哪些元素(類(lèi)、屬性、方法,構(gòu)造器等等)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
(3)Documented 介紹: 用于指定被該元注解修飾的注解類(lèi)將被javadoc工具提取成文檔。默認(rèn)情況下,javadoc是不包括注解的,但是加上這個(gè)注解生成的文檔中就會(huì)帶著注解了
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
(4)Inherited 介紹:被它修飾的Annotation將具有繼承性。如果某個(gè)類(lèi)使用了被@Inherited修飾的Annotation,則其子類(lèi)將自動(dòng)具有該注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
注解的基礎(chǔ)知識(shí)基本介紹完畢,我們開(kāi)始寫(xiě)起來(lái)吧!??!
2.3 實(shí)現(xiàn)自定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Log {
// 日志類(lèi)型
LogType logType() ;
// 操作類(lèi)型
OperateType operate();
// 備注
String note() default "";
}
3、技術(shù)方案-AOP切面
AOP切面編程一般可以幫助我們?cè)诓恍薷默F(xiàn)有代碼的情況下,對(duì)程序的功能進(jìn)行拓展, 往往用于實(shí)現(xiàn) 日志處理,權(quán)限控制,性能檢測(cè),事務(wù)控制等。AOP實(shí)現(xiàn)的原理就是動(dòng)態(tài)代理。在有接口的情況下,使用JDK動(dòng)態(tài)代理,在沒(méi)有接口的情況下使用cglib動(dòng)態(tài)代理。
3.1 AOP術(shù)語(yǔ)解析
(1) Joint point:類(lèi)里面那些可以被增強(qiáng)的方法,這些方法被稱(chēng)之為連接點(diǎn)
(2) PointCut:實(shí)際被增強(qiáng)的方法,這些方法被稱(chēng)之為連接點(diǎn)
(3) Advice:實(shí)際增加的邏輯部分稱(chēng)為通知
(4) Target:被增強(qiáng)功能的對(duì)象(被代理的對(duì)象)
(5) Aspect:Aspect 聲明類(lèi)似與Java中的類(lèi)聲明,在Aspect中會(huì)包含著一些PointCut以及相應(yīng)的Advice.
(6) Weaving:創(chuàng)建代理對(duì)象并實(shí)現(xiàn)功能增強(qiáng)的聲明并運(yùn)行過(guò)程將Aspect和其他對(duì)象連接起來(lái),并創(chuàng)建Adviced object的過(guò)程
3.2 切入點(diǎn)表達(dá)式
切入點(diǎn)表達(dá)式:通過(guò)一個(gè)表達(dá)式來(lái)確定AOP要增強(qiáng)的是哪個(gè)或者哪些方法.
3.3 ADVICE通知類(lèi)型
(1)前置通知:@Before 執(zhí)行前置通知,并通過(guò)JointPoint獲取方法中的參數(shù)
@Aspect
@Component
public class DaoAspect {
/*
前置通知:在執(zhí)行addUser方法之前,執(zhí)行前置通知,并通過(guò)JointPoint獲取addUser()方法中的參數(shù)
*/
@Before("execution(* com.xzit.dao.Impl.UserDaoImpl.addUser(..))")
public void methodBefore(JoinPoint joinPoint){
System.out.println("methodBefore invoked ... ...");
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.toString(args));
}
}
(2)后置通知:@After 切點(diǎn)方法是否出現(xiàn)異常,后置通知都會(huì)執(zhí)行
(3)返回通知:@AfterReturning 切點(diǎn)出現(xiàn)異常,返回通知不會(huì)執(zhí)行
(4)異常通知:@AfterThrowing 切點(diǎn)方法出現(xiàn)異常就執(zhí)行,不出現(xiàn)異常就不執(zhí)行
(5)環(huán)繞通知:@Around 在切點(diǎn)方法之前和之后執(zhí)行。是@Before和@AfterReturing 相結(jié)合
3.4 技術(shù)實(shí)現(xiàn)
根據(jù)任務(wù)背景,我選擇了返回通知@AfterReturning。使用返回通知一定要注意的是:由于我需要用到返回值,所以會(huì)用@AfterReturning注解中的returning屬性來(lái)獲取方法的返回值
- returning指定的名稱(chēng)必須和切面方法參數(shù)中的名稱(chēng)相同。例如在下面代碼中指定的"cId"都是相同的
@AfterReturning(pointcut = "@annotation(com.xxx.xxx.xxx.Log)",
returning = "cId")
public void handleRdLogs(JoinPoint joinPoint, int cId)
- 切面方法參數(shù)中的參數(shù)類(lèi)型必須和方法返回類(lèi)型一致
@AfterReturning(pointcut = "@annotation(com.xxx.xxx.xxx.Log)",
returning = "cId")
public void handleRdLogs(JoinPoint joinPoint, int cId) {
// 獲取方法簽名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 獲取@Log注解內(nèi)容
if (!methodSignature.getMethod().isAnnotationPresent(Log.class)) {
log.error("獲取注解@Log失敗");
throw new Exception("獲取注解@Log失敗");
}
Log log = methodSignature.getMethod().getAnnotation(Log.class);
// 輸出日志的備注
System.out.println(log.note())
}
3.5 相關(guān)操作
(1) 獲取方法簽名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();`
(2)根據(jù)方法簽名獲取自定義注解
Log log = methodSignature.getMethod().getAnnotation(Log.class);
(3)根據(jù)自定義注解獲取,注解內(nèi)部的參數(shù)
System.out.println(log.note())
4、高級(jí)操作
場(chǎng)景:不僅需要獲取返回值,還得知道方法參數(shù)的值,而且方法參數(shù)的值不能作為返回值,這個(gè)該怎么辦呢?
秀操作開(kāi)始:
第一步: 在注解上寫(xiě) "#" + "方法參數(shù)的名"
@Log(note = "#note")
public int rdAuditReturn(String note) {
System.out.println(note)
xxxx.....
xxxx.....
業(yè)務(wù)代碼.....
xxxx.....
xxxx....
return cId;
}
第二步: 實(shí)現(xiàn)切面,只要調(diào)用這個(gè)方法,就可以得到note的值了
private final ExpressionParser parser = new SpelExpressionParser();
private final EvaluationContext evaluationContext = new StandardEvaluationContext();
private void getNote(JoinPoint joinPoint, StringBuilder noteBuilder, String note) throws NoSuchMethodException {
if (!StringUtils.isBlank(note)) {
Class<?> targetCls = joinPoint.getTarget().getClass();
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
String[] parameterNames = pnd.getParameterNames(targetMethod);
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; ++i) {
int index = i;
Optional.ofNullable(args[i]).ifPresent(param -> {
String paramName = parameterNames[index];
this.evaluationContext.setVariable(paramName, param);
});
}
Optional.ofNullable(this.parser.parseExpression(note).getValue(this.evaluationContext)).ifPresent(k ->
noteBuilder.append((String) k)
);
}
}以上就是SpringBoot 自定義注解異步記錄復(fù)雜日志詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot注解異步日志記錄的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringSceurity實(shí)現(xiàn)短信驗(yàn)證碼登陸
這篇文章主要介紹了SpringSceurity實(shí)現(xiàn)短信驗(yàn)證碼登陸,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
詳解Java線(xiàn)程池如何實(shí)現(xiàn)優(yōu)雅退出
這篇文章我們將從源碼角度深度解析線(xiàn)程池是如何優(yōu)雅的退出程序的,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)java線(xiàn)程池有一定幫助,需要的可以參考一下2022-07-07
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之環(huán)形鏈表
無(wú)論是靜態(tài)鏈表還是動(dòng)態(tài)鏈表,有時(shí)在解決具體問(wèn)題時(shí),需要我們對(duì)其結(jié)構(gòu)進(jìn)行稍微地調(diào)整。比如,可以把鏈表的兩頭連接,使其成為了一個(gè)環(huán)狀鏈表,通常稱(chēng)為循環(huán)鏈表2022-02-02
Java基礎(chǔ)教程之類(lèi)數(shù)據(jù)與類(lèi)方法
這篇文章主要介紹了Java基礎(chǔ)教程之類(lèi)數(shù)據(jù)與類(lèi)方法,本文是對(duì)類(lèi)的深入探討,類(lèi)數(shù)據(jù)指類(lèi)的一些屬性、參數(shù)等,類(lèi)方法就是類(lèi)包含的功能方法,需要的朋友可以參考下2014-08-08
基于SpringCloud手寫(xiě)一個(gè)簡(jiǎn)易版Sentinel
SpringCloud Alibaba Sentinel是當(dāng)前最為流行一種熔斷降級(jí)框架,簡(jiǎn)單易用的方式可以快速幫助我們實(shí)現(xiàn)服務(wù)的限流和降級(jí),保證服務(wù)的穩(wěn)定性。2021-05-05
java金額數(shù)字轉(zhuǎn)中文工具類(lèi)詳解
這篇文章主要為大家詳細(xì)介紹了java金額數(shù)字轉(zhuǎn)中文工具類(lèi)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04
IntelliJ IDEA 部署 Web 項(xiàng)目,看這一篇夠了!
這篇文章主要介紹了IntelliJ IDEA 部署 Web 項(xiàng)目的圖文教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05

