JAVA自定义注解的步骤示例详解

 更新时间:2025年11月17日 15:30:55   作者:编程小Y  
本文介绍了Java中注解的基本概念、自定义注解的步骤、常用元注解的解析以及自定义注解的常见应用场景,自定义注解可以帮助开发者实现日志记录、权限校验、参数校验等通用功能,提高代码的复用性和可读性,感兴趣的朋友跟随小编一起看看吧

简介

在 Java 中,注解(Annotation) 是一种特殊的元数据(Metadata),可以标记在类、方法、字段、参数等元素上,用于传递额外信息。自定义注解允许开发者根据业务需求定义自己的注解,结合反射、AOP 等技术实现灵活的功能(如日志记录、权限校验、参数校验等)。

一、注解的核心概念

1. 注解的本质

注解本质是一个继承了 java.lang.annotation.Annotation 接口的特殊接口,编译器会自动为其生成实现类。

2. 元注解(Meta-Annotation)

元注解是用于修饰注解的注解,定义了自定义注解的生命周期、作用范围等属性。Java 内置 4 个核心元注解:

元注解作用说明
@Retention指定注解的生命周期(必须声明)
@Target指定注解可作用的元素类型(如类、方法、字段等,必须声明)
@Documented标记注解会被 javadoc 工具提取到文档中(可选)
@Inherited标记注解可被子类继承(仅对类注解有效,可选)
@Repeatable(Java8+)允许注解在同一元素上重复使用(可选)

二、自定义注解的步骤

1. 定义注解(使用@interface关键字)

格式:

// 元注解
元注解1
元注解2
...
public @interface 注解名 {
    // 注解属性(本质是接口的抽象方法)
    类型 属性名() default 默认值; // 可选默认值,无默认值则使用时必须指定
}

关键说明:

  • 注解属性的类型只能是:基本类型(int/String/boolean 等)、枚举、注解、数组(上述类型的数组)。
  • 若属性名是 value,且只有一个属性时,使用注解可省略属性名(直接写值)。
  • 数组类型的属性赋值时,若只有一个元素,可省略 {}

2. 常用元注解详解

(1)@Retention:指定生命周期

取值(RetentionPolicy 枚举):

  • SOURCE:仅保留在源代码中,编译时删除(如 @Override)。
  • CLASS:保留到编译后的 .class 文件中,但 JVM 运行时不加载(默认值)。
  • RUNTIME:保留到 JVM 运行时,可通过反射获取(最常用,如日志、校验注解)。

(2)@Target:指定作用范围

取值(ElementType 枚举):

  • TYPE:作用于类、接口、枚举。
  • METHOD:作用于方法。
  • FIELD:作用于字段(成员变量)。
  • PARAMETER:作用于方法参数。
  • CONSTRUCTOR:作用于构造器。
  • ANNOTATION_TYPE:作用于注解本身。
  • LOCAL_VARIABLE:作用于局部变量。
  • MODULE:作用于模块(Java9+)。

三、自定义注解示例

下面通过 3 个常见场景,演示自定义注解的定义与使用。

示例 1:基础注解(无属性)

定义一个标记型注解(仅用于标记,无额外属性)。

1. 定义注解

import java.lang.annotation.*;
// 生命周期:运行时(可通过反射获取)
@Retention(RetentionPolicy.RUNTIME)
// 作用范围:类、方法
@Target({ElementType.TYPE, ElementType.METHOD})
// 生成 javadoc 文档
@Documented
public @interface LogAnnotation {
    // 无属性(标记型注解)
}

2. 使用注解

// 作用于类
@LogAnnotation
public class UserService {
    // 作用于方法
    @LogAnnotation
    public void addUser(String username) {
        System.out.println("添加用户:" + username);
    }
}

示例 2:带属性的注解(参数校验)

定义一个用于字段非空校验的注解,支持自定义提示信息。

1. 定义注解

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) // 仅作用于字段
@Documented
public @interface NotNull {
    // 注解属性:提示信息,默认值为 "字段不能为空"
    String message() default "字段不能为空";
}

2. 使用注解(标记实体类字段)

public class User {
    @NotNull(message = "用户名不能为空")
    private String username;
    @NotNull(message = "年龄不能为空")
    private Integer age;
    // getter/setter/构造器省略
}

3. 解析注解(通过反射实现校验逻辑)

import java.lang.reflect.Field;
public class ValidationUtil {
    // 校验对象的注解字段
    public static void validate(Object obj) throws IllegalAccessException {
        // 获取对象的类
        Class<?> clazz = obj.getClass();
        // 获取所有字段(包括私有字段)
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 允许访问私有字段
            field.setAccessible(true);
            // 判断字段是否有 @NotNull 注解
            if (field.isAnnotationPresent(NotNull.class)) {
                // 获取注解实例
                NotNull notNull = field.getAnnotation(NotNull.class);
                // 获取字段值
                Object value = field.get(obj);
                // 校验逻辑:值为 null 则抛出异常
                if (value == null) {
                    throw new IllegalArgumentException(notNull.message());
                }
            }
        }
    }
    // 测试
    public static void main(String[] args) throws IllegalAccessException {
        User user1 = new User(null, 20); // 用户名 null
        try {
            validate(user1);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage()); // 输出:用户名不能为空
        }
        User user2 = new User("张三", null); // 年龄 null
        try {
            validate(user2);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage()); // 输出:年龄不能为空
        }
    }
}

示例 3:重复注解(Java8+)

允许同一元素上重复使用注解(如多角色权限校验)。

