💡 一则也许对你有用的小广告 🏆
欢迎飞飞程序员 ,你将获得:专属的实战项目(已更新的所有会员标识的项目都能学习) / 1v1 提问 / Java 学习路线 / PHP 学习路线 / 学习打卡 / 社群讨论
截止目前, 飞飞 正在疯狂爆肝实战项目,后续还会上新更多项目,目标是将所学知识开发成项目并且分享给大家,如知识付费系统, Ai系统, CMS系统,在线商城系统,等等 ,欢迎点击围观
下面是一个完整的、可直接运行的 Spring Boot 示例,实现 自定义注解 + AOP 切面记录 API 请求日志。
一、项目结构
src/main/java/com/example/demo/
├── DemoApplication.java // 启动类
├── annotation/
│ └── ApiLog.java // 自定义注解
├── aspect/
│ └── ApiLogAspect.java // 日志切面
├── util/
│ └── JsonUtil.java // JSON 工具类
└── controller/
└── UserController.java // 测试接口
二、引入依赖
pom.xml:
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
<!-- Lombok(可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
如果不用 Lombok,手动写 Logger 即可。
三、自定义注解 @ApiLog
package com.example.demo.annotation;
import java.lang.annotation.*;
/**
* API 操作日志注解
* 标注在方法上,用于记录接口请求日志
*/
@Target(ElementType.METHOD) // 只能标在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时可被反射读取(AOP 必需)
@Documented
public @interface ApiLog {
/**
* API 功能描述
*/
String value() default "";
}
四、JSON 工具类
package com.example.demo.util;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* JSON 工具类
*/
public class JsonUtil {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private JsonUtil() {}
public static String toJsonString(Object obj) {
if (obj == null) {
return null;
}
try {
return OBJECT_MAPPER.writeValueAsString(obj);
} catch (Exception e) {
return obj.toString();
}
}
}
五、日志切面 ApiLogAspect
package com.example.demo.aspect;
import com.example.demo.annotation.ApiLog;
import com.example.demo.util.JsonUtil;
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;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* API 日志切面
* 拦截所有标注 @ApiLog 的方法,自动打印请求日志
*/
@Aspect
@Component
@Slf4j
public class ApiLogAspect {
/**
* 切点:所有标注 @ApiLog 的方法
*/
@Pointcut("@annotation(com.example.demo.annotation.ApiLog)")
public void apiLogPointcut() {}
/**
* 环绕通知:方法执行前后记录日志
*/
@Around("apiLogPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 开始时间
long startTime = System.currentTimeMillis();
// 2. 获取类名、方法名
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
// 3. 获取入参并转 JSON
Object[] args = joinPoint.getArgs();
String argsJson = Arrays.stream(args)
.map(JsonUtil::toJsonString)
.collect(Collectors.joining(", "));
// 4. 获取注解上的描述
String description = getDescription(joinPoint);
// 5. 打印请求开始日志
log.info("====== 请求开始: [{}], 类: {}, 方法: {}, 入参: {} ======",
description, className, methodName, argsJson);
// 6. 执行真正的业务方法
Object result = joinPoint.proceed();
// 7. 计算耗时并打印结束日志
long cost = System.currentTimeMillis() - startTime;
log.info("====== 请求结束: [{}], 耗时: {}ms, 出参: {} ======",
description, cost, JsonUtil.toJsonString(result));
return result;
}
/**
* 从注解中读取 description
*/
private String getDescription(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
ApiLog apiLog = method.getAnnotation(ApiLog.class);
return apiLog != null ? apiLog.value() : "";
}
}
六、测试接口 UserController
package com.example.demo.controller;
import com.example.demo.annotation.ApiLog;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 加了 @ApiLog 注解,会被切面拦截并记录日志
*/
@ApiLog("用户注册")
@PostMapping("/register")
public Map<String, Object> register(@RequestBody Map<String, Object> user) {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "注册成功");
result.put("data", user);
return result;
}
/**
* 没有加 @ApiLog 注解,不会被切面拦截
*/
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
}
七、启动类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
八、测试验证
1. 请求带 @ApiLog 的接口
请求:
POST http://localhost:8080/user/register
Content-Type: application/json
{
"name": "张三",
"age": 18
}
控制台日志:
====== 请求开始: [用户注册], 类: UserController, 方法: register, 入参: {"name":"张三","age":18} ======
====== 请求结束: [用户注册], 耗时: 5ms, 出参: {"code":200,"msg":"注册成功","data":{"name":"张三","age":18}} ======
2. 请求不带 @ApiLog 的接口
请求:
GET http://localhost:8080/user/hello
控制台日志: 无(因为没有被切面拦截)。
九、核心流程总结
用户请求
↓
Controller 方法(标注 @ApiLog)
↓
AOP 切面拦截(@Around)
↓
① 记录开始时间、类名、方法名、入参
② joinPoint.proceed() 执行业务方法
③ 计算耗时、记录出参
↓
返回结果给前端
十、关键点回顾
| 关键点 | 说明 |
@Target(METHOD) | 注解只能标在方法上 |
@Retention(RUNTIME) | 运行时可被反射读取,AOP 必需 |
@Pointcut("@annotation(...)") | 切点匹配所有标注该注解的方法 |
@Around | 环绕通知,能控制方法执行前后 |
joinPoint.proceed() | 执行真正的业务方法 |
MethodSignature | 用于获取方法上的注解 |
JsonUtil.toJsonString | 把入参/出参转成 JSON 打印 |
十一、扩展建议
| 扩展方向 | 说明 |
| 加 traceId | 用 MDC.put("traceId", UUID) 给每个请求生成唯一标识 |
| 异常日志 | 在 @Around 中 try-catch,捕获异常并记录 log.error |
| 异步日志 | 用 @Async 或消息队列,避免日志影响接口性能 |
| 脱敏处理 | 对密码、手机号等敏感字段做脱敏后再打印 |
| 持久化 | 把日志存到数据库或 ELK,便于查询和分析 |
一句话总结:只要在方法上加 @ApiLog("描述"),AOP 切面就会自动帮你打印请求的入参、出参、耗时和功能描述,无需在业务代码里手写一行日志。