当前位置: 首页 > news >正文

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)
  • 二、元注解
    • 1、@Retention(注解保留策略)
      • 1.1、RetentionPolicy.SOURCE(源代码)
      • 1.2、RetentionPolicy.CLASS(默认Class文件)
      • 1.3、RetentionPolicy.RUNTIME(常用-运行时)
    • 2、@Target(注解目标范围)
      • 2.1、ElementType.TYPE(类)
      • 2.2、ElementType.FIELD(成员变量)
      • 2.3、ElementType.METHOD(方法)
      • 2.4、ElementType.PARAMETER(参数)
      • 2.5、ElementType.CONSTRUCTOR(构造器)
      • 2.6、ElementType.LOCAL_VARIABLE(局部变量)
      • 2.7、ElementType.ANNOTATION_TYPE(注解的注解)
      • 2.8、ElementType.PACKAGE(包)
      • 2.9、ElementType.TYPE_PARAMETER(Java8新增-泛型参数)
      • 2.10、ElementType.TYPE_USE(Java8新增-类型)
      • 2.11、ElementType.MODULE(Java9新增-模块)
    • 3、@Documented(文档)
    • 4、@Inherited(继承)
    • 5、@Repeatable(Java8新增-多次使用)
  • 五、自定义注解
    • 1. 定义注解
    • 2、注解元素类型
    • 3、使用自定义注解
  • 六、处理注解
    • 1、编译时处理
      • 1.1、编译时注解处理的作用
      • 1.2、编写注解处理器
    • 2、运行时处理

一、什么是注解(Annotation)

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

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

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

二、元注解

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

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

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

1.1、RetentionPolicy.SOURCE(源代码)

  • 含义​:注解仅保留在源代码级别,编译时会被编译器丢弃,​不会包含在生成的字节码文件中
  • 用途​:一般用于编译期检查代码生成等场景
    • @Override@SuppressWarnings仅在编译阶段被编译器用来检查方法重写或抑制警告,不会保留到字节码或影响运行时
    • lombok就是在编译期扫描@Getter等注解,并生成对应方法,注解不会出现在.class文件中
  • 示例:
    @Retention(RetentionPolicy.SOURCE)
    public @interface MySourceAnnotation {}
    

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

  • 含义​:注解会被保留到编译后的字节码文件(.class 文件)中,但在运行时不会被加载到 JVM 中
  • 用途​:一般用于字节码处理工具
    • APT(Annotation Processing Tool)​​:在编译期扫描和处理注解,生成额外的代码(如 Dagger、ButterKnife 在早期版本中使用)
    • 字节码增强/织入工具​:如 ASM、Javassist、AspectJ,在类加载前修改字节码,这些工具可以读取 .class文件中的注解信息,做一些增强操作
  • 注意​:这是默认的保留策略,如果不写 @Retention,则默认这个策略
  • 示例:
    @Retention(RetentionPolicy.CLASS)
    public @interface MyClassAnnotation {}
    

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

  • 含义​:注解会保留到运行时,可以通过反射机制读取注解信息
  • 用途​:最常用的策略,通常用于框架(如 Spring、JUnit)、ORM、自定义业务逻辑等场景,实现基于注解的动态行为控制
  • 示例:
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRuntimeAnnotation {String value() default "";
    }
    

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

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

2.1、ElementType.TYPE(类)

  • 含义:用于类、接口、枚举、注解类型本身(也叫注解的注解)
  • 适用场景:自定义注解用于类级别,比如 ORM 框架中的 @Entity、Spring 中的 @Component
  • 示例:
    @Target(ElementType.TYPE)
    public @interface EntityType {String value() default "";
    }// 使用
    @EntityType("User")
    public class User {
    }
    

2.2、ElementType.FIELD(成员变量)

  • 含义:用于类的字段(成员变量),包括:
    • 实例字段
    • 静态字段
    • 枚举常量(因为枚举常量本质也是字段)
  • 适用场景:用于字段级别的注解,比如序列化标记、JSON 字段映射、数据库列映射等
  • 示例:
    @Target(ElementType.FIELD)
    public @interface NotNull {
    }// 使用
    public class User {@NotNullprivate String name;
    }
    

2.3、ElementType.METHOD(方法)

  • 含义:用于类的方法
  • 适用场景:方法级别的注解,比如权限控制、日志记录、事务管理、API 接口方法等
  • 示例:
    @Target(ElementType.METHOD)
    public @interface LogExecution {
    }// 使用
    public class Service {@LogExecutionpublic void doSomething() {}
    }
    

2.4、ElementType.PARAMETER(参数)

  • 含义:用于方法的参数
  • 适用场景:参数校验、参数日志、依赖注入等
  • 示例:
    @Target(ElementType.PARAMETER)
    public @interface ValidParam {
    }// 使用
    public class Validator {public void check(@ValidParam String input) {}
    }
    

