一、什么是自定义注解
注解(Annotation) 是一种元数据,用来为代码提供额外信息,但本身不改变代码逻辑。
Java 内置注解如:
-
@Override -
@Deprecated -
@SuppressWarnings
而 自定义注解 就是开发者自己定义的注解类型,用于:
-
编译期检查
-
框架配置(如 Spring、MyBatis)
-
运行时反射处理
-
自动生成代码
二、定义一个注解的基本语法
java
public @interface 注解名 {
类型 元素名() default 默认值;
}
注解本质上是一个 接口 , 注解的方法称为 元素(Element)
三、元注解(必须掌握)
元注解:用来修饰注解的注解。
Java 提供了 5 个标准元注解:
| 元注解 | 作用 |
|---|---|
@Target |
注解可以用在什么地方 |
@Retention |
注解保留到哪个阶段 |
@Documented |
是否生成到 JavaDoc |
@Inherited |
是否允许子类继承 |
@Repeatable |
是否可重复注解 |
1. @Target(常用)
java
@Target(ElementType.METHOD)
常见取值:
-
TYPE (类/接口) -
METHOD (方法) -
FIELD -
PARAMETER -
CONSTRUCTOR -
ANNOTATION_TYPE
2. @Retention(非常重要)
java
@Retention(RetentionPolicy.RUNTIME)
| RetentionPolicy | 说明 |
|---|---|
| SOURCE | 只在源码中,编译后丢弃 |
| CLASS | 编译进 class,但运行时不可见 |
| RUNTIME | 运行时可通过反射读取 ✅ |
如果你想在运行期解析注解,必须用 RUNTIME
四、自定义一个简单注解(示例)
示例 :方法级别的日志注解
1. 定义注解
java
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String value() default "";
boolean enable() default true;
}
说明:
-
value():注解的默认属性 -
default:默认值 -
支持多个参数
2. 使用注解
java
public class UserService {
@Log(value = "查询用户", enable = true)
public void queryUser() {
System.out.println("查询用户逻辑");
}
@Log("删除用户")
public void deleteUser() {
System.out.println("删除用户逻辑");
}
}
如果只有一个参数且叫
value,可以省略value=
3. 运行时解析注解(反射)
java
import java.lang.reflect.Method;
public class AnnotationTest {
public static void main(String[] args) throws Exception {
Class<?> clazz = UserService.class;
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Log.class)) {
Log log = method.getAnnotation(Log.class);
System.out.println("方法名:" + method.getName());
System.out.println("日志描述:" + log.value());
System.out.println("是否启用:" + log.enable());
}
}
}
}
输出示例:
java
方法名:queryUser
日志描述:查询用户
是否启用:true
五、自定义注解 + AOP + 权限校验
(1)项目结构(推荐)
com.example.demo
├── DemoApplication.java
├── annotation
│ └── RequiresRole.java
├── aop
│ └── RoleAspect.java
├── service
│ └── OrderService.java
└── controller
└── OrderController.java
(2)引入依赖(Maven)
XML
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
(3)自定义注解(核心)
RequiresRole.java
java
package com.example.demo.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresRole {
/**
* 需要的角色
*/
String value();
}
关键点:
-
RUNTIME:AOP 才能反射拿到 -
用在 方法 上
(4)AOP 切面(重点)
RoleAspect.java
java
package com.example.demo.aop;
import com.example.demo.annotation.RequiresRole;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RoleAspect {
// 当前登录用户(模拟)
private static final String CURRENT_USER_ROLE = "USER";
@Around("@annotation(com.example.demo.annotation.RequiresRole)")
public Object checkRole(ProceedingJoinPoint pjp) throws Throwable {
// 1. 获取方法签名
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 2. 获取方法上的注解
RequiresRole requiresRole =
signature.getMethod().getAnnotation(RequiresRole.class);
// 3. 权限校验
if (!CURRENT_USER_ROLE.equals(requiresRole.value())) {
throw new RuntimeException("权限不足,需要角色:" + requiresRole.value());
}
// 4. 放行
return pjp.proceed();
}
}
这里做了什么?
-
拦截所有带
@RequiresRole的方法 -
从注解中读取值
-
和当前用户角色比较
-
不通过直接抛异常
(5)业务层使用注解
OrderService.java
java
package com.example.demo.service;
import com.example.demo.annotation.RequiresRole;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@RequiresRole("ADMIN")
public String deleteOrder(Long orderId) {
return "订单 " + orderId + " 已删除";
}
@RequiresRole("USER")
public String queryOrder(Long orderId) {
return "查询订单 " + orderId;
}
}
(6)Controller 调用
OrderController.java
java
package com.example.demo.controller;
import com.example.demo.service.OrderService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/delete/{id}")
public String delete(@PathVariable Long id) {
return orderService.deleteOrder(id);
}
@GetMapping("/query/{id}")
public String query(@PathVariable Long id) {
return orderService.queryOrder(id);
}
}
(7)启动类
DemoApplication.java
java
@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@EnableAspectJAutoProxy必须加
(8)运行结果演示
-
访问(有权限)
GET /order/query/1
返回:
查询订单 1
-
访问(无权限)
GET /order/delete/1
控制台 / 返回:
RuntimeException: 权限不足,需要角色:ADMIN
六、注解支持数组类型
(1) 改造注解(支持数组)
RequiresRoles.java
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresRoles {
/**
* 允许访问的角色列表
*/
String[] value();
}
关键变化:
String[] value();
(2) 改造 AOP 切面
RoleAspect.java
java
@Aspect
@Component
public class RoleAspect {
// 模拟当前登录用户角色
private static final String CURRENT_USER_ROLE = "USER";
@Around("@annotation(com.example.demo.annotation.RequiresRoles)")
public Object checkRole(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
RequiresRoles requiresRoles =
signature.getMethod().getAnnotation(RequiresRoles.class);
// 判断是否包含当前用户角色
boolean hasPermission = Arrays.asList(requiresRoles.value())
.contains(CURRENT_USER_ROLE);
if (!hasPermission) {
throw new RuntimeException(
"权限不足,所需角色:" + Arrays.toString(requiresRoles.value())
);
}
return pjp.proceed();
}
}
核心逻辑:
java
Arrays.asList(roles).contains(currentRole)
(3) 业务方法中使用
📌 OrderService.java
java
@Service
public class OrderService {
@RequiresRoles({"ADMIN", "OPS"})
public String deleteOrder(Long orderId) {
return "订单 " + orderId + " 已删除";
}
@RequiresRoles("USER")
public String queryOrder(Long orderId) {
return "查询订单 " + orderId;
}
}
(4) 测试结果
| 当前角色 | 方法 | 结果 |
|---|---|---|
| USER | deleteOrder | 权限不足 |
| ADMIN | deleteOrder | 成功 |
| OPS | deleteOrder | 成功 |
| USER | queryOrder | 成功 |
七、升级版:支持 AND / OR 逻辑(进阶)
(1)注解定义
java
public @interface RequiresRoles {
String[] value();
Logical logical() default Logical.OR;
}
java
public enum Logical {
AND, OR
}
(2)AOP 判断
java
Logical logical = requiresRoles.logical();
List<String> requiredRoles = Arrays.asList(requiresRoles.value());
if (logical == Logical.AND) {
// 包含所有角色
} else {
// 包含任一角色
}