在Spring Boot中,利用AOP(Aspect-Oriented Programming)结合自定义注解,可以优雅地实现操作日志记录。这种方式不仅解耦了业务逻辑与日志记录功能,还能让代码更简洁、可维护性更高。
以下是实现步骤:
1. 项目依赖
在Spring Boot项目中,确保以下依赖已存在(默认spring-boot-starter-aop
随spring-boot-starter
引入):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 自定义注解
首先创建一个注解,用于标记需要记录操作日志的方法。
package com.example.aspect;
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD) // 作用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface LogOperation {
String value() default ""; // 描述信息
}
3. 创建 AOP 切面
通过 AOP 切面拦截带有 @LogOperation
注解的方法,并实现日志记录。
package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
// 定义切点:匹配带有 @LogOperation 注解的方法
@Pointcut("@annotation(com.example.aspect.LogOperation)")
public void logPointcut() {
}
// 环绕通知:在方法执行前后做增强
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取方法信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogOperation logOperation = method.getAnnotation(LogOperation.class);
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 日志内容
String operationDesc = logOperation.value();
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
logger.info("开始执行操作: {}", operationDesc);
logger.info("类名: {}, 方法: {}", className, methodName);
logger.info("方法参数: {}", Arrays.toString(args));
Object result;
try {
result = joinPoint.proceed(); // 执行目标方法
logger.info("操作成功: {}", operationDesc);
} catch (Exception e) {
logger.error("操作失败: {}, 异常信息: {}", operationDesc, e.getMessage(), e);
throw e;
} finally {
long endTime = System.currentTimeMillis();
logger.info("操作耗时: {}ms", (endTime - startTime));
}
return result;
}
}
4. 示例:应用注解
在控制器或服务方法中使用 @LogOperation
注解。
控制器示例
package com.example.controller;
import com.example.aspect.LogOperation;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class ExampleController {
@GetMapping("/example")
@LogOperation("查询示例数据")
public String getExample(@RequestParam String name) {
return "Hello, " + name;
}
@PostMapping("/example")
@LogOperation("保存示例数据")
public String saveExample(@RequestBody String data) {
// 模拟保存逻辑
return "Data saved: " + data;
}
}
服务层示例
package com.example.service;
import com.example.aspect.LogOperation;
import org.springframework.stereotype.Service;
@Service
public class ExampleService {
@LogOperation("执行复杂业务逻辑")
public String complexOperation(String input) {
// 模拟复杂逻辑
return "Processed: " + input;
}
}
5. 日志输出示例
访问 /api/example?name=John
时,日志将输出如下内容:
INFO - 开始执行操作: 查询示例数据
INFO - 类名: com.example.controller.ExampleController, 方法: getExample
INFO - 方法参数: [John]
INFO - 操作成功: 查询示例数据
INFO - 操作耗时: 12ms
6. 进阶功能
(1) 保存日志到数据库
可以在 AOP 中直接将日志信息持久化到数据库,例如通过 LogRepository
保存日志:
@Autowired
private LogRepository logRepository;
logRepository.save(new LogEntity(operationDesc, className, methodName, args, timestamp));
(2) 动态获取用户信息
结合 Spring Security 或 Token 解析,获取当前用户信息:
String username = SecurityContextHolder.getContext().getAuthentication().getName();
logger.info("操作用户: {}", username);
(3) 日志级别控制
根据不同操作,选择合适的日志级别(INFO、DEBUG、ERROR 等)。
(4) 异步记录
使用 @Async
注解将日志记录操作改为异步,提高接口响应速度。
7. 完整性测试
对带有 @LogOperation
注解的方法进行测试,验证日志功能是否正常记录,并检查以下场景:
- 方法执行成功。
- 方法抛出异常。
- 方法参数为空。
通过以上实现,利用 AOP 和自定义注解,不仅能优雅地记录操作日志,还能保持业务逻辑的简洁性,为后续功能扩展打下基础。
发布者:myrgd,转载请注明出处:https://www.object-c.cn/4434