杰瑞科技汇

Java自定义annotation如何实现与使用?

什么是注解?

在深入自定义之前,我们先理解注解是什么,注解(Annotation)是 Java 5 引入的一种元数据(metadata)机制,它不属于程序逻辑本身,但可以被用来提供关于代码的额外信息。

Java自定义annotation如何实现与使用?-图1
(图片来源网络,侵删)

可以把注解想象成给代码打的“标签”或“注释”,但这些标签可以被编译器、工具或运行时环境读取,并执行相应的操作。

常见的内置注解有:

  • @Override: 告诉编译器这个方法是一个重写方法,如果方法名写错了,编译器会报错。
  • @Deprecated: 标记一个类、方法或字段已过时,不推荐使用。
  • @SuppressWarnings: 告诉编译器忽略某些警告。
  • @FunctionalInterface: 标记一个接口是函数式接口。

自定义注解的语法

自定义注解需要使用 @interface 关键字,一个最简单的自定义注解如下:

// 定义一个名为 MyAnnotation 的注解
public @interface MyAnnotation {
}

元注解

元注解是用于注解其他注解的注解,它们提供了如何使用自定义注解的规则,最常用的元注解有:

Java自定义annotation如何实现与使用?-图2
(图片来源网络,侵删)
  1. @Target: 定义注解可以应用在哪些程序元素上。

    • ElementType.TYPE: 类、接口、枚举
    • ElementType.METHOD: 方法
    • ElementType.FIELD: 字段、属性
    • ElementType.PARAMETER: 参数
    • ElementType.CONSTRUCTOR: 构造函数
    • ElementType.ANNOTATION_TYPE: 注解类型
    • ElementType.PACKAGE: 包
  2. @Retention: 定义注解的生命周期,即注解信息保留到什么时候。

    • RetentionPolicy.SOURCE: 只在源码中保留,编译时被丢弃(如 @Override)。
    • RetentionPolicy.CLASS: 在 .class 文件中保留,但在运行时被 JVM 丢弃,这是默认策略。
    • RetentionPolicy.RUNTIME: 在运行时仍然保留,可以通过反射机制读取。这是我们最常关心的策略,因为只有在运行时可用,才能在程序逻辑中处理注解。
  3. @Documented: 表示该注解会被包含在 JavaDoc 文档中。

  4. @Inherited: 表示如果一个注解被用在某个类上,那么这个类的子类也会自动继承这个注解。

    Java自定义annotation如何实现与使用?-图3
    (图片来源网络,侵删)

带成员的自定义注解

注解可以像接口一样定义成员(抽象方法),成员的返回类型只能是以下几种:

  • 基本数据类型 (byte, char, short, int, long, float, double, boolean)
  • String
  • Class 及其数组
  • enum 及其数组
  • 其他注解及其数组
  • 类型的数组

成员不能有参数,也不能有 throws 子句,默认值通过 default 关键字指定。

示例:定义一个带有成员的注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 1. @Target: 这个注解可以用于方法和字段
@Target({ElementType.METHOD, ElementType.FIELD})
// 2. @Retention: 这个注解在运行时可用
@Retention(RetentionPolicy.RUNTIME)
// 3. 定义注解的成员
public @interface MyAnnotation {
    // 成员,默认值为 "default value"
    String value() default "default value";
    // 成员,没有默认值,使用时必须提供
    int priority();
    // 成员,有默认值
    String[] tags() default {"tag1", "tag2"};
}

如何使用自定义注解?

使用自定义注解非常简单,只需要在需要的地方加上 符号和注解名称即可。

public class MyClass {
    // 使用注解在字段上
    @MyAnnotation(priority = 1, value = "This is a field annotation")
    private String myField;
    // 使用注解在方法上
    @MyAnnotation(priority = 10, value = "This is a method annotation", tags = {"important", "service"})
    public void myMethod() {
        System.out.println("Annotated method is called.");
    }
}

如何处理(解析)自定义注解?

注解本身不会做任何事情,它只是一个“标签”,真正的威力在于如何解析这些标签,解析注解主要通过反射机制。

下面是一个完整的示例,展示如何定义、使用和解析我们上面创建的 @MyAnnotation

步骤 1: 定义注解

// MyAnnotation.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "default value";
    int priority();
    String[] tags() default {"tag1", "tag2"};
}

步骤 2: 在代码中使用注解

// MyClass.java
public class MyClass {
    @MyAnnotation(priority = 1, value = "Annotated Field")
    private String myField;
    @MyAnnotation(priority = 10, value = "Annotated Method", tags = {"core", "api"})
    public void doSomething() {
        System.out.println("Executing doSomething...");
    }
    // 一个没有使用注解的方法,用于对比
    public void doSomethingElse() {
        System.out.println("Executing doSomethingElse...");
    }
}

步骤 3: 编写解析器(通过反射读取注解)

// AnnotationProcessor.java
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class AnnotationProcessor {
    public static void process(String className) {
        try {
            // 1. 获取 Class 对象
            Class<?> clazz = Class.forName(className);
            System.out.println("=== Processing Class: " + clazz.getName() + " ===");
            // 2. 处理类上的注解 (如果有的话)
            // System.out.println("Class annotations: " + Arrays.toString(clazz.getAnnotations()));
            // 3. 处理字段上的注解
            System.out.println("\n--- Processing Fields ---");
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(MyAnnotation.class)) {
                    MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
                    System.out.println("Field: " + field.getName());
                    System.out.println("  - Value: " + annotation.value());
                    System.out.println("  - Priority: " + annotation.priority());
                    System.out.println("  - Tags: " + String.join(", ", annotation.tags()));
                }
            }
            // 4. 处理方法上的注解
            System.out.println("\n--- Processing Methods ---");
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(MyAnnotation.class)) {
                    MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                    System.out.println("Method: " + method.getName());
                    System.out.println("  - Value: " + annotation.value());
                    System.out.println("  - Priority: " + annotation.priority());
                    System.out.println("  - Tags: " + String.join(", ", annotation.tags()));
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        // 解析 MyClass 中的注解
        process("MyClass");
    }
}

运行结果

执行 AnnotationProcessormain 方法,你将看到如下输出:

=== Processing Class: MyClass ===
--- Processing Fields ---
Field: myField
  - Value: Annotated Field
  - Priority: 1
  - Tags: tag1, tag2
--- Processing Methods ---
Method: doSomething
  - Value: Annotated Method
  - Priority: 10
  - Tags: core, api

实际应用场景

自定义注解是许多现代 Java 框架的基石。

  1. 依赖注入:

    • Spring 框架大量使用注解,如 @Autowired@Component@Service@RestController 等。
    • 当 Spring 启动时,它会扫描指定包下的所有类,通过反射读取这些注解,然后根据注解信息创建对象(Bean)并管理它们的生命周期,实现依赖注入。
  2. 框架配置:

    • JUnit 5 的 @Test@BeforeEach@AfterEach 等注解,测试运行器会读取这些注解,在适当的时候执行测试方法和生命周期方法。
  3. Web 开发:

分享:
扫描分享到社交APP
上一篇
下一篇