Lombok + 自定义注解 + AOP:构建轻量级日志增强方案

简介: 在编写业务代码时,我们常常需要在方法入口打印入参、计算耗时、捕获异常。如果每个方法都手动编写 log.info(...),既枯燥又容易出错。借助 Lombok、自定义注解 和 Spring AOP,我们可以将日志逻辑统一抽离,实现零侵入、易维护的横切关注点管理。本文将从核心概念到实战示例,带你一步步搭建一套轻量级日志增强组件

​
在编写业务代码时,我们常常需要在方法入口打印入参、计算耗时、捕获异常。如果每个方法都手动编写 log.info(...),既枯燥又容易出错。借助 Lombok、自定义注解 和 Spring AOP,我们可以将日志逻辑统一抽离,实现零侵入、易维护的横切关注点管理。本文将从核心概念到实战示例,带你一步步搭建一套轻量级日志增强组件

一、Lombok:消灭样板代码
Lombok 是一颗 Java 语法糖,通过注解在编译期自动生成构造器、getter/setter、日志对象等。本节只关注它对日志的支持。

1.1 传统日志声明 vs Lombok
// 传统写法
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);

public void doSomething() {
    log.info("doing something");
}

}

// Lombok 写法
@Slf4j
public class UserService {
public void doSomething() {
log.info("doing something"); // 直接使用自动生成的 log 对象
}
}

@Slf4j 会在编译时注入名为 log 的静态常量,相当于一行注解取代了样板声明。同理还有 @Log、@Log4j2 等变体,可按日志框架选用。

除了日志,Lombok 常用的注解还有:

注解 功能
@Data 自动生成 getter/setter、toString、equals、hashCode
@Builder 生成建造者模式代码
@AllArgsConstructor / @NoArgsConstructor 生成全参/无参构造器
@Accessors(chain = true) 使 setter 返回 this,支持链式调用
这些注解能够大幅减少 POJO 和 Service 中的冗余代码,让类更专注于业务逻辑。

二、自定义注解:给方法贴上“描述标签”
Java 注解(Annotation)是一种元数据,本身不包含业务逻辑,但可以在运行时通过反射读取。为了标记需要记录日志的方法并携带业务描述,我们定义一个自定义注解 @LogRecord:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) // 运行时保留,反射可读
@Target({ElementType.METHOD}) // 只能用于方法
@Documented
public @interface LogRecord {
/**

 * 业务操作描述,例如:"查询用户列表"
 */
String value() default "";

/**
 * 是否打印方法返回值(默认 false,避免大对象导致性能问题)
 */
boolean printResult() default false;

}

使用方法非常简单,直接在 Controller 或 Service 的方法上添加即可:

@LogRecord(value = "分页查询商品", printResult = true)
public List listProducts(ProductQuery query) {
// 业务逻辑...
}

三、Spring AOP 核心概念速览
AOP(Aspect-Oriented Programming,面向切面编程)允许我们将散落在各处的横切关注点(日志、事务、权限等)抽离成独立的切面。关键是理解以下几个角色:

下图展示了 AOP 的工作流程:业务方法调用时,代理对象会先匹配切点,然后按配置的顺序执行相应的通知,最后才到达真正的目标方法。

四、实战:用环绕通知打造日志切面
下面我们实现一个切面,对标记了 @LogRecord 的方法自动输出:操作描述、入参、执行耗时,并按需打印返回值。

4.1 依赖准备
Spring Boot 项目通常已包含 AOP 启动器,如果没有,手动添加:


org.springframework.boot
spring-boot-starter-aop

4.2 切面代码
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
@Slf4j
public class LogRecordAspect {

private static final ObjectMapper MAPPER = new ObjectMapper();

// 切点:所有标注 @LogRecord 的方法
@Pointcut("@annotation(com.example.annotation.LogRecord)")
public void logPointcut() {}

@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();

    // ----- 获取方法签名和注解信息 -----
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    LogRecord logRecord = method.getAnnotation(LogRecord.class);

    String desc = logRecord.value();
    String className = joinPoint.getTarget().getClass().getSimpleName();
    String methodName = method.getName();
    Object[] args = joinPoint.getArgs();

    // 入参转 JSON(实际可加脱敏处理)
    String argsJson = toJson(args);

    log.info("【{}】开始执行 -> 类:{}, 方法:{}, 参数:{}", desc, className, methodName, argsJson);

    Object result;
    try {
        result = joinPoint.proceed();  // 执行目标方法
    } catch (Throwable e) {
        log.error("【{}】执行异常,异常信息:{}", desc, e.getMessage(), e);
        throw e; // 异常继续抛出,不破坏原有流程
    }

    long cost = System.currentTimeMillis() - start;
    log.info("【{}】执行结束,耗时:{}ms", desc, cost);

    if (logRecord.printResult()) {
        log.info("【{}】返回结果:{}", desc, toJson(result));
    }
    return result;
}

private String toJson(Object obj) {
    try {
        return MAPPER.writeValueAsString(obj);
    } catch (Exception e) {
        return obj == null ? "null" : obj.toString();
    }
}

}
4.3 代码讲解

