Java-注解详解

Java-注解详解Retention 注解的保留策略

大家好,欢迎来到IT知识分享网。


1️⃣ 注解的含义

注解(Annotation)是Java语言中一种特殊的修饰符,它可以用于类、方法、参数、变量、构造器以及包声明中,用于为Java代码提供元数据。相对于其他修饰符如publicfinal等,注解并不直接影响代码的语义,但却能被某些工具软件(如编译器、框架)所读取和利用。

📝 注解的主要作用

  1. 编译检查 – 如@Override放在方法前,如果该方法不是重写父类的方法,则编译器会发出警告。
  2. 代码分析 – 通过代码里标识的注解,程序可以在编译时进行一些基于注解的处理。注解信息和JAVA的反射功能在一起时会使得程序的功能更加强大。
  3. 编译时动态处理 – 如常见的Java框架Spring、Hibernate、JUnit等,会在编译时读取注解的信息,然后根据注解的信息进行一些其他处理。
  4. 生成额外的文件 – 如Javadoc工具会根据源码中的注解来生成API文档。

📝 注解的基本语法

注解的声明类似于接口的声明,但是前面多了一个@符号:

public @interface MyAnnotation { 
     String value() default ""; //定义value属性 } 

使用注解:

@MyAnnotation(value="This is my custom annotation") public class MyClass { 
     // class body } 

📝 内置注解

Java提供了一些预定义的注解,如:

  • @Override: 限定重写父类方法。
  • @Deprecated: 表示某个程序元素(如方法)已经过时。
  • @SuppressWarnings: 告诉编译器忽略指定的警告。

📝 元注解

用于注解其他注解的注解称为元注解。Java提供了以下几种元注解:

  • @Target: 表明该注解可以被应用于什么地方(如方法、类、字段等)。
  • @Retention: 表明该注解的生命周期(仅源代码、编译期、运行期)。
  • @Documented: 表示使用该注解的元素应被Javadoc或其他工具文档化。
  • @Inherited: 表示该注解可以被子类继承。

注解为Java提供了一种元级编程的方式,它允许开发者在不修改代码逻辑的前提下,向源码中添加一些额外的信息,这些信息可以被编译器或其他工具所读取并使用。


2️⃣ 注解分类

  • 由编译器使用的注解
    • 含义:这类注解不会被编译进入 .class 文件,在编译后它们就被编译器丢弃了。
    • 示例:
      • @Override:此注解让编译器检查该方法是否正确地实现了覆写。
      • @SuppressWarnings:此注解告诉编译器忽略此处代码产生的警告。
  • 由工具处理 .class 文件使用的注解
    • 含义:有些工具在加载 class 的时候,会对 class 文件做动态修改,以实现一些特殊的功能。这类注解会被编译进入 .class 文件,但在加载完成后并不会存在于内存中。这类注解主要被一些底层库使用,一般我们不需要自己处理。
    • 示例:可以参考 lombok
  • 在程序运行期能够读取的注解
    • 含义:这些注解在加载后会一直存在于 JVM 中,是最常用的注解。
    • 示例:配置了 @PostConstruct 的方法会在调用构造方法后自动被调用。这是 Java 代码通过读取该注解实现的功能,JVM 本身并不会识别该注解。

3️⃣ 元注解(java.lang.annotation下的注解:用来定义注解的)

Java 的注解为代码添加了额外的元数据,这些元数据在编译或运行时都可以被访问。为了定义注解的属性和行为,Java 提供了以下一系列的元注解:


📝 @Retention

  • 描述:确定注解的生命周期,即何时可用。
  • 用途:通知开发者注解在何时应被使用或访问。
  • 示例
    @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { 
              } 

📝 @Target

  • 描述:指定注解可以应用的源码元素。
  • 用途:避免注解被误用在不适当的地方,限制注解的适用范围。
  • 示例
    @Target(ElementType.METHOD) public @interface MyMethodAnnotation { 
               } 

📝 @Inherited

  • 描述:允许子类继承父类上的注解。
  • 用途:当需要子类自动继承父类的注解时使用。
  • 示例
    @Inherited public @interface Inheritable { 
                } 

📝 @Documented

  • 描述:注解信息会被包含在 Javadoc 中。
  • 用途:确保开发者在查看 API 文档时,可以看到注解的信息。
  • 示例
    @Documented public @interface DocumentedAnnotation { 
                 } 

📝 @Repeatable (Java 8 新增)

