JAVA中用AOP打日志资盅,偷個懶

日志記錄

在寫接口時,為了方便后續(xù)問題排查踊赠,需要記錄接口的入?yún)⒑统鰠?結(jié)果)呵扛。

常用的方法是使用slf4j的.info方法打日志。

如:

public class TestAopLogServiceImpl implements TestAopLogService {
 
    private static final Logger log = LoggerFactory.getLogger(TestAopLogServiceImpl.class);
 
    @Override
    public TestAopLogRespDTO testAopLog(TestAopLogReqDTO reqDTO) {
        // 記錄入?yún)?        log.info("methodName = {}, parameters = {}", "testAopLog", JSON.toJSONString(reqDTO));
        // 此處開始調(diào)用服務(wù)獲取響應(yīng)DTO
        // mock一個
        TestAopLogRespDTO respDTO = new TestAopLogRespDTO();
        respDTO.setRespCode(200);
        respDTO.setRespMsg("success");
        respDTO.setRespData("this is data");
        // 記錄結(jié)果
        log.info("methodName = {}, result = {}", "testAopLog", JSON.toJSONString(respDTO));
        return respDTO;
    }
}

方法邏輯很清晰筐带,包含記錄入?yún)⒔翊@取結(jié)果,記錄結(jié)果伦籍,返回蓝晒。

日志記錄就不再展示了腮出。

然而,每個接口都需要記錄入?yún)⒑徒Y(jié)果芝薇,略微有點小麻煩利诺。

于是,使用AOP搞定剩燥。

注:AOP可以搞定這個問題慢逾,本次試驗不保證也不認為這是一個完美的方案,只能說是一種思路灭红。

@Around("execution(* com.XXX.service.impl..*.*(..))")
public Object aopLog(ProceedingJoinPoint joinPoint) throws Throwable {
    // 反射調(diào)用方法所在類
    Class clz = joinPoint.getTarget().getClass();
    String methodName = joinPoint.getSignature().getName();
    Object[] paramValues = joinPoint.getArgs();
    log.info("className = {}, methodName = {}, parameters = {}", clz.getSimpleName(), methodName, JSON.toJSONString(paramValues));
    // 獲取方法結(jié)果
    Object result = joinPoint.proceed();
    log.info("className = {}, methodName = {}, parameters = {}", clz.getSimpleName(), methodName, JSON.toJSONString(result));
    // 返回
    return result;
}

關(guān)于AOP的內(nèi)容略過侣滩。

一頓操作猛如虎。該方法通過joinPoint獲取對應(yīng)的類变擒,方法名君珠,入?yún)⒌龋⒄{(diào)用其proceed方法計算出結(jié)果娇斑。

加入此AOP之后策添,service.impl下所有包下的所有類的所有方法都會被代理。
接口的代碼量減少毫缆。

public TestAopLogRespDTO testAopLog(TestAopLogReqDTO reqDTO) {
    // 此處開始調(diào)用服務(wù)獲取響應(yīng)DTO
    // mock一個
    TestAopLogRespDTO respDTO = new TestAopLogRespDTO();
    respDTO.setRespCode(200);
    respDTO.setRespMsg("success");
    respDTO.setRespData(((TestAopLogService) AopContext.currentProxy()).test());
    return respDTO;
}

然而唯竹,這樣的操作會讓之后的維護者以為是沒打日志,存在重復加日志的可能性苦丁。

此外浸颓,并不是所有接口都需要打日志。有些QPS超高的接口旺拉,打了日志反而消耗存儲产上。

如果在方法上加需要打日志的注解,既可以表示打了日志蛾狗,也可以做到選擇性打日志晋涣。

新建兩個注解,對應(yīng)需要記錄入?yún)⒑陀涗浗Y(jié)果沉桌。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AddParamLog {
}
  
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AddResultLog {
}

此時谢鹊,AOP修改為:

@Around("execution(* com.XXX.service.impl..*.*(..))")
public Object aopLog(ProceedingJoinPoint joinPoint) throws Throwable {
    // 反射調(diào)用方法所在類
    Class clz = joinPoint.getTarget().getClass();
 
    // 反射出被攔截的方法,以判斷是否包含注解
    String methodName = joinPoint.getSignature().getName();
    Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
    Method method = clz.getMethod(methodName, parameterTypes);
    //
 
    if (method.getAnnotation(AddParamLog.class) != null) {
        // 獲取當前方法入?yún)?打日志用
        Object[] paramValues = joinPoint.getArgs();
        log.info("className = {}, methodName = {}, parameters = {}", clz.getSimpleName(), methodName, JSON.toJSONString(paramValues));
    }
    // 獲取方法結(jié)果
    Object result = joinPoint.proceed();
    if (method.getAnnotation(AddResultLog.class) != null) {
        log.info("className = {}, methodName = {}, parameters = {}", clz.getSimpleName(), methodName, JSON.toJSONString(result));
    }
    // 返回
    return result;
}

該方法反射出對應(yīng)方法,判斷其是否包含注解蒲牧,以決定是否需要記錄日志撇贺。

此外,可以使用更簡單的注解方式冰抢,直接在@Around注解中指定只有使用了AddParamLog注解的方法才會被代理松嘶。

@Around("@annotation(AddParamLog)")

也可在注解中增加屬性,進一步擴展功能挎扰。如在AddParamLog注解中增加time屬性翠订,表示只在某時間段內(nèi)才打日志巢音。

用AOP打日志就基本搞定了。


