在 SpringBoot 中,自定义注解 是非常实用的技术,常用于:日志记录、权限校验、接口限流、参数校验、事务增强、统一返回处理等。
核心原理:注解 + AOP(切面) 实现(Spring AOP 基于动态代理)。
下面带你从零实现一个 自定义日志注解(最通用、最易理解的案例)。
一、开发步骤
1. 引入依赖
需要 spring-boot-starter-aop 支持注解和切面:
xml
<!-- Spring AOP 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- web依赖(用于测试接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 自定义注解类
创建注解 @LogRecord,用于标记需要记录日志的方法:
java
import java.lang.annotation.*;
/**
* 自定义日志注解
* Target: 注解作用目标(METHOD=方法)
* Retention: 注解生命周期(RUNTIME=运行时有效)
*/
@Target(ElementType.METHOD) // 只作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Documented // 生成文档
public @interface LogRecord {
// 注解属性:操作描述(默认空)
String operateDesc() default "";
}
3. 编写 AOP 切面(注解核心逻辑)
这是真正实现注解功能 的地方,拦截加了 @LogRecord 的方法:
java
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
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 // 交给 Spring 管理
@Slf4j
public class LogRecordAspect {
/**
* 切点:匹配所有添加了 @LogRecord 注解的方法
*/
@Pointcut("@annotation(com.example.demo.annotation.LogRecord)")
public void logPointCut() {}
/**
* 环绕通知:方法执行前后都拦截
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行目标方法
Object result = point.proceed();
// 执行耗时
long time = System.currentTimeMillis() - beginTime;
// 保存日志
recordLog(point, time);
return result;
}
/**
* 记录日志逻辑
*/
private void recordLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取自定义注解
LogRecord logAnnotation = method.getAnnotation(LogRecord.class);
log.info("=====================日志开始====================");
// 获取注解上的描述
log.info("操作描述:{}", logAnnotation.operateDesc());
// 获取方法名
log.info("执行方法:{}", signature.getDeclaringTypeName() + "." + signature.getName());
// 执行耗时
log.info("执行耗时:{}ms", time);
log.info("=====================日志结束====================");
}
}
4. 使用自定义注解
直接在 Controller/Service 方法 上添加 @LogRecord:
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
/**
* 使用自定义日志注解
*/
@LogRecord(operateDesc = "测试自定义注解接口")
@GetMapping("/annotation")
public String testAnnotation() {
return "自定义注解生效啦!";
}
}
二、测试效果
启动项目,访问接口:
http://localhost:8080/test/annotation
控制台输出:
=====================日志开始====================
操作描述:测试自定义注解接口
执行方法:com.example.demo.controller.TestController.testAnnotation
执行耗时:3ms
=====================日志结束====================
✅ 自定义注解实现完成!
三、进阶:自定义注解常用场景扩展
1. 权限校验注解
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value(); // 权限码
}
切面中:获取当前登录用户权限 → 对比注解权限 → 无权限抛出异常。
2. 接口限流注解
切面中:使用 Redis + Lua 实现限流逻辑。
3. 参数非空校验注解
作用在参数/字段上,切面自动校验参数是否为空。
四、核心知识点总结
-
注解三要素
@Target:定义注解用在哪里(方法/类/参数)@Retention:定义注解生命周期(必须用RUNTIME)@interface:声明自定义注解
-
AOP 五大通知
@Around:环绕通知(最常用,前后都拦截)@Before:方法执行前@After:方法执行后@AfterReturning:方法返回后@AfterThrowing:方法异常后
-
核心原理
Spring AOP 通过动态代理,拦截带有自定义注解的方法,执行增强逻辑。
总结
- 实现自定义注解 = 定义注解 + AOP切面 + 使用注解
- 必须引入
spring-boot-starter-aop依赖 - 注解本身无逻辑,逻辑全在 AOP 切面中
- 可用于日志、权限、限流、校验等场景