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 {
    // 包含任一角色
}

相关推荐
小陈工2 小时前
Python Web开发入门(十二):使用Flask-RESTful构建API——让后端开发更优雅
开发语言·前端·python·安全·oracle·flask·restful
艾莉丝努力练剑2 小时前
【Linux系统:信号】线程安全不等于可重入:深度拆解变量作用域与原子操作
java·linux·运维·服务器·开发语言·c++·学习
笑鸿的学习笔记2 小时前
Qt与CMake笔记之option、宏传递与Qt Creator项目设置
开发语言·笔记·qt
楼田莉子2 小时前
同步/异步日志系统:日志的工程意义及其实现思想
linux·服务器·开发语言·数据结构·c++
无心水2 小时前
20、Spring陷阱:Feign AOP切面为何失效?配置优先级如何“劫持”你的设置?
java·开发语言·后端·python·spring·java.time·java时间处理
QfC92C02p2 小时前
C# 中的 Span 和内存:.NET 中的高性能内存处理
java·c#·.net
0xDevNull2 小时前
Java 21 新特性概览与实战教程
java·开发语言·后端
We་ct2 小时前
JS手撕:性能优化、渲染技巧与定时器实现
开发语言·前端·javascript·面试·性能优化·定时器·性能
柏林以东_2 小时前
java遍历的所有方法及优缺点
java·开发语言·数据结构