目录
一、引言:为什么需要日志记录?
在软件开发中,日志记录是排查问题、监控行为、审计操作的重要手段。但传统日志代码(如System.out.println
)存在以下问题:
- 代码冗余:每个方法都需要手动添加日志代码。
- 耦合度高:日志逻辑与业务代码紧密耦合,难以维护。
- 功能单一:无法灵活扩展日志格式、存储方式等。
Spring AOP 通过面向切面编程,将日志记录与业务逻辑分离,实现:
- 统一管理日志逻辑
- 动态扩展日志内容
- 降低代码耦合度
二、Spring AOP 核心概念
1. 角色划分
- 切面(Aspect):封装日志逻辑的类(如
LogAspect
)。 - 切入点(Pointcut):定义日志触发的条件(如“所有Service层方法”)。
- 通知(Advice):切入代码的执行时机(如方法前、后、异常时)。
2. 关键注解
@Aspect
:标记切面类。@Component
:将切面类交给Spring管理。@Around
:环绕通知,可控制目标方法是否执行。@Before
/@After
:前置/后置通知。
三、基础案例:简单日志记录
1. 场景描述
记录任意标注方法的执行时间及参数,适用于调试和监控。
2. 实现步骤
(1)创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
String value() default ""; // 日志描述
}
(2)编写切面类
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Around("@annotation(loggable)")
public Object logMethod(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
// 获取方法信息
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.info("开始执行方法:{},参数:{}", methodName, Arrays.toString(args));
// 执行目标方法
Object result = joinPoint.proceed();
// 后置日志
logger.info("方法结束:{},返回值:{}", methodName, result);
return result;
}
}
(3)在业务方法上使用注解
@Service
public class UserService {
@Loggable("查询用户信息")
public User getUserById(Long id) {
// 模拟数据库查询
return new User(id, "张三");
}
}
(4)测试输出
[INFO] 开始执行方法:getUserById,参数:[1]
[INFO] 方法结束:getUserById,返回值:User{id=1, name='张三'}
四、进阶案例:动态参数日志与异常处理
1. 场景升级
- 记录交易类型(如转账、查询)
- 捕获异常并记录错误日志
- 支持自定义日志描述
2. 实现方案
(1)扩展自定义注解
public @interface RecordLog {
TransType transType(); // 必填交易类型
String description() default ""; // 可选描述
}
public enum TransType {
TRANSFER, QUERY
}
(2)改造切面逻辑
@Around("@annotation(recordLog)")
public Object around(ProceedingJoinPoint joinPoint, RecordLog recordLog) {
try {
// 获取注解参数
TransType type = recordLog.transType();
logger.info("[{}] 开始执行:{}", type, joinPoint.getSignature());
// 获取方法参数
Object[] args = joinPoint.getArgs();
if (args.length > 0 && args[0] instanceof String) {
logger.info("请求参数:{}", args[0]);
}
// 执行目标方法
return joinPoint.proceed();
} catch (Exception e) {
logger.error("[{}] 执行异常:{}", recordLog.transType(), e.getMessage());
throw e; // 向上抛出异常
} finally {
logger.info("[{}] 执行完成", recordLog.transType());
}
}
(3)业务方法示例
@RecordLog(transType = TransType.TRANSFER, description = "转账操作")
public void transferMoney(String account, double amount) {
if (amount > 10000) {
throw new RuntimeException("金额过大");
}
logger.info("转账成功:{}元 -> {}", amount, account);
}
(4)异常日志输出
[TRANSFER] 开始执行:public void com.example.transferMoney(String,double)
请求参数:["张三", 15000.0]
[TRANSFER] 执行异常:金额过大
[TRANSFER] 执行完成
五、高级应用:日志持久化到数据库
1. 场景需求
将用户操作日志(如登录、修改资料)存储到数据库,用于审计和追溯。
2. 实现步骤
(1)创建日志表
CREATE TABLE `operation_log` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
operation VARCHAR(50) NOT NULL,
details TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
(2)定义日志实体
@Entity
@Table(name = "operation_log")
public class OperationLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String operation;
private String details;
// Getter/Setter
}
(3)创建切面类
@Aspect
@Component
public class DbLogAspect {
@Autowired
private OperationLogRepository logRepository;
@Around("@annotation(log)")
public Object saveLog(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
// 获取日志信息
Long userId = getCurrentUserId(); // 需实现获取当前用户ID的逻辑
String operation = log.value();
String methodName = joinPoint.getSignature().getName();
// 执行目标方法
Object result = joinPoint.proceed();
// 保存日志到数据库
OperationLog logEntry = new OperationLog();
logEntry.setUserId(userId);
logEntry.setOperation(operation);
logEntry.setDetails(methodName + "执行成功");
logRepository.save(logEntry);
return result;
}
}
六、总结与注意事项
1. 优势
- 解耦业务与日志:修改日志逻辑无需改动业务代码。
- 灵活扩展:通过注解和切面组合,适应不同场景。
- 统一管理:集中控制日志格式、存储方式。
2. 注意事项
- 性能影响:大量日志可能影响性能,建议异步记录或分级日志。
- 代理模式:JDK动态代理(默认)不支持
final
类和方法,如需支持需改用CGLib。 - 异常处理:切面中捕获异常后需决定是否向上抛出。
七、扩展思考
- 如何实现按需开启/关闭特定包的日志?
- 如何将日志同步到Elasticsearch等分布式存储?
- 如何设计通用的日志切面,支持多种存储方式(文件、数据库、云日志)?
通过本文案例,您已掌握Spring AOP日志记录的核心技能。赶快在项目中实践吧!