SpringBoot中日志切面实现小结
作者:这个名字应该没人用吧
在应用开发中,日志记录对于监控、调试和追踪用户行为至关重要。Spring Boot 虽然内置了强大的日志框架,但在某些情况下,我们可能需要更细粒度的日志管理。Spring AOP 提供了一种灵活的方式来实现方法级别的日志记录,而无需侵入业务代码。本文将介绍如何通过 Spring AOP 切面来实现这一功能。
什么是Spring AOP?
Spring AOP 是一个面向切面的编程(AOP)框架,它允许开发者将横切关注点(如日志记录、事务管理等)与业务逻辑分离。通过使用 Spring AOP,我们可以在不修改业务代码的情况下,为应用程序添加日志记录功能。
Spring AOP 详细可以看这篇文章 Spring AOP入门
日志切面的设计
在我们的示例中,我们定义了一个 SysLog
注解,用于标记需要记录日志的方法。接着,我们创建了一个日志切面 LogAspect
,它会拦截所有带有 SysLog
注解的方法,并记录日志信息。
SysLog 注解
首先,我们定义了一个 SysLog
注解,它可以用来标记需要记录日志的方法:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { String value() default ""; }
这个注解非常简单,它只有一个 value
属性,用于存储日志的描述信息。
日志切面 LogAspect
接下来,我们创建了 LogAspect
切面类,它会拦截所有带有 SysLog
注解的方法:
@Aspect @Component public class LogAspect { // 省略其他成员变量和方法 @Around("@annotation(sysLog)") public Object logAround(ProceedingJoinPoint joinPoint, SysLog sysLog) throws Throwable { // 日志记录逻辑 } }
在这个切面中,我们使用了 @Around
注解来定义一个环绕通知,它会在目标方法执行前后记录日志信息。
日志记录的实现
在 LogAspect
切面类中,我们实现了 logAround
方法,它会在目标方法执行前后记录日志信息:
@Around("@annotation(sysLog)") public Object logAround(ProceedingJoinPoint joinPoint, SysLog sysLog) throws Throwable { Log log = new Log(); log.setTimestamp(new Date()); log.setDescription(sysLog.value()); log.setMethodName(joinPoint.getSignature().getName()); log.setParameters(Arrays.toString(joinPoint.getArgs())); try { Object result = joinPoint.proceed(); log.setLevel("INFO"); log.setMessage("方法 " + joinPoint.getSignature().getName() + " 执行成功。"); return result; } catch (Exception e) { log.setLevel("ERROR"); log.setMessage("方法 " + joinPoint.getSignature().getName() + " 执行过程中发生异常。"); log.setException(e.toString()); throw e; } finally { logService.save(log); } }
在这个环绕通知中,我们首先创建了一个 Log
对象,并设置了日志的基本信息,如时间戳、描述、方法名和参数。然后,我们执行目标方法,并根据执行结果记录日志信息。如果目标方法执行过程中发生异常,我们会记录异常信息。
测试
完整代码
Maven依赖
在 pom.xml
文件中添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
SQL脚本
创建日志表的 SQL 脚本:
DROP TABLE IF EXISTS `logs`; CREATE TABLE `logs` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', `level` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '日志级别', `timestamp` datetime NOT NULL COMMENT '时间戳', `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '日志消息', `class_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '类名', `method_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '方法名', `parameters` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '参数', `user_identifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户标识', `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '异常信息', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '日志表' ROW_FORMAT = DYNAMIC;
日志实体类 Log
我们定义了一个 Log
实体类,用于存储日志信息:
/** * 日志实体类 * * @author 王闻薪 */ @Data @TableName("logs") public class Log implements Serializable { private static final long serialVersionUID = 1L; /** * 主键ID */ @TableId(value = "id", type = IdType.AUTO) private Long id; /** * 描述 */ @TableField("description") private String description; /** * 日志级别 */ @TableField("level") private String level; /** * 时间戳 */ @TableField("timestamp") private Date timestamp; /** * 日志消息 */ @TableField("message") private String message; /** * 类名 */ @TableField("class_name") private String className; /** * 方法名 */ @TableField("method_name") private String methodName; /** * 参数 */ @TableField("parameters") private String parameters; /** * 用户标识 */ @TableField("user_identifier") private String userIdentifier; /** * 异常信息 */ @TableField("exception") private String exception; }
这个实体类映射到数据库中的 logs
表,用于存储日志的详细信息。
切面代码
日志切面类 LogAspect
:
package org.example.demo.aspectj; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.example.demo.annotation.SysLog; import org.example.demo.entity.Log; import org.example.demo.service.ILogService; import org.example.demo.utils.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.Date; /** * 日志切面类 * 用于拦截标注有@SysLog注解的方法,并记录日志信息。 * * @author 王闻薪 */ @Slf4j @Aspect // 标记为切面类 @Component // 将此切面类交给Spring管理 public class LogAspect { @Autowired private ILogService logService; @Autowired private TokenService tokenService; /** * 环绕通知 * 拦截带有@SysLog注解的方法,并在方法执行前后进行日志记录。 * * @param joinPoint 连接点对象,包含方法的调用信息。 * @param sysLog 注解对象,包含日志描述信息。 * @return 方法执行结果。 * @throws Throwable 可能抛出的异常。 */ @Around("@annotation(sysLog)") public Object logAround(ProceedingJoinPoint joinPoint, SysLog sysLog) throws Throwable { // 创建日志对象 Log log = new Log(); log.setLevel("INFO"); log.setTimestamp(new Date()); log.setUserIdentifier(getUserId().toString()); log.setDescription(sysLog.value()); // 记录方法调用前的日志 logBefore(joinPoint, log); try { // 继续执行目标方法 Object result = joinPoint.proceed(); // 记录方法调用后的日志 logAfter(joinPoint, result, log); log.setMessage("方法 " + joinPoint.getSignature().getName() + " 执行成功。"); return result; } catch (Exception e) { // 记录异常日志 logException(joinPoint, e, log); log.setLevel("ERROR"); log.setMessage("方法 " + joinPoint.getSignature().getName() + " 执行过程中发生异常。"); throw e; // 重新抛出异常 } finally { // 保存日志到数据库 logService.save(log); } } /** * 记录方法调用前的日志信息。 * * @param joinPoint 连接点对象,包含方法的调用信息。 * @param log 日志对象,用于存储日志信息。 */ private void logBefore(ProceedingJoinPoint joinPoint, Log log) { log.setClassName(joinPoint.getSignature().getDeclaringTypeName()); log.setMethodName(joinPoint.getSignature().getName()); log.setParameters(Arrays.toString(joinPoint.getArgs())); log.setTimestamp(new Date()); // 设置日志时间 } /** * 记录方法调用后的日志信息。 * * @param joinPoint 连接点对象,包含方法的调用信息。 * @param result 方法执行结果。 * @param log 日志对象,用于存储日志信息。 */ private void logAfter(ProceedingJoinPoint joinPoint, Object result, Log log) { log.setMessage(log.getMessage() + result); } /** * 记录方法执行过程中的异常信息。 * * @param joinPoint 连接点对象,包含方法的调用信息。 * @param e 捕获的异常对象。 * @param log 日志对象,用于存储日志信息。 */ private void logException(ProceedingJoinPoint joinPoint, Exception e, Log log) { log.setException(e.toString()); log.setMessage(log.getMessage() + " 异常信息:" + e.getMessage()); } /** * 获取当前用户ID。 * * @return 用户ID。 */ private Long getUserId() { return tokenService.getUserId(); } }
结论
通过使用 Spring AOP 和注解,我们可以灵活地为 Spring Boot 应用程序添加日志记录功能,而无需修改业务逻辑代码。这不仅提高了代码的可维护性,还使得日志记录变得更加方便和强大。
到此这篇关于SpringBoot中日志切面实现小结的文章就介绍到这了,更多相关SpringBoot 日志切面内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!