SpingBoot日志切面(使用自定义注解打印日志)
使用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 }
这样就可以使用在类上了,但是我经过测试发现,改了之后只能用在类上,用在方法上就失效了,不能正常打印日志。暂时没找到原因,如果有找到原因的朋友,可以留言评论一下。