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);
}

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

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

​

目录
相关文章
|
14天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
8080 15
|
13天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
2111 12
|
12天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
1804 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
11天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)
|
7天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
26天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
3850 10
|
21天前
|
缓存 IDE Java
【保姆级】Android Studio下载、安装和汉化教程(2026最新)
Android Studio 是 Google 官方推出的免费 Android 应用开发集成环境,基于 IntelliJ IDEA,内置模拟器、调试器、性能分析及 Compose 界面工具,功能全面,文档丰富,是安卓开发首选工具。(239字)
2207 1

热门文章

最新文章