1. 定义 “容器注解”(用于存放重复的注解)

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Roles {
    // 容器注解必须包含一个返回目标注解数组的属性(名称固定为 value)
    Role[] value();
}

2. 定义可重复注解(使用@Repeatable关联容器注解)

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Roles.class) // 指定容器注解
public @interface Role {
    // 注解属性:角色名称
    String value();
}

3. 使用重复注解

public class PermissionService {
    // 重复使用 @Role 注解(等价于 @Roles({@Role("admin"), @Role("manager")}))
    @Role("admin")
    @Role("manager")
    public void deleteData() {
        System.out.println("删除数据成功");
    }
}

4. 解析重复注解

import java.lang.reflect.Method;
public class PermissionUtil {
    public static void checkRole(Method method, String userRole) {
        // 判断方法是否有 @Roles 注解(重复注解会被包装为容器注解)
        if (method.isAnnotationPresent(Roles.class)) {
            Roles roles = method.getAnnotation(Roles.class);
            Role[] roleArr = roles.value();
            // 校验用户角色是否在允许范围内
            boolean hasPermission = false;
            for (Role role : roleArr) {
                if (role.value().equals(userRole)) {
                    hasPermission = true;
                    break;
                }
            }
            if (!hasPermission) {
                throw new SecurityException("无权限执行该操作");
            }
        }
    }
    // 测试
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = PermissionService.class.getMethod("deleteData");
        // 测试有权限(admin)
        checkRole(method, "admin");
        System.out.println("admin 执行成功");
        // 测试无权限(user)
        try {
            checkRole(method, "user");
        } catch (SecurityException e) {
            System.out.println(e.getMessage()); // 输出:无权限执行该操作
        }
    }
}

四、自定义注解的常见应用场景

  1. 日志记录:标记需要打印日志的方法,通过 AOP 拦截并记录请求参数、返回值、执行时间等。
  2. 权限校验:标记需要权限的接口 / 方法,通过拦截器或 AOP 校验用户角色。
  3. 参数校验:标记实体类字段,校验非空、长度、格式等(如 Spring 的 @NotNull@Size)。
  4. 事务管理:标记需要事务的方法(如 Spring 的 @Transactional)。
  5. 代码生成:通过注解标记类 / 字段,结合代码生成工具(如 MyBatis Generator)生成模板代码。

五、注意事项

  1. 注解本身不包含业务逻辑,需通过反射AOP 解析注解并执行相应逻辑。
  2. @Retention(RetentionPolicy.RUNTIME) 是运行时解析注解的前提,若仅用于编译时检查(如 @Override),可设为 SOURCE
  3. 注解属性的默认值不能是 null,需指定合理的默认值(如空字符串、默认枚举值)。
  4. 重复注解需配合容器注解使用(Java8+ 特性),低版本需手动包装为容器注解。

通过自定义注解,开发者可以将通用逻辑(如日志、校验)与业务逻辑解耦,提高代码的复用性和可读性。实际开发中,Spring、MyBatis 等框架大量使用自定义注解简化配置(如 @Controller@Mapper),核心原理与本文示例一致。

到此这篇关于JAVA自定义注解的文章就介绍到这了,更多相关java自定义注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot2.0实现静态资源版本控制详解

    Spring Boot2.0实现静态资源版本控制详解

    这篇文章主要给大家介绍了关于Spring Boot2.0实现静态资源版本控制的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-11-11
  • redis setIfAbsent和setnx的区别与使用说明

    redis setIfAbsent和setnx的区别与使用说明

    这篇文章主要介绍了redis setIfAbsent和setnx的区别与使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • mybatis-plus更新策略部分字段不更新问题

    mybatis-plus更新策略部分字段不更新问题

    这篇文章主要介绍了mybatis-plus更新策略部分字段不更新问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Java面试重点中的重点之Elasticsearch核心原理

    Java面试重点中的重点之Elasticsearch核心原理

    ElasticSearch是一个基于Lucene的搜索引擎,是用Java语言开发的,能够达到实时搜索,稳定,可靠,快速,安装使用方便,作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎,是最受欢迎的企业搜索引擎
    2022-01-01
  • Spring实战之容器中的工程Bean用法示例

    Spring实战之容器中的工程Bean用法示例

    这篇文章主要介绍了Spring实战之容器中的工程Bean用法,结合实例形式分析了Sring框架容器中的工程Bean相关配置、使用操作技巧,需要的朋友可以参考下
    2019-11-11
  • java巧用@Convert实现表字段自动转entity

    java巧用@Convert实现表字段自动转entity

    本文主要介绍了java巧用@Convert实现表字段自动转entity,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • Java中默认的访问权限作用域解析

    Java中默认的访问权限作用域解析

    这篇文章主要介绍了Java中默认的访问权限作用域,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • mybatis中的limit参数解读

    mybatis中的limit参数解读

    这篇文章主要介绍了mybatis中的limit参数,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Java中基于注解的代码生成工具MapStruct映射使用详解

    Java中基于注解的代码生成工具MapStruct映射使用详解

    MapStruct 作为一个基于注解的代码生成工具,为我们提供了一种更加优雅、高效的解决方案,本文主要为大家介绍了它的具体使用,感兴趣的可以了解下
    2025-02-02
  • 引入QQ邮箱发送验证码进行安全校验功能实现

    引入QQ邮箱发送验证码进行安全校验功能实现

    最近遇到这样的需求用户输入自己的邮箱,点击获取验证码,后台会发送一封邮件到对应邮箱中,怎么实现呢?下面小编给大家带来了引入QQ邮箱发送验证码进行安全校验功能,需要的朋友可以参考下
    2023-02-02

最新评论