java注解結(jié)合aspectj AOP進(jìn)行日志打印的操作
在很多系統(tǒng)開發(fā)中,我們希望在指定的方法調(diào)用之前或者之后能打印出該方法的調(diào)用時(shí)間以及方法的出參和入?yún)?,就可以使用spring的AOP,還可以結(jié)合自定義的注解進(jìn)行進(jìn)行一些指定參數(shù)的打印
例如:
一個(gè)分層的架構(gòu)系統(tǒng),每層都有自己的指定系統(tǒng)名字,并且每個(gè)方法都有自己指定的作用(通過注解指定,在切面的時(shí)候取出該參數(shù)),而且可以根據(jù)注解的指定日志類型(在注解中指定,在切面的時(shí)候取出參數(shù)進(jìn)行判斷,然后打印相對(duì)應(yīng)的日志格式)。
1.首先需要自定義注解:
systemName:表示該系統(tǒng)的名稱。
bizCode:表示具體的方法描述
logtype:表示日志的格式類型
package myspring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface LogAnnotation { String systemName(); String bizCode(); LogType logtype() default LogType.TIME; }
2.定義日志格式枚舉:
package myspring; public enum LogType { TIME("TIME", "方法調(diào)用時(shí)間"), PARAM("PARAM", "參數(shù)打印"); private String type; private String desc; LogType(String type, String desc) { this.type = type; this.desc = desc; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } }
3.切面代碼:
其中execution(* *(..))
第一個(gè)* :表示所有返回值
第二個(gè)* :表示所有包匹配規(guī)則和所有類匹配規(guī)則以及所有方法匹配規(guī)則
兩個(gè)點(diǎn).. :表示任何參數(shù)匹配
例如:
execution(* *..*Service.*(..))
表示任何返回值 任何包以Service結(jié)尾的類或者實(shí)現(xiàn)類的任何方法任何參數(shù)
*.. :表示所有包
* :Service表示所有以Service結(jié)尾的類或者實(shí)現(xiàn)類
execution(* cn.lijie.MyService.*(..))
表示任何返回值 cn.lijie包下面 MyService類或者實(shí)現(xiàn)類的所有方法 所有參數(shù)
代碼如下:
package myspring; import com.alibaba.fastjson.JSON; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; @Component @Aspect public class LogAspect { private static Logger thisLog = LoggerFactory.getLogger(LogAspect.class); private static Logger timeLog = LoggerFactory.getLogger(TimeTypeLog.class); private static Logger paramLog = LoggerFactory.getLogger(ParamTypeLog.class); @Around("execution(* *(..)) && @annotation(logAnnotation)") public Object log(ProceedingJoinPoint point, LogAnnotation logAnnotation) { StopWatch stop = new StopWatch(); stop.start(); boolean flag = false; Object retValue = null; try { retValue = point.proceed(); flag = true; } catch (Throwable throwable) { throwable.printStackTrace(); } finally { stop.stop(); if (logAnnotation.logtype().equals(LogType.TIME)) { timeLog(this.getMethod(point), point, stop.getTotalTimeMillis(), logAnnotation, flag); } else { paramLog(this.getMethod(point), point, retValue); } } return retValue; } private void timeLog(String method, ProceedingJoinPoint point, long totalTimeMillis, LogAnnotation logAnnotation, boolean flag) { timeLog.info("系統(tǒng)為:{},調(diào)用的方法名字:{},調(diào)用是否成功:{},運(yùn)行時(shí)間為:{}ms", logAnnotation.systemName(), method, flag, totalTimeMillis); } private void paramLog(String method, ProceedingJoinPoint point, Object retValue) { try { String result = JSON.toJSONString(retValue); //獲取入?yún)? Object[] args = point.getArgs(); StringBuffer sb = new StringBuffer(); for (Object obj : args) { String str = JSON.toJSONString(obj); sb.append(subStr(str, 200)).append(" "); } paramLog.info("調(diào)用方法為:{},入?yún)?{},出參為:{}", method, sb, subStr(result, 200)); } catch (Exception e) { thisLog.warn("切面日志 參數(shù)日志打印異常,異常信息:{}", e.getMessage()); } } private String getMethod(ProceedingJoinPoint pjp) { MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); return methodSignature.getDeclaringTypeName() + "#" + methodSignature.getMethod().getName(); } private String subStr(String string, int length) { if (!StringUtils.isEmpty(string)) { if (string.length() > length) { string = string.substring(0, 200); return string; } } return string; } }
4.定義一個(gè)操作對(duì)象:
package myspring; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component("logTest") public class LogTest { private static Logger logger = LoggerFactory.getLogger(LogTest.class); @LogAnnotation(systemName = "計(jì)算器系統(tǒng)", bizCode = "+", logtype = LogType.TIME) public int testLog01(int a, int b) { logger.info("進(jìn)入+運(yùn)算"); return a + b; } @LogAnnotation(systemName = "計(jì)算器系統(tǒng)", bizCode = "-", logtype = LogType.PARAM) public int testLog02(int a, int b) { logger.info("進(jìn)入-運(yùn)算"); return a - b; } }
5.定義兩個(gè)空類,用于區(qū)分不同類型的日志:
package myspring; public class TimeTypeLog {
package myspring; public class ParamTypeLog { }
6.spring xml配置文件:
<context:component-scan base-package="myspring"/> <aop:aspectj-autoproxy/>
7.啟動(dòng)spring的測(cè)試類:
package myspring; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); LogTest logTest = (LogTest) context.getBean("logTest"); logTest.testLog01(1, 2); logTest.testLog02(3, 4); context.registerShutdownHook(); } }
8.pom
<properties> <spring_version>4.3.8.RELEASE</spring_version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring_version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring_version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring_version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring_version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring_version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring_version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.11</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.4</version> </dependency> </dependencies>
最后執(zhí)行測(cè)試的類,日志打印如下:
補(bǔ)充:spring boot 自定義注解實(shí)現(xiàn)自動(dòng)打印方法日志(入?yún)⒑头祷刂担?/strong>
pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.aline</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
定義一個(gè)日志注解
SysLog.java
package com.aline.demo.annotation; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { }
定義一個(gè)日志實(shí)例緩存
LoggerCache.class package com.aline.demo.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; public class LoggerCache { /** * 日志實(shí)例記錄在內(nèi)存中 */ private static HashMap<String, Logger> LOGERS = new HashMap<String, Logger>(); /** * 根據(jù)類名獲取緩存的日志實(shí)例 * @param className 包名加類名 this.getClass().getName(); * @return */ public static Logger getLoggerByClassName(String className) { // 從靜態(tài)map中獲取日志實(shí)例 Logger logger = LOGERS.get(className); // 如果沒取到 if (logger == null) { // 創(chuàng)建一個(gè)日志實(shí)例 logger = LoggerFactory.getLogger(className); // 加入到靜態(tài)map中 LOGERS.put(className, logger); } // 返回 return logger; } }
定義一個(gè)切面實(shí)現(xiàn)日志記錄
SysLogAspect.java
package com.aline.demo.aspect; import com.alibaba.fastjson.JSON; import com.aline.demo.util.LoggerCache; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Aspect @Component public class SysLogAspect { @Pointcut("@annotation(com.aline.demo.annotation.SysLog)") public void log() { } /** * 加入注解自動(dòng)記錄方法日志 * * @param joinPoint * @return * @throws Throwable */ @Around(value = "log()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // 獲取執(zhí)行方法的類的名稱(包名加類名) String className = joinPoint.getTarget().getClass().getName(); // 獲取實(shí)例和方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 從緩存中獲取日志實(shí)例 Logger log = LoggerCache.getLoggerByClassName(className); // 記錄日志 log.info(className + "." + method.getName() + "() 執(zhí)行"); Object[] args = joinPoint.getArgs(); log.info("Params\t===》\t" + JSON.toJSONString(args)); // 執(zhí)行方法獲取返回值 Object proceed = joinPoint.proceed(); // 記錄日志 log.info("Returns\t===》\t" + JSON.toJSONString(proceed)); // 返回 return proceed; } }
寫個(gè)controller測(cè)試
TestController.java
package com.aline.demo.controller; import com.aline.demo.annotation.SysLog; import com.aline.demo.util.LoggerCache; import org.slf4j.Logger; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.SimpleDateFormat; import java.util.Date; @RestController @RequestMapping("/test") public class TestController { static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); @GetMapping("/now") public String now(){ // 從緩存中獲取日志 Logger LOG = LoggerCache.getLoggerByClassName(this.getClass().getName()); LOG.info("自定義日志 ==》 日志內(nèi)容。。。"); return sdf.format(new Date()); } @GetMapping("/hello") @SysLog() public String hello(String name){ return "Hello, " + name; } }
訪問 http://localhost:8080/test/hello?name=aline
打印日志:
2019-05-09 16:58:20.410 INFO 40004 --- [nio-8080-exec-1] c.aline.demo.controller.TestController : com.aline.demo.controller.TestController.hello() 執(zhí)行,參數(shù) ==》 2019-05-09 16:58:20.584 INFO 40004 --- [nio-8080-exec-1] c.aline.demo.controller.TestController : Params ===》 ["aline"] 2019-05-09 16:58:20.598 INFO 40004 --- [nio-8080-exec-1] c.aline.demo.controller.TestController : Returns ===》 "Hello, aline"
訪問 http://localhost:8080/now
打印日志:
2019-05-09 16:58:29.208 INFO 40004 --- [nio-8080-exec-3] c.aline.demo.controller.TestController : 自定義日志 ==》 日志內(nèi)容。。。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Java正則驗(yàn)證正整數(shù)的方法分析【測(cè)試可用】
這篇文章主要介紹了Java正則驗(yàn)證正整數(shù)的方法,結(jié)合實(shí)例形式對(duì)比分析了java針對(duì)正整數(shù)的驗(yàn)證方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-08-08spring boot 防止重復(fù)提交實(shí)現(xiàn)方法詳解
這篇文章主要介紹了spring boot 防止重復(fù)提交實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了spring boot 防止重復(fù)提交具體配置、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下2019-11-11java編程之AC自動(dòng)機(jī)工作原理與實(shí)現(xiàn)代碼
這篇文章主要介紹了java編程之AC自動(dòng)機(jī)的有關(guān)內(nèi)容,涉及其應(yīng)用場(chǎng)景,運(yùn)行原理,運(yùn)行過程,構(gòu)造方法及Java中的實(shí)現(xiàn)代碼,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11Java實(shí)現(xiàn)調(diào)用第三方相關(guān)接口
最近在做一個(gè)項(xiàng)目,需要調(diào)用第三方接口,本文主要介紹了Java實(shí)現(xiàn)調(diào)用第三方相關(guān)接口,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09解決沒有@RunWith 和 @SpringBootTest注解或失效問題
這篇文章主要介紹了解決沒有@RunWith 和 @SpringBootTest注解或失效問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04