Spring AOP 实现日志记录案例详解

目录

一、引言:为什么需要日志记录?

二、Spring AOP 核心概念

1. 角色划分

2. 关键注解

三、基础案例:简单日志记录

1. 场景描述

2. 实现步骤

(1)创建自定义注解

(2)编写切面类

(3)在业务方法上使用注解

(4)测试输出

四、进阶案例:动态参数日志与异常处理

1. 场景升级

2. 实现方案

(1)扩展自定义注解

(2)改造切面逻辑

(3)业务方法示例

(4)异常日志输出

五、高级应用:日志持久化到数据库

1. 场景需求

2. 实现步骤

(1)创建日志表

(2)定义日志实体

(3)创建切面类

六、总结与注意事项

1. 优势

2. 注意事项

七、扩展思考


一、引言:为什么需要日志记录?

在软件开发中,日志记录是排查问题、监控行为、审计操作的重要手段。但传统日志代码(如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。
  • 异常处理:切面中捕获异常后需决定是否向上抛出。

七、扩展思考

  1. 如何实现按需开启/关闭特定包的日志?
  2. 如何将日志同步到Elasticsearch等分布式存储?
  3. 如何设计通用的日志切面,支持多种存储方式(文件、数据库、云日志)?

通过本文案例,您已掌握Spring AOP日志记录的核心技能。赶快在项目中实践吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码农小灰

你的鼓励是我创造最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值