  • 描述:允许同一个注解在同一声明上使用多次。
  • 用途:在 Java 8 之前,一个注解在同一位置只能使用一次,此元注解允许多次使用同一个注解。
  • 示例
    public @interface RepeatedValues { 
                  Value[] value(); } @Repeatable(RepeatedValues.class) public @interface Value { 
                  String info() default ""; } 

4️⃣ JDK 内置注解(都属于由编译器使用的注解)

java.lang 包中,JDK 提供了五个基本注解:


📝 @Override

  • 描述:用于指定子类中的方法重写了父类的方法。
  • 用法
    • 告知编译器我们意图重写父类方法。
    • 如果子类的方法与父类方法签名不匹配,编译器会报错。
  • 适用范围:只能用于方法。

📝 @Deprecated

  • 描述:用于标记已过时的程序元素,如类、方法等。
  • 用法
    • 提醒开发者不建议使用该程序元素。
    • 使用被此注解标记的元素时,编译器会给出警告。

📝 @SuppressWarnings

  • 描述:用于抑制编译器产生警告。
  • 用法:告知编译器忽略特定的警告。
  • 常见参数
    • deprecation:使用已过时的类或方法。
    • unchecked:未经检查的类型转换。
    • fallthroughswitch 语句缺少 break
    • path:类或源文件路径不存在。
    • serial:可序列化类缺少 serialVersionUID
    • finallyfinally 语句未正常完成。
    • all:上述所有情况。

📝 @SafeVarargs

  • 描述:抑制堆污染警告。
  • 特点:JDK 7 新增,用于标记可能产生堆污染的方法或构造函数。

📝 @FunctionalInterface (Java 8 新增)

  • 描述:标记一个接口为函数式接口。
  • 定义:接口中只有一个抽象方法(但可以有多个 defaultstatic 方法)。

📌注意事项

  • value 特权:如果注解的 value 成员变量是唯一需要赋值的,可以省略 name=value 的格式,直接在注解的括号中指定值。
  • 推荐使用 @Override:在意图重写父类方法的每个方法上使用 @Override,帮助编译器及时捕获可能的错误。

5️⃣ 自定义注解

在 Java 中,注解是一种为代码添加元数据的方式。当我们使用 @interface 关键字定义新的注解时,背后的一些细节会由编译器自动完成,例如自动继承 java.lang.annotation.Annotation 接口。

📝 注解的定义规则:

  1. 在定义新注解时,不能继承其他注解或接口。
  2. @interface 关键字用于声明一个新的注解。
  3. 在注解内,每个方法都代表一个配置参数。
    • 方法名为参数名。
    • 返回值类型定义了参数的类型。
    • 方法可以没有参数。
    • 可以使用 default 关键字为参数设定默认值。

📝 注解参数的支持类型:

