Java基础(十五):注解(Annotation)详解

Java基础系列文章

Java基础(一):初识Java------发展历程、技术体系与JDK环境搭建

Java基础(二):八种基本数据类型详解

Java基础(三):逻辑运算符详解

Java基础(四):位运算符详解

Java基础(五):流程控制全解析------分支(if/switch)和循环(for/while)的深度指南

Java基础(六):数组全面解析

Java基础(七): 面向过程与面向对象、类与对象、成员变量与局部变量、值传递与引用传递、方法重载与方法重写

Java基础(八):封装、继承、多态与关键字this、super详解

Java基础(九):Object核心类深度剖析

Java基础(十):关键字static详解

Java基础(十一):关键字final详解

Java基础(十二):抽象类与接口详解

Java基础(十三):内部类详解

Java基础(十四):枚举类详解

Java基础(十五):注解(Annotation)详解

目录

一、什么是注解(Annotation)

**  注解(Annotation)是Java 5引入的一种元数据机制,可以看作是一种特殊的标记,它提供了一种向代码中添加元信息的方式。这些元信息可以在编译时类加载时运行时被读取和处理,从而影响程序的行为。**

注解本质上是一个接口,它继承自java.lang.annotation.Annotation接口。当我们使用@注解名的形式时,实际上是在创建该注解接口的一个实例。

注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式。

二、元注解

元注解是用于定义其他注解的注解,Java提供了几个重要的元注解来控制自定义注解的行为

1、@Retention(注解保留策略)

  • 用于指定一个 ​注解(Annotation)的保留策略(Retention Policy)​,即该注解在何时有效存在于哪个阶段

1.1、RetentionPolicy.SOURCE(源代码)

  • 含义​:注解仅保留在源代码级别,编译时会被编译器丢弃,​不会包含在生成的字节码文件中

  • 用途​:一般用于编译期检查代码生成等场景

    • @Override@SuppressWarnings仅在编译阶段被编译器用来检查方法重写或抑制警告,不会保留到字节码或影响运行时
    • lombok就是在编译期扫描@Getter等注解,并生成对应方法,注解不会出现在.class文件中
  • 示例:

    java 复制代码
    @Retention(RetentionPolicy.SOURCE)
    public @interface MySourceAnnotation {}

1.2、RetentionPolicy.CLASS(默认Class文件)

  • 含义​:注解会被保留到编译后的字节码文件(.class 文件)中,但在运行时不会被加载到 JVM 中

  • 用途​:一般用于字节码处理工具

    • APT(Annotation Processing Tool)​​:在编译期扫描和处理注解,生成额外的代码(如 Dagger、ButterKnife 在早期版本中使用)
    • 字节码增强/织入工具​:如 ASM、Javassist、AspectJ,在类加载前修改字节码,这些工具可以读取 .class文件中的注解信息,做一些增强操作
  • 注意​:这是默认的保留策略,如果不写 @Retention,则默认这个策略

  • 示例:

    java 复制代码
    @Retention(RetentionPolicy.CLASS)
    public @interface MyClassAnnotation {}

1.3、RetentionPolicy.RUNTIME(常用-运行时)

  • 含义​:注解会保留到运行时,可以通过反射机制读取注解信息

  • 用途​:最常用的策略,通常用于框架(如 Spring、JUnit)、ORM、自定义业务逻辑等场景,实现基于注解的动态行为控制

  • 示例:

    java 复制代码
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRuntimeAnnotation {
        String value() default "";
    }

2、@Target(注解目标范围)

  • 用于指定自定义注解可以应用的目标范围,也就是该注解可以用在哪些程序元素(如类、方法、字段等)上

2.1、ElementType.TYPE(类)

  • 含义:用于类、接口、枚举、注解类型本身(也叫注解的注解)

  • 适用场景:自定义注解用于类级别,比如 ORM 框架中的 @Entity、Spring 中的 @Component

  • 示例:

    java 复制代码
    @Target(ElementType.TYPE)
    public @interface EntityType {
        String value() default "";
    }
    
    // 使用
    @EntityType("User")
    public class User {
    }