2.5、ElementType.CONSTRUCTOR(构造器)

  • 含义:用于类的构造方法
  • 适用场景:依赖注入框架(如 Spring)中用于构造器注入、构造方法权限等
  • 示例:
    @Target(ElementType.CONSTRUCTOR)
    public @interface Injectable {
    }// 使用
    public class Service {@Injectablepublic Service() {}
    }
    

2.6、ElementType.LOCAL_VARIABLE(局部变量)

  • 含义:用于局部变量
  • 适用场景:一般用于代码分析、IDE 提示,但运行时通常不可见(因为局部变量信息在编译后往往被丢弃)
  • 示例:
    @Target(ElementType.LOCAL_VARIABLE)
    public @interface DebugValue {
    }
    

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

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

  • 含义:专门用于注解类型本身
  • 适用场景:用于“元注解”,即注解的注解,比如你定义一个注解,想让它只能用于其他注解上
  • 示例:
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface MetaAnnotation {
    }
    

2.8、ElementType.PACKAGE(包)

  • 含义:用于包(package)
  • 适用场景:包级别的注解,比如包文档、包权限、包配置等
  • 使用方式:需要借助 package-info.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>)添加注解
  • 适用场景:用于标注泛型参数,通常结合注解处理器做类型约束或检查
  • 示例:
    @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
    • 局部变量类型
  • 适用场景:类型安全、非空约束、类型注解、框架校验等高级用法
  • 示例:
    @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)
  • 适用场景:模块系统相关注解,比如模块导出、服务声明等元信息标注
  • 示例:
    @Target(ElementType.MODULE)
    public @interface ModuleInfo {String name();
    }// 在 `module-info.java` 中使用
    @ModuleInfo(name = "my.module")
    module my.module {
    }
    

3、@Documented(文档)

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

4、@Inherited(继承)

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

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

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

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

2.父类使用该注解

// 父类被 @InheritableAnnotation 标注
@InheritableAnnotation("父类被标注了")
public class ParentClass {
}

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

// 子类没有显式使用 @InheritableAnnotation
public class ChildClass extends ParentClass {
}

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

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注解)

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

⚠️ 注意:

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

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

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

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

@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​

// 现在可以合法地多次使用 @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 编译器实际上会将其转换为类似下面的形式(伪代码):
@Schedules({@Schedule(time = "10:00"),@Schedule(time = "14:00")@Schedule(time = "14:00")
})
public class DailyTask { ...
}

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

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关键字来定义注解,这实际上定义了一个特殊的接口

基本语法:

@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.定义好注解后,就可以在代码中使用它

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

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

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

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

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)

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

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

// 声明支持的注解类型
@SupportedAnnotationTypes("com.example.GenerateBuilder") // 替换为你的注解全限定名
// 声明支持的 Java 版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GenerateBuilderProcessor extends AbstractProcessor {@Overridepublic 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/目录下创建文件
src/main/resources/META-INF/services/javax.annotation.processing.Processor
  • 文件内容(你的处理器类的全限定名,每行一个)
com.example.GenerateBuilderProcessor

2、运行时处理

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

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()));}}
}
http://www.dtcms.com/a/573017.html

相关文章:

  • 离散制造与流程制造 MES 应用核心差异对比表
  • 实战代码解析:京东获得 JD 商品详情 API (item_get_pro) 返回值说明
  • Agent 设计与上下文工程- 02 Workflow 设计模式(上)
  • UE安卓环境搭建
  • 【代码随想录算法训练营——Day59】图论——47.参加科学大会、94.城市间货物运输I
  • 做网站推广前途某互联网公司开发官网的首页
  • 网站未收录wordpress设置假阅读量
  • 将大规模shp白模贴图转3dtiles倾斜摄影,并可单体化拾取建筑
  • CMP7(类Cloudera CMP 7 404版华为Kunpeng)用开源软件Label Studio做数据标注
  • Go、DevOps运维开发实战(视频教程)
  • 25.Spring Boot 启动流程深度解析:从run()到自动配置
  • Spring Boot 实现多语言国际化拦截器
  • 神经网络—— 人工神经网络导论
  • 实时云渲染平台 LarkXR:2D/3D 应用云推流的高效解决方案
  • 厦门市建设局网站摇号如何自己搭建一个企业网站
  • 郑州市科协网站游戏推广员到底犯不犯法
  • create_map外部函数
  • 中跃建设集团网站湖北省建设厅官方网站
  • 【028】乐器租赁管理系统
  • 散列文件的使用与分析
  • JavaEE初阶——多线程(6)定时器和线程池
  • 【Go语言爬虫】为什么要用Go语言写爬虫?
  • 网络安全培训
  • DNN 预测手术机器人姿态并做补偿包工程样本(2025.09)
  • 13. Qt 绘图-Graphics View
  • php构建网站如何开始展览设计
  • 金仓KingbaseES数据库:迁移、运维与成本优化的全面解析
  • AI推理硬件选型指南:CPU 与 GPU 的抉择
  • 手刃一个爬虫小案例
  • DMFNet代码讲解