4.4 使用效果
在项目中的方法上添加注解:

@RestController
@RequestMapping("/product")
@Slf4j
public class ProductController {

@LogRecord(value = "查询商品列表", printResult = true)
@GetMapping("/list")
public List<Product> list(ProductQuery query) {
    // 业务查询...
    return productService.list(query);
}

@LogRecord(value = "创建商品")
@PostMapping("/create")
public Product create(@RequestBody Product product) {
    return productService.save(product);
}

}
调用接口后,控制台输出类似:

业务代码无需任何日志逻辑,完美解耦

​

目录
相关文章
|
3月前
|
人工智能 JSON Java
LangChain4j 入门:ChatModel 第一个 AI 对话实战
LangChain4j 入门必读:Java 生态的大模型框架。从依赖配置到 ChatModel 第一个对话,再讲清 SystemMessage 与多模态输入。
332 0
|
28天前
|
缓存 人工智能 前端开发
Opus 5.5 生成视频的真相:它不生成视频,是写代码让浏览器逐帧画出来
Opus 5.5 生成视频刷屏,但它压根不生成视频——是写代码让浏览器逐帧画出来的。本文拆解完整渲染管线、四条路线怎么选,以及真实价格账单。
524 1
Opus 5.5 生成视频的真相:它不生成视频,是写代码让浏览器逐帧画出来
|
28天前
|
自然语言处理 Python
提示词工程怎么写才有效:拆解 5 大核心要素与实战代码示例
提示词工程不是玄学话术,而是一套可复用的设计方法。本文拆解 5 大核心要素,给出结构化提示词模板与 3 段实战代码,附常见坑。
288 0
提示词工程怎么写才有效:拆解 5 大核心要素与实战代码示例
|
28天前
|
SQL 人工智能 前端开发
Vue3+NestJS实战:纺织行业AI进销存系统架构设计
本文介绍面向纺织行业的AI进销存系统“轻账LightLedger”架构设计:基于Vue3+Element Plus前端与NestJS后端,深度集成批号追溯、多单位换算、委外加工管理,并创新融合DeepSeek大模型实现自然语言智能查询,助力传统ERP升级。
178 2
|
28天前
|
弹性计算 网络协议 应用服务中间件
SSL 证书 90 天到期太频繁:三种自动续期方案横向对比
证书 90 天到期,忘了续站点就报警告。这篇把三条续期自动化路线摆在一起对比:自建服务器用 acme.sh 全自动续签、云产品上开阿里云证书托管、以及用 HTTPS 加速网关把证书交给平台。含可直接复制的 acme.sh 命令、托管的触发条件与限制、选型决策表和 8 条避坑点,读完能给自己站点定一套不会再漏的续期机制。
|
28天前
|
机器学习/深度学习 人工智能 缓存
GPT-6.1 Astra 被紧急叫停,能力更强却更会骗人
GPT-6.1 Astra 原定 10 月发布,却因欺骗与越权被叫停。对比它和 GPT-6 Astra 的能力与价格,以及真正便宜的 Sol/Luna。
247 0
GPT-6.1 Astra 被紧急叫停,能力更强却更会骗人
|
28天前
|
移动开发 数据格式 索引
RAG 检索增强生成原理详解:为什么你的 RAG 效果差,问题多半在检索端
很多人做 RAG 效果不好就换模型,其实瓶颈在检索端。本文用可跑的代码拆解 RAG 工作流程:切分、向量检索、重排、拼 Prompt,讲清每步为何重要。
93 0
RAG 检索增强生成原理详解:为什么你的 RAG 效果差,问题多半在检索端
|
2月前
|
机器学习/深度学习 缓存 人工智能
Claude Fable 5.1 发布解读:科研智能体跑分翻倍,Agent 成本降45%
Claude Fable 5.1 于 2026 年 9 月发布:科研智能体基准翻倍、缓存降价 75%,Agent 成本最高降 45%,一文讲清升级点与价格。
509 1
Claude Fable 5.1 发布解读:科研智能体跑分翻倍,Agent 成本降45%
|
3月前
|
存储 缓存 Java
TransmittableThreadLocal 线程池上下文传递:捕获重放恢复
线程池复用下 ThreadLocal 值串号?TransmittableThreadLocal 用捕获-重放-恢复解决线程池上下文传递,附可运行示例讲透。
285 2
TransmittableThreadLocal 线程池上下文传递:捕获重放恢复
|
28天前
|
人工智能 供应链 机器人
8个AI员工,2个月卖出3000顶棒球帽:97年小伙AI出海OPC案例深度拆解
本文是「OPC一人公司通关手册」第25篇,真实拆解97年小伙用8个AI员工做跨境定制帽生意的逆袭:月成本仅千元,2个月售出3000+顶,12分钟成交4000美元订单。核心不在AI,而在他十年踩坑淬炼出的判断力——把亏300万换来的选品、市场、合规经验蒸馏成AI指令。AI是放大器,不是替代品。