2.2、ElementType.FIELD(成员变量)

  • 含义:用于类的字段(成员变量),包括:

    • 实例字段
    • 静态字段
    • 枚举常量(因为枚举常量本质也是字段)
  • 适用场景:用于字段级别的注解,比如序列化标记、JSON 字段映射、数据库列映射等

  • 示例:

    java 复制代码
    @Target(ElementType.FIELD)
    public @interface NotNull {
    }
    
    // 使用
    public class User {
        @NotNull
        private String name;
    }

2.3、ElementType.METHOD(方法)

  • 含义:用于类的方法

  • 适用场景:方法级别的注解,比如权限控制、日志记录、事务管理、API 接口方法等

  • 示例:

    java 复制代码
    @Target(ElementType.METHOD)
    public @interface LogExecution {
    }
    
    // 使用
    public class Service {
        @LogExecution
        public void doSomething() {
        }
    }

2.4、ElementType.PARAMETER(参数)

  • 含义:用于方法的参数

  • 适用场景:参数校验、参数日志、依赖注入等

  • 示例:

    java 复制代码
    @Target(ElementType.PARAMETER)
    public @interface ValidParam {
    }
    
    // 使用
    public class Validator {
        public void check(@ValidParam String input) {
        }
    }

2.5、ElementType.CONSTRUCTOR(构造器)

  • 含义:用于类的构造方法

  • 适用场景:依赖注入框架(如 Spring)中用于构造器注入、构造方法权限等

  • 示例:

    java 复制代码
    @Target(ElementType.CONSTRUCTOR)
    public @interface Injectable {
    }
    
    // 使用
    public class Service {
        @Injectable
        public Service() {
        }
    }

2.6、ElementType.LOCAL_VARIABLE(局部变量)

  • 含义:用于局部变量

  • 适用场景:一般用于代码分析、IDE 提示,但运行时通常不可见(因为局部变量信息在编译后往往被丢弃)

  • 示例:

    java 复制代码
    @Target(ElementType.LOCAL_VARIABLE)
    public @interface DebugValue {
    }

⚠️ 注意:虽然可以标注,但局部变量注解在运行时通常拿不到(受限于 JVM 字节码和类型擦除)

2.7、ElementType.ANNOTATION_TYPE(注解的注解)

  • 含义:专门用于注解类型本身

  • 适用场景:用于"元注解",即注解的注解,比如你定义一个注解,想让它只能用于其他注解上

  • 示例:

    java 复制代码
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface MetaAnnotation {
    }

2.8、ElementType.PACKAGE(包)

  • 含义:用于包(package)

  • 适用场景:包级别的注解,比如包文档、包权限、包配置等

  • 使用方式:需要借助 package-info.java 文件来标注

  • 示例:

    java 复制代码
    // 定义注解
    // File: src/main/java/com/example/annotations/PackageInfo.java
    package com.example.annotations;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.PACKAGE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PackageInfo {
        String author();
        String version();
        String description() default "";
    }
    
    // 使用注解(在 package-info.java中)
    // File: src/main/java/com/example/package-info.java
    @PackageInfo(
        author = "Alice Wang",
        version = "1.2.0",
        description = "该包包含与订单处理相关的所有类,如 OrderService, OrderController 等"
    )
    package com.example;
    
    // 在运行时获取包注解信息
    // File: src/main/java/com/example/annotations/PackageAnnotationReader.java
    public class PackageAnnotationReader {
        public static void main(String[] args) {
            // 获取包对象
            Package pkg = Package.getPackage("com.example");
            // 获取包上的 PackageInfo 注解
            PackageInfo info = pkg.getAnnotation(PackageInfo.class);
            System.out.println("📦 包名: com.example");
            System.out.println("👤 作者: " + info.author());
            System.out.println("🏷️  版本: " + info.version());
            System.out.println("📝 描述: " + info.description());
        }
    }

⚠️注意:该文件中不要定义任何类!

