需求背景:需要在controller层的方法进行统一的日志记录,例如:日志记录请求参数,响应参数;记录请求处理时间(性能监控)。
1.maven依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- StopWatch 计时器 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
2.自定义注解
package com.example.demo.interceptor;
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 SystemLogAnnotation {
String value() default "日志注解";
}
3.切面类实现
package com.example.demo.interceptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.Signature;
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.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Slf4j
@Aspect
@Component
public class DeInterceptor {
private static final Logger log = LoggerFactory.getLogger(DeInterceptor.class);
/**
* 在方法执行之前和之后执行(计算执行时间)
*/
@Around("execution(* com.example.demo.yw.controller.*.*(..)) && (@annotation(SystemLogAnnotation))")
public Object interceptor(ProceedingJoinPoint point) throws Throwable {
// Spring计时器StopWatch
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 1.获取请求路径
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest httpServletRequest = requestAttributes.getRequest();
String url = httpServletRequest.getRequestURI();
// 2.获取请求参数
Object[] args = point.getArgs();
String reqParam = null;
String requestContentType = httpServletRequest.getContentType()== null ? "" : httpServletRequest.getContentType();
// 不同请求类型使用不同的方法获取入参
try{
if (requestContentType.contains("application/json")){
// args数组里面存储的就是controller里方法里面的入参顺序参数,一般JSON格式入参就一个对象接收参数,那就取第0个,否则可以全部输出args
reqParam = JSON.toJSONString(args[0]);
}else{
reqParam = JSON.toJSONString(httpServletRequest.getParameterMap());
}
}catch (Exception e){
e.getStackTrace();
}
// 3.获取方法相关信息
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
SystemLogAnnotation annotation = method.getAnnotation(SystemLogAnnotation.class);
String methodStr = point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName();
log.info("请求开始,请求路径:{},请求方法:{},请求方法说明:{},请求参数:{}",url,methodStr,annotation.value(),reqParam);
// 4.获取响应数据
Object result = point.proceed();
stopWatch.stop();
log.info("请求结束,耗时{}毫秒,响应参数:{}",stopWatch.getTime(),result);
return result;
}
}
关于这段注解的理解:
@Around("execution(* com.example.demo.demo01.controller.*.*(..)) && (@annotation(org.springframework.web.bind.annotation.RequestMapping))")
execution:用于匹配方法执行的连接点;@annotation:用于匹配当前执行方法持有指定注解的方法;具体的可以搜索AspectJ语法来了解;
4.controller层代码
package com.example.demo.yw.controller;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.interceptor.SystemLogAnnotation;
@Slf4j
@RequestMapping("/test")
@RestController
public class Test {
private static final Logger log = LoggerFactory.getLogger(Test.class);
@RequestMapping("/req01")
@SystemLogAnnotation("查询测试接口")
public Object postReq(String id){
try {
log.info("模拟处理请求.....");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return id;
}
}
4.控制台执行结果
如果需要,可以把相关信息记录到日志表。
Aop加自定义注解的使用场景还是比较多的,例如:Aop+自定义注解对入参进行操作、过滤、解密(点击跳转)