使用Sping AOP切面打印日志時,為了不影響之前的代碼,可以不攔截全部的controller層接口,而使用時注解的形式,在相應的接口方法加上日志注解,就可以打印請求參數和請求結果信息。
代碼如下:
1.定義切面類
1 @Aspect 2 @Component 3 @Slf4j 4 public class LogAspect { 5 6 private LogModel logModel; 7 8 /** 9 * 這里我們使用注解的形式 10 * 當然,我們也可以通過切點表達式直接指定需要攔截的package,需要攔截的class 以及 method 11 * 切點表達式: execution(...) 12 */ 13 @Pointcut("@annotation(com.zto.poseidon.datacenter.common.annotation.OperationLog)") 14 public void restControllerLog() { 15 } 16 17 /** 18 * 需要忽略的返回日志 19 */ 20 @Pointcut("!@annotation(com.zto.poseidon.datacenter.common.annotation.IgnoreResponseLog)") 21 public void ignoreResponseLog() { 22 } 23 24 @Before("restControllerLog()") 25 public void exBefore(JoinPoint joinPoint) { 26 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 27 HttpServletRequest request = attributes.getRequest(); 28 logModel = new LogModel(); 29 logModel.setUrl(request.getRequestURL().toString()); 30 logModel.setReqIp(request.getRemoteAddr()); 31 logModel.setStartTime(DateTime.now().toDate()); 32 logModel.setMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); 33 try { 34 logModel.setParams(getRequestParamsByJoinPoint(joinPoint)); 35 log.info("--------------請求參數日志------------"); 36 log.info(" ---> 請求參數 :{}", JSON.toJSONString(logModel)); 37 } catch (Exception e) { 38 log.info("異常信息:{}", ExceptionUtils.getStackTrace(e)); 39 } 40 } 41 42 @After("restControllerLog()") 43 public void exAfter(JoinPoint joinPoint) { 44 log.info(" ---> request method:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName() + " 執行完畢!"); 45 } 46 47 @AfterReturning(returning = "result", pointcut = "restControllerLog() && ignoreResponseLog()") 48 public void exAfterReturning(Object result) { 49 log.info("--------------請求結果日志------------"); 50 log.info(" ---> 請求結果 :{}", JSON.toJSONString(result)); 51 } 52 53 private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) { 54 //參數名 55 String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); 56 //參數值 57 Object[] paramValues = joinPoint.getArgs(); 58 return buildRequestParam(paramNames, paramValues); 59 } 60 61 private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) { 62 Map<String, Object> requestParams = new HashMap<>(); 63 for (int i = 0; i < paramNames.length; i++) { 64 Object value = paramValues[i]; 65 //如果是文件對象 66 if (value instanceof MultipartFile) { 67 MultipartFile file = (MultipartFile) value; 68 //獲取文件名 69 value = file.getOriginalFilename(); 70 } 71 requestParams.put(paramNames[i], value); 72 } 73 return requestParams; 74 } 75 76 @Data 77 public static class LogModel { 78 79 private String url; 80 81 private Date startTime; 82 83 private Date endTime; 84 85 private Long tookTime; 86 87 private Object result; 88 89 private String reqIp; 90 91 private Boolean success = Boolean.FALSE; 92 93 private String errMsg; 94 95 private Object queryString; 96 97 private String method; 98 99 private Map<String, Object> params; 100 101 private RuntimeException exception; 102 103 } 104 105 }
2.添加自定義日志打印注解,打印入參和出參日志
1 @Target({ElementType.METHOD, ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface OperationLog { 4 }
3.忽視請求結果日志打印注解
1 @Target({ElementType.METHOD, ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface IgnoreResponseLog { 4 boolean required() default true; 5 }
4.然后在需要打印的接口方法上添加 @OperationLog 注解就ok了
自定義注解@OperationLog 注解定義了可以使用在類和方法上,但使用在類上不生效。
看官方文檔解釋:
//@Around("@annotation(自定義注解)")//自定義注解標注在方法上的方法執行aop方法
如:@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
//@Around("@within(自定義注解)")//自定義注解標注在的類上;該類的所有方法(不包含子類方法)執行aop方法
如:@Around("@within(org.springframework.transaction.annotation.Transactional)")
//@Around("within(包名前綴.*)")//com.aop.within包下所有類的所有的方法都會執行(不包含子包) aop方法
如:@Around("within(com.aop.test.*)")
//@Around("within(包名前綴..*)")//com.aop.within包下所有的方法都會執行(包含子包)aop 方法
如:@Around("within(com.aop.test..*)")
//@Around("this(java類或接口)")//實現了該接口的類、繼承該類、該類本身的類---的所有方法(包括不是接口定義的方法,但不包含父類的方法)都會執行aop方法
如:@Around("this(com.aop.service.TestService)")
//@Around("target(java類或接口)")//實現了該接口的類、繼承該類、該類本身的類---的所有方法(包括不是接口定義的方法,包含父類的方法)
如:@Around("this(com.aop.service.TestService)")
//@Around("@target(自定義注解)")//springboot項目啟動報如下錯誤,沒有解決
// Caused by: java.lang.IllegalStateException:
// StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[] failed to start
所以,如果想在類上使用自定義注解,需要將 LogAspect.class 切面類里的
1 @Pointcut("@annotation(com.zto.poseidon.datacenter.common.annotation.OperationLog)") 2 public void restControllerLog() { 3 }
改為:
1 @Pointcut("@within(com.zto.poseidon.datacenter.common.annotation.OperationLog)") 2 public void restControllerLog() { 3 }
這樣就可以使用在類上了,但是我經過測試發現,改了之后只能用在類上,用在方法上就失效了,不能正常打印日志。暫時沒找到原因,如果有找到原因的朋友,可以留言評論一下。