2.9、ElementType.TYPE_PARAMETER(Java8新增-泛型参数)

  • 含义:用于泛型类型参数,也就是在定义泛型类泛型方法泛型接口时,给类型参数(如 <T><K, V>)添加注解

  • 适用场景:用于标注泛型参数,通常结合注解处理器做类型约束或检查

  • 示例:

    java 复制代码
    @Target(ElementType.TYPE_PARAMETER)
    public @interface TypeParam {
    }
    
    // 使用
    public class Box<@TypeParam T> {
    }

2.10、ElementType.TYPE_USE(Java8新增-类型)

  • 含义:用于类型被使用的任何地方

    • 字段类型(如 private String name; 中的 String
    • 方法参数类型
    • 方法返回类型
    • 泛型类型参数实例化(如 List<@TypeUse String>
    • 强制类型转换类型(如 (@TypeUse String) obj
    • 局部变量类型
  • 适用场景:类型安全、非空约束、类型注解、框架校验等高级用法

  • 示例:

    java 复制代码
    @Target(ElementType.TYPE_USE)
    public @interface NonNull {
    }
    
    // 使用
    @NonNull
    String name; // 字段类型上使用
    
    List<@NonNull String> list; // 泛型类型参数使用

⚠️注意:由于Java的类型擦除 + 反射API限制,​类型参数上的注解(包括 TYPE_PARAMETERTYPE_USE)在运行时通常不可见

2.11、ElementType.MODULE(Java9新增-模块)

  • 含义:用于模块(module-info.java)

  • 适用场景:模块系统相关注解,比如模块导出、服务声明等元信息标注

  • 示例:

    java 复制代码
    @Target(ElementType.MODULE)
    public @interface ModuleInfo {
        String name();
    }
    
    // 在 `module-info.java` 中使用
    @ModuleInfo(name = "my.module")
    module my.module {
    }

3、@Documented(文档)

  • 用于指定:​当某个注解被用于一个类/方法/字段等目标上时,该注解应该包含在生成的JavaDoc API文档中
java 复制代码
@Documented
public @interface MyDocumentedAnnotation {
    // ...
}

4、@Inherited(继承)

  • 如果一个类被某个注解标注了,那么它的子类(继承该类)也会"继承"这个注解(可通过反射获取到)
  • @Inherited​只对类(Class)有效,对接口、方法、字段、枚举等无效

示例场景:定义一个自定义注解,并测试是否具有继承性

1.定义一个注解,​加上 @Inherited​

java 复制代码
// 定义一个可继承的注解
@Inherited
@Retention(RetentionPolicy.RUNTIME) // 必须设置为 RUNTIME,否则反射看不到
@Target(ElementType.TYPE) // 只能用于类
public @interface InheritableAnnotation {
    String value() default "这是一个可继承的注解";
}

2.父类使用该注解

java 复制代码
// 父类被 @InheritableAnnotation 标注
@InheritableAnnotation("父类被标注了")
public class ParentClass {
}

3.子类继承父类(但子类本身没有标注该注解)

java 复制代码
// 子类没有显式使用 @InheritableAnnotation
public class ChildClass extends ParentClass {
}

4.测试:通过反射检查子类是否有该注解

java 复制代码
public class InheritanceTest {
    public static void main(String[] args) {
        // 检查父类
        Class<ParentClass> parentClass = ParentClass.class;
        InheritableAnnotation parentAnnotation = parentClass.getAnnotation(InheritableAnnotation.class);
        System.out.println("父类是否有 @InheritableAnnotation: " + (parentAnnotation != null)); // true

        // 检查子类
        Class<ChildClass> childClass = ChildClass.class;
        InheritableAnnotation childAnnotation = childClass.getAnnotation(InheritableAnnotation.class);
        System.out.println("子类是否有 @InheritableAnnotation: " + (childAnnotation != null)); // true
    }
}

5、@Repeatable(Java8新增-多次使用)

  • 表示一个注解可以被多次使用在同一个目标上

场景:我们想定义一个 @Schedule注解,表示任务调度时间,并允许一个类上标注多个调度时间

1.定义一个 ​容器注解@Schedules​(用来存放多个@Schedule注解)

java 复制代码
// 容器注解,用于存放多个 @Schedule
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Schedules {
    Schedule[] value();  // 必须字段名为 value,且类型是 Schedule 数组
}

⚠️ 注意:

  • 这个注解叫 Schedules,它是一个容器,里面可以包含多个@Schedule注解
  • 它的字段必须是value(),且类型是 Schedule[](即目标注解的数组)
  • 这是 Java 的强制约定,容器注解必须有一个名为 value()的数组字段来存放可重复的注解

2.定义目标注解 @Schedule,并加上 @Repeatable

java 复制代码
// 可重复的注解
@Repeatable(Schedules.class)  // 👈 指定它的容器是 Schedules.class
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Schedule {
    String time();  // 表示调度时间,比如 "10:00"
}

⚠️ 注意:
开发中常常12步骤合并,将容器@Schedules的定义放到@Schedule里面

java 复制代码
@Repeatable(Schedule.Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Schedule {
    String time();

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @interface Schedules {
        Schedule[] value();
    }
}

3.使用:​在一个类上多次使用 @Schedule​

java 复制代码
// 现在可以合法地多次使用 @Schedule 注解了!
@Schedule(time = "10:00")
@Schedule(time = "14:00")
@Schedule(time = "18:00")
public class DailyTask {
    public void execute() {
        System.out.println("执行日常任务...");
    }
}

⚠️ 注意:
Java 编译器实际上会把这三个@Schedule注解封装进一个隐藏的 @Schedules容器注解中

java 复制代码
// Java 编译器实际上会将其转换为类似下面的形式(伪代码):
@Schedules({
    @Schedule(time = "10:00"),
    @Schedule(time = "14:00"),
    @Schedule(time = "14:00")
})
public class DailyTask { 
 ...
}

4.通过反射获取多个 @Schedule注解

java 复制代码
public class RepeatableAnnotationDemo {
    public static void main(String[] args) {
        Class<DailyTask> taskClass = DailyTask.class;

        // 方法 1:直接获取所有的 @Schedule 注解(Java 做了适配,可以直接获取)
        Schedule[] schedules = taskClass.getAnnotationsByType(Schedule.class);
        for (Schedule schedule : schedules) {
            System.out.println("调度时间: " + schedule.time());
        }

        // 方法 2:也可以获取容器注解 @Schedules(一般不需要手动处理)
        Schedules container = taskClass.getAnnotation(Schedules.class);
        if (container != null) {
            for (Schedule s : container.value()) {
                System.out.println("[通过容器] 调度时间: " + s.time());
            }
        }
    }
}

五、自定义注解

1. 定义注解

  • 使用@interface关键字来定义注解,这实际上定义了一个特殊的接口

基本语法:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {
    // 注解元素定义
    String value() default "默认值";
    int priority() default 0;
    String[] tags() default {};
}

2、注解元素类型

  • 元素类型只能是基本类型StringClass枚举注解或这些类型的数组
  • 元素可以有默认值,使用default关键字指定,否则必须为改元素提供值
  • 如果注解只有一个元素且名称为value,在使用时可以省略value=前缀

3、使用自定义注解

1.定义好注解后,就可以在代码中使用它

java 复制代码
@MyCustomAnnotation(value = "重要方法", priority = 1, tags = {"业务", "核心"})
public void importantBusinessMethod() {
    // 业务逻辑
}

2.如果注解元素使用了默认值,可以省略这些元素

java 复制代码
@MyCustomAnnotation // 使用所有默认值
public void simpleMethod() {
    // 简单逻辑
}

3.对于只有一个元素的注解,如果该元素名为"value",在使用时可以省略元素名

java 复制代码
public @interface SingleValueAnnotation {
    String value();
}

// 使用时
@SingleValueAnnotation("只需要提供值")
public void method() {}

六、处理注解

1、编译时处理

Java 注解的编译时处理是指在 Java 源代码编译阶段(即.java→ .class的过程中),通过注解处理器对带有特定注解的代码进行分析、验证或生成新的源代码、资源文件等,而无需运行程序

1.1、编译时注解处理的作用

  • 代码生成:如 Lombok通过注解生成 getter/setter/toString()等方法
  • 代码校验:检查代码是否符合某些规范,如注解使用是否正确
  • 模板代码生成:如生成 Builder 模式、DAO 层代码、RPC 接口桩代码等
  • 减少样板代码:自动生成重复性代码,提高开发效率

1.2、编写注解处理器

1.首先定义一个注解,并指定其保留策略为SOURCE或CLASS(编译时处理一般不用 RUNTIME)

java 复制代码
// 该注解只能用于类上
@Target(ElementType.TYPE)
// 仅在源码级别保留,编译后不保留(也可用 CLASS,视需求而定)
@Retention(RetentionPolicy.SOURCE)
public @interface GenerateBuilder {
}

2.注解处理器是核心,你需要继承 javax.annotation.processing.AbstractProcessor,并实现相关方法

java 复制代码
// 声明支持的注解类型
@SupportedAnnotationTypes("com.example.GenerateBuilder") // 替换为你的注解全限定名
// 声明支持的 Java 版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GenerateBuilderProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 遍历所有被 @GenerateBuilder 注解的元素
        for (Element element : roundEnv.getElementsAnnotatedWith(GenerateBuilder.class)) {
            System.out.println("发现 @GenerateBuilder 注解的类: " + element.getSimpleName());

            // 这里可以生成代码,进行校验等操作
            // 通常我们会使用 JavaPoet 等库来生成 .java 源文件
        }
        return true; // 表示已经处理了这些注解,其他处理器无需再处理
    }
}

3.注册方式

  • 在项目的resources/META-INF/services/目录下创建文件
java 复制代码
src/main/resources/META-INF/services/javax.annotation.processing.Processor
  • 文件内容(你的处理器类的全限定名,每行一个)
java 复制代码
com.example.GenerateBuilderProcessor

2、运行时处理

**  通过Java反射API在运行时处理注解,这是最常见的使用方式。**

java 复制代码
public class AnnotationProcessor {
    public static void main(String[] args) throws NoSuchMethodException {
        // 获取类上的注解
        Class<MyAnnotatedClass> clazz = MyAnnotatedClass.class;
        
        // 检查类是否有特定注解
        if (clazz.isAnnotationPresent(MyCustomAnnotation.class)) {
            MyCustomAnnotation classAnnotation = clazz.getAnnotation(MyCustomAnnotation.class);
            System.out.println("Class annotation: " + classAnnotation);
        }
        
        // 获取方法上的注解
        Method method = clazz.getMethod("testMethod");
        if (method.isAnnotationPresent(MyCustomAnnotation.class)) {
            MyCustomAnnotation methodAnnotation = method.getAnnotation(MyCustomAnnotation.class);
            System.out.println("Method annotation name: " + methodAnnotation.name());
            System.out.println("Method annotation priority: " + methodAnnotation.priority());
            System.out.println("Method annotation tags: " + Arrays.toString(methodAnnotation.tags()));
        }
    }
}
相关推荐
汤姆yu2 小时前
基于大数据的全国降水可视化分析预测系统
大数据·开发语言·python
元亓亓亓3 小时前
Leet热题100--208. 实现 Trie (前缀树)--中等
java·开发语言
拿破轮3 小时前
不小心在idea中点了add 到版本控制 怎么样恢复?
java·ide·intellij-idea
星空的资源小屋4 小时前
Text Grab,一款OCR 截图文字识别工具
python·django·ocr·scikit-learn
寒秋丶4 小时前
Milvus:Json字段详解(十)
数据库·人工智能·python·ai·milvus·向量数据库·rag
自由随风飘8 小时前
python 题目练习1~5
开发语言·python
cynicme8 小时前
力扣3318——计算子数组的 x-sum I(偷懒版)
java·算法·leetcode
青云交9 小时前
Java 大视界 -- Java 大数据在智能教育学习效果评估与教学质量改进实战
java·实时分析·生成式 ai·个性化教学·智能教育·学习效果评估·教学质量改进
崎岖Qiu10 小时前
【设计模式笔记17】:单例模式1-模式分析
java·笔记·单例模式·设计模式