SpringBoot通过自定义注解(AOP)优雅实现日志记录
为什么要通过AOP来实现日志记录
在业务处理中,我们经常需要将一些用户操作、行为或系统日志记录到数据库中,并在后台做展示。一般情况下我们需要在每个需要进行记录的业务方法中做sql操作,这样一来日志记录这种非业务层面的代码就会和业务代码耦合,显得非常难看。那么有没有一种优雅记录日志的办法呢?当然是有的,以下介绍一种基于自定义注解的使用AOP来记录日志的办法。
自定义注解
首先我们需要创建一个自定义的日志注解,使它能够被用于标记方法或类(取决于你的AOP要用于增强类还是方法)。注解的关键字类似于接口,但是前面多了一个@。
创建自定义注解前需要了解两个注解
@Target 注解:
表示注解可用于标记在什么位置
ElementType.ANNOTATION_TYPE 可以作用于注解
ElementType.CONSTRUCTOR 可以作用于构造器
ElementType.FIELD 可以作用于属性
ElementType.LOCAL_VARIABLE 可以作用于局部变量
ElementType.METHOD 可以作用于方法
ElementType.PACKAGE 可以作用于包
ElementType.PARAMETER 可以作用于方法参数
ElementType.TYPE 可以作用于类型,比如类、接口、枚举
@Retention注解:
说明注解的生命周期
SOURCE 注解只保留在源文件
CLASS 注解保留到CLASS文件
RUNTIME 注解在运行期间一直存在
@Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE}) // 表示注解可用于方法参数、方法、类
@Retention(RetentionPolicy.RUNTIME) // 表示注解一直存在
public @interface Log {
String title() default "";
}
这样一来,一个自定义Log注解就写好了。
将Log注解打到想进行日志记录的类或方法上
在开发中我们经常需要对接口处理时间进行优化,那么就有必要对接口的处理时间进行记录,因此以下以对接口处理时间进行记录为例。
我们将@Log注解打到Controller层的类上,下面新建一个拥有两个接口方法的控制器
@RestController
@RequestMapping(value = "/xxx")
@Log(title = "xxx模块")
public class XxxController {
@ApiOperation(value = "method1")
@RequestMapping(method = RequestMethod.GET)
public ApiResult<VO> getMethod1() {
// 调用业务层代码
return ApiResult.success();
}
@ApiOperation(value = "method2")
@RequestMapping(method = RequestMethod.GET)
public ApiResult<VO> getMethod2() {
// 调用业务层代码
return ApiResult.success();
}
}
创建切面,对拥有@Log注解的类进行增强
@Pointcut
定义切入点
@within 表示对持有某注解的类下所有方法进行切入
@Around
对切入点进行处理
ProceedingJoinPoint.proceed()表示对切入方法进行执行,并返回切入方法的结果。在proceed()前切入方法未执行,在proceed()后切入方法已执行,可以对返回结果进行统一增强处理。
@Aspect //表示这是一个切面
@Component
public class LogAspect {
@Resource
ISystemLogService systemLogService; // 记录接口时间的系统业务类
ThreadLocal<Long> currentTime = new ThreadLocal<>();
@Pointcut("@within(com.packagename.annotation.Log)") // 扫描所有标记了Log注解的类
public void logPointcut() {}
}
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
currentTime.set(System.currentTimeMillis()); // 记录当前线程的系统时间
Object result = joinPoint.proceed(); // 获取接口方法执行结果
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod(); // 获取接口方法
// 获取@Log注解的title值
Class<?> clazz = joinPoint.getTarget().getClass();
Log l = clazz.getAnnotation(Log.class);
String title = "";
if (ObjectUtil.isNotNull(l)) {
title = l.title();
}
// 获取接口方法上ApiOperation注解(查Swagger相关)的值,用以获得接口业务名称
String annotationValue = "";
ApiOperation a = method.getAnnotation(ApiOperation.class);
if (ObjectUtil.isNotNull(a)) {
annotationValue = a.value();
}
// 计算业务接口耗时,存入数据库
String operation = annotationValue + ",耗时" + (System.currentTimeMillis() - currentTime.get());
currentTime.remove();
SystemLog log = new SystemLog("INFO", operation);
log.setName(title);
// 日志记录
systemLogService.save(log);
// 将接口结果返回(与业务相关,不做任何处理)
return result;
}
图解
程序逻辑可以参考下图