  • 基本数据类型:如 int, float, boolean, byte, double, char, long, short
  • String
  • Class
  • 枚举类型 (Enum)
  • 其他注解 (Annotation)
  • 以上所有类型的数组形式
package annotation.custom; // 导入必要的注解处理类 import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 表明这个注解是可以被子类继承的 @Inherited // 指明该注解的生命周期,此处指定为RUNTIME,表示注解在运行时仍然存在,并可以通过反射机制读取 @Retention(RetentionPolicy.RUNTIME) // 指明该注解可以被应用于方法和类型(类、接口、枚举、注解) @Target({ 
                   ElementType.METHOD, ElementType.TYPE}) public @interface Tag { 
                    // 定义一个名为name的元素,它的默认值为"undefined" String name() default "undefined"; // 定义一个名为description的元素,使用该注解时,这个元素是必须要设置的,因为它没有默认值 String description(); } 
// 代表所有的注解类型 public interface Annotation { 
                    // 用于比较两个注解是否相等。注意:自定义的注解会有编译器生成的实现,所以使用者不需要手动实现此方法。 boolean equals(Object obj); // 返回该注解的哈希码。同样,这个方法会由编译器自动生成其实现。 int hashCode(); // 返回该注解的字符串表示形式。这也是由编译器自动生成的方法。 String toString(); // 返回表示此注解的注解类型的Class对象。它可以用于在运行时获取注解的信息。 Class<? extends Annotation> annotationType(); } 

注意: Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。 自定义的注解继承了 Annotation 这个接口,因此自定义注解中包含了 Annotation 接口中所有的方法


6️⃣ 注解的提取

Java在java.lang.reflect包内提供了强大的反射机制,其中AnnotatedElement接口扮演了关键角色,代表那些可以携带注解的程序元素。

📝 AnnotatedElement接口

  • 定义:此接口表示可以接受注解的程序元素。
  • 实现类
    • AccessibleObject:是FieldMethodConstructor对象的基类。
    • Executable:是MethodConstructor对象的共有基类。
    • Method:表示类或接口中的某个方法。
    • Constructor:提供某个类的构造方法的信息。
    • Field:表示类或接口中的某个字段。
    • Class:代表应用程序中的类和接口。
    • Package:提供有关Java包的版本信息。
    • Parameter:提供关于方法参数的信息,Java 8 新增。
  • 功能:通过这些类,Java提供了一套丰富的API用于获取运行时的注解信息。

📝 如何使用

只有当一个注解被定义为运行时注解(即使用@Retention(RetentionPolicy.RUNTIME)标注)时,它才会在运行时被JVM读取。这意味着,通过反射,我们可以读取类、方法或字段上的这些注解,并据此执行某些操作。例如,通过反射获得某个类的Method对象后,可以调用该对象的getAnnotations()方法来获取所有的注解。

📝 方法描述

  • isAnnotationPresent(Class<? extends Annotation> annotationType):
    • 描述:此方法用于判断指定的对象是否应用了某个注解。
    • 应用:主要用于方便地访问标记注解。
  • getAnnotations():
    • 描述:返回作用于指定对象的所有注解。
    • 注意:如果没有,则返回长度为0的数组。
  • getDeclaredAnnotations():
    • 描述:返回直接作用于指定对象的所有注解。
    • 注意:如果不存在,则返回长度为0的数组。此方法忽略继承的注解。
  • getAnnotation(Class annotationClass):
    • 描述:返回指定对象上的指定类型的注解。
    • 注意:如果注解不存在,则返回null。
  • getDeclaredAnnotation(Class annotationClass):
    • 描述:返回直接作用于指定对象的指定类型的注解。
    • 注意:如果注解不存在,则返回null。此方法忽略继承的注解。
  • getAnnotationsByType(Class annotationClass):
    • 描述:返回指定对象上的指定类型的所有注解。
    • 注意:如果不存在,则返回长度为0的数组。此方法检测其参数是否为可重复的注解类型。
  • getDeclaredAnnotationsByType(Class annotationClass):
    • 描述:返回直接作用于指定对象的指定类型的所有注解。
    • 注意:如果不存在,则返回长度为0的数组。此方法检测其参数是否为可重复的注解类型,并忽略继承的注解。

7️⃣ 注解处理器


📝 注解处理器简介

  1. 定义:注解处理器是用于在编译时扫描、处理源代码中的注解的工具。它可以分析注解并生成额外的源代码或其他文件。
  2. 历史
    • Java 5:首次引入注解,但处理注解的API不够成熟。需要使用一个独立的工具apt(Annotation Processor Tool)和com.sun.mirror包中的Mirror API来处理注解。
    • Java 6:通过JSR 269,注解处理器被标准化并纳入到Java标准库中。apt工具被集成到javac编译工具中。Java 6提供了一个通用功能的抽象类javax.annotation.processing.AbstractProcessorjavax.lang.model包来支持注解处理。

📝 如何工作

  1. 在编译阶段,javac会扫描每个源文件中的注解。
  2. 对于每个找到的注解,javac会查询是否有已注册的注解处理器可以处理它。
  3. 如果有,javac将调用该处理器来处理注解。处理器可以分析注解、读取注解的值,并根据这些值生成额外的源代码或其他文件。

📝 注解处理器的用途

  1. 自动化代码生成:允许开发者定义规则和模式,然后让机器自动为我们生成代码,避免了人为错误和重复劳动。
  2. 编译时检查:通过注解处理器,我们可以在编译时进行更多的检查,确保代码的质量和准确性。
  3. 性能:由于大部分工作在编译时完成,运行时的性能开销被最小化。
  4. 代码整洁:自动生成的代码可以分离到另外的文件中,保持主要业务逻辑的代码清晰和整洁。

📝 应用场景

  1. 类属性自动赋值
    • 自动注入对象或值,例如Spring框架中的@Autowired@Value
  2. 验证对象属性完整性
    • 例如,使用Java Bean Validation API(如@NotNull, @Size, @Min, @Max等)进行对象属性验证。
  3. 代替配置文件功能
    • 通过注解配置,如Spring的@Component, @Service, @Repository, @Controller等。
  4. 生成文档
    • Javadoc工具使用注解来生成API文档。
  5. 编译时检查
    • 例如,@Override用于告诉编译器子类中的某个方法是否正确地重写了父类中的方法。
  6. 代码生成
    • 编译时注解处理器可以用于生成额外的源代码。
  7. 运行时处理
    • 通过反射API在运行时读取注解信息,如Spring AOP、JUnit等。
  8. APT(Annotation Processing Tool)
    • 处理和提取注解的工具或代码。例如,Lombok库使用注解处理器在编译时生成getter、setter和其他常见方法。

📝 AbstractProcessor

AbstractProcessor 是一个提供便捷实现注解处理器功能的抽象类,实现了 Processor 接口,都位于 javax.annotation.processing 包中。

自定义注解处理器的主要步骤:

  1. 实现 Processor 接口: 通过继承 AbstractProcessor 类来实现自定义的注解处理器。主要的工作在于实现 process 方法,进行你想要完成的注解处理功能。

    init() 方法中,你可以获得:

    • Elements: 一个处理 Element 的工具类。
    • Types: 用来处理 TypeMirror 的工具类。
    • Filer: 用于创建文件。

    在注解处理过程中,会扫描所有的 Java 源文件。每个部分都是一个特定类型的 Element,代表程序的元素,例如包、类或者方法。主要的子类有:

    • PackageElement: 代表一个包。
    • TypeElement: 代表一个类或接口。
    • VariableElement: 表示变量,如成员变量、枚举常量、方法或构造方法参数等。
    • ExecutableElement: 表示类或接口的方法、构造方法。
    • TypeParameterElement: 表示类型参数。
  2. 注册注解处理器: 有两种方法:
    • 手动注册: 在项目的 resources/META-INF/services 目录下新建文件 javax.annotation.processing.Processor,内容是处理器的全称。
    • 自动注册: 使用 Google 提供的库,添加 com.google.auto.service:auto-service 依赖,并在处理器上添加 @AutoService(Processor.class) 注解。这会自动在 META-INF 目录下生成配置信息文件。

注意:在 Java 6 及以上版本中,可以使用 @SupportedAnnotationTypes 注解和 @SupportedSourceVersion 注解来分别替代 getSupportedAnnotationTypes() 方法和 getSupportedSourceVersion() 方法。

📝 自定义注解处理器都需要继承于 AbstractProcessor,如下所示:

/ * 自定义注解处理器 */ public class CustomProcessor extends AbstractProcessor { 
                        private Filer mFiler; private Messager mMessager; private Elements mElementUtils; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { 
                        super.init(processingEnvironment); mFiler = processingEnvironment.getFiler(); mMessager = processingEnvironment.getMessager(); mElementUtils = processingEnvironment.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { 
                        Set<String> annotataions = new LinkedHashSet<String>(); annotataions.add(Tag.class.getCanonicalName()); return annotataions; } @Override public SourceVersion getSupportedSourceVersion() { 
                        return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) { 
                        Set<? extends Element> tagElements = roundEnvironment.getElementsAnnotatedWith(Tag.class); for (Element element : tagElements) { 
                        // 1.获取包名 PackageElement packageElement = mElementUtils.getPackageOf(element); String pkName = packageElement.getQualifiedName().toString(); printMessage(String.format("package = %s", pkName)); // 2.获取包装类类型 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String enclosingName = enclosingElement.getQualifiedName().toString(); printMessage(String.format("enclosindClass = %s", enclosingName)); // 3.获取注解的成员变量名 String tagFiledName = element.getSimpleName().toString(); // 4.获取注解的成员变量类型 String tagFiledClassType = element.asType().toString(); // 5.获取注解元数据 Tag tag = element.getAnnotation(Tag.class); String name = tag.name(); printMessage(String.format("%s %s = %s", tagFiledClassType, tagFiledName, name)); // 6.生成文件 createFile(enclosingElement, tagFiledClassType, tagFiledName, name); return true; } return false; } private void createFile(TypeElement enclosingElement, String tagFiledClassType, String tagFiledName, String name) { 
                        String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString(); try { 
                        JavaFileObject javaFileObject = mFiler.createSourceFile(pkName + ".Tag"); Writer writer = javaFileObject.openWriter(); writer.write(generateCode(pkName, tagFiledClassType, tagFiledName, name)); writer.flush(); writer.close(); } catch (IOException e) { 
                        e.printStackTrace(); } } private void printMessage(String msg) { 
                        mMessager.printMessage(Diagnostic.Kind.NOTE, msg); } private String generateCode(String pkName, String tagFiledClassType, String tagFiledName, String name) { 
                        StringBuilder builder = new StringBuilder(); builder.append("package " + pkName + ";\n\n"); builder.append("//Auto generated by apt,do not modify!!\n\n"); builder.append("public class Tag { \n\n"); builder.append("public static void main(String[] args){ \n"); String info = String.format("%s %s = %s", tagFiledClassType, tagFiledName, name); builder.append("System.out.println(\"" + info + "\");\n"); builder.append("}\n"); builder.append("}"); return builder.toString(); } } 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121609.html

(0)
上一篇 2025-10-22 16:26
下一篇 2025-10-22 16:33

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信