【JAVA】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;
}

图解

程序逻辑可以参考下图

请求
控制器
接口方法入口
切入点logPointcut
记录当前时间
proceed接口方法执行
计算得时间间隔
出切面
接口方法出口
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yahahaCoding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值