Java自定义注解创建详解

一、什么是自定义注解

注解(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)运行结果演示

  1. 访问(有权限)

    GET /order/query/1

返回:

复制代码
查询订单 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 {
    // 包含任一角色
}

相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔1 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区5 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai6 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说6 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json