疑問:
如果在接口中調(diào)用另一個會被代理的接口尽超,兩個接口的日志都會被記錄么官撼?

答案是否定的,因為AOP生成的是代理類似谁,在代理類中調(diào)用test()或者this.test()方法傲绣,指向的都是原類,不會記錄日志巩踏。
如下:

@AddParamLog
@Override
public TestAopLogRespDTO testAopLog(TestAopLogReqDTO reqDTO) {
    // 此處開始調(diào)用服務(wù)獲取響應(yīng)DTO
    // mock一個
    TestAopLogRespDTO respDTO = new TestAopLogRespDTO();
    respDTO.setRespCode(200);
    respDTO.setRespMsg("success");
    // 此處調(diào)用另一個理論上會被代理的接口
    respDTO.setRespData(test());
    return respDTO;
}
 
@AddParamLog
@Override
public String test() {
    return "ceshi";
}

testAopLog方法在調(diào)用test時候秃诵,test不會記錄日志,而直接調(diào)用test方法時塞琼,會記錄菠净。

那么如何解決呢,有兩個方法彪杉。主要操作是拿到代理的引用毅往,再進行test方法調(diào)用。

方法一:
更新xml配置為

<aop:aspectj-autoproxy expose-proxy="true" />

更新調(diào)用方法為

((TestAopLogService) AopContext.currentProxy()).test()

方法二:
注入本身

public class TestAopLogServiceImpl implements TestAopLogService {
  
    @Autowirde
    private TestAopLogService self;
  
    @AddParamLog
    @Override
    public TestAopLogRespDTO testAopLog(TestAopLogReqDTO reqDTO) {
        // 此處開始調(diào)用服務(wù)獲取響應(yīng)DTO
        // mock一個
        TestAopLogRespDTO respDTO = new TestAopLogRespDTO();
        respDTO.setRespCode(200);
        respDTO.setRespMsg("success");
        // 此處調(diào)用另一個理論上會被代理的接口
        respDTO.setRespData(self.test());
        return respDTO;
    }
 
    @AddParamLog
    @Override
    public String test() {
        return "ceshi";
    }
}

當然派近,這兩種操作都是不優(yōu)雅的攀唯。

最棒的方法是避免在接口中調(diào)用另外接口的方法。


AOP注解的value配置參考构哺,即切面Pointcut配置:
使用最頻繁的是execution和@annotation革答。

舉例:
任意公共方法的執(zhí)行:
execution(public * *(..))
任何一個以“set”開始的方法的執(zhí)行:
execution(* set*(..))
AccountService 接口的任意方法的執(zhí)行:
execution(* com.xyz.service.AccountService.*(..))
定義在service包里的任意類的任意方法的執(zhí)行:
execution(* com.xyz.service.*.*(..))
定義在service包和所有子包里的任意類的任意方法的執(zhí)行:
execution(* com.xyz.service..*.*(..))
定義在pointcutexp包和所有子包里的JoinPointObjP2類的任意方法的執(zhí)行:
execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")

@annotation(org.springframework.transaction.annotation.Transactional)

此外切點表達式支持與或非運算战坤,很強大曙强。


參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市途茫,隨后出現(xiàn)的幾起案子碟嘴,更是在濱河造成了極大的恐慌,老刑警劉巖囊卜,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜扇,死亡現(xiàn)場離奇詭異,居然都是意外死亡栅组,警方通過查閱死者的電腦和手機雀瓢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玉掸,“玉大人刃麸,你說我怎么就攤上這事∷纠耍” “怎么了泊业?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵把沼,是天一觀的道長。 經(jīng)常有香客問我吁伺,道長饮睬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任篮奄,我火速辦了婚禮捆愁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窟却。我一直安慰自己牙瓢,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布间校。 她就那樣靜靜地躺著矾克,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憔足。 梳的紋絲不亂的頭發(fā)上胁附,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音滓彰,去河邊找鬼控妻。 笑死,一個胖子當著我的面吹牛揭绑,可吹牛的內(nèi)容都是我干的弓候。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼他匪,長吁一口氣:“原來是場噩夢啊……” “哼菇存!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邦蜜,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤依鸥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悼沈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贱迟,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年絮供,在試婚紗的時候發(fā)現(xiàn)自己被綠了衣吠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡壤靶,死狀恐怖缚俏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤袍榆,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布胀屿,位于F島的核電站,受9級特大地震影響包雀,放射性物質(zhì)發(fā)生泄漏宿崭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一才写、第九天 我趴在偏房一處隱蔽的房頂上張望葡兑。 院中可真熱鬧,春花似錦赞草、人聲如沸讹堤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洲守。三九已至,卻和暖如春沾凄,著一層夾襖步出監(jiān)牢的瞬間梗醇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工撒蟀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叙谨,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓保屯,卻偏偏與公主長得像手负,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子姑尺,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理竟终,服務(wù)發(fā)現(xiàn),斷路器股缸,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法衡楞,類相關(guān)的語法,內(nèi)部類的語法敦姻,繼承相關(guān)的語法,異常的語法歧杏,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • 怕過生日 《午夜之子》(Midnight's Children)薩曼?魯西迪(Salman Rushdie) 你即...
    蘿卜閱讀 146評論 0 0
  • 小五臺連穿反穿gps軌跡 完整版 于 2009-09-05 00:20 出發(fā),歷時 18 小時, 24 分鐘 河北...
    六只腳閱讀 1,071評論 0 1