Java-初识注解

初识注解

简介

  • Annotation提供了一种为恒旭元素设置元数据的方法。
  • 类似于修饰符,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。
  • Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象。
  • Annotation不影响程序代码的运行。
  • 如果希望Annotation在程序运行时起作用,只有通过某种配套工具对Annotation的信息进行访问和处理。访问和处理Annotation的工具统称为APT。

内置注解

Java定义了一套注解如下。

  • 以下注解在java.lang中:
    • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
    • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
    • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
    • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
    • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • 以下注解在java.lang.annotation中,他们作用在其他注解中,也成为元注解
    • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
    • @Documented - 标记这些注解是否包含在用户文档中。
    • @Target - 标记这个注解应该是哪种 Java 成员。
    • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
      • 详解:假设,我们定义了某个 Annotaion,它的名称是 MyAnnotation,并且 MyAnnotation 被标注为 @Inherited。现在,某个类 Base 使用了MyAnnotation,则 Base “具有了注解 MyAnnotation”;现在,Sub 继承了 Base,由于 MyAnnotation 是 @Inherited的(具有继承性),所以,Sub 也 “具有了注解 MyAnnotation”。
    • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

Annotation架构

Annotation架构

从中可以看出:

  1. 1个Annotation对象,都会有唯一的RetentionPolicy属性。
  2. 1 个 Annotation 对象,可以有若干个 ElementType 属性。
  3. Annotation 有许多实现类,包括:Deprecated, Documented, Inherited, Override 等等。

下面介绍,在java Annotation的组成中,有三个重要的主干类:

//Annotation.java
package java.lang.annotation;
public interface Annotation {

boolean equals(Object obj);

int hashCode();

String toString();

Class<? extends Annotation> annotationType();
}
//ElementType.java
package java.lang.annotation;

public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */

FIELD, /* 字段声明(包括枚举常量) */

METHOD, /* 方法声明 */

PARAMETER, /* 参数声明 */

CONSTRUCTOR, /* 构造方法声明 */

LOCAL_VARIABLE, /* 局部变量声明 */

ANNOTATION_TYPE, /* 注释类型声明 */

PACKAGE /* 包声明 */
}
//RetentionPolicy.java
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */

CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */

RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

说明:

  1. Annotation是一个接口,继承这个接口的就是注解。
  2. ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。
    1. 例如,若一个 Annotation 对象是 METHOD 类型,则该 Annotation 只能用来修饰方法。
  3. RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。
    1. 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如,” @Override” 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,”@Override” 就没有任何作用了。
    2. 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
    3. 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由JVM读入。

注解通用定义

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
//属性列表
//Annotation的成员变量在Annotation定义中以无形参方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。
//注解元素的类型可以为:基本类型,String,Class,枚举类型,注解类型,以及前面所述类型组成的数组
}

说明:上面的作用是定义了一个注解,名为MyAnnotation1。定义了MyAnnotation1之后,我们可以在代码中通过@MyAnnotation1使用它。其他的,@Documented, @Target, @Retention, @interface都是来修饰MyAnnotation1的。

  • @interface:使用 @interface 关键字定义注解时,意味着它实现了java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。

  • @Documented:类和方法的 Annotation 在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented 修饰该 Annotation,则表示它可以出现在 javadoc 中。定义 Annotation 时,@Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc 中。

  • @Target(ElementType.TYPE):ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定 Annotation 的类型属性。

    @Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味着,MyAnnotation1 是来修饰”类、接口(包括注释类型)或枚举声明”的注解。

    定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。

  • @Retention(RetentionPolicy.RUNTIME):前面我们说过,RetentionPolicy 是 Annotation 的策略属性,而 @Retention 的作用,就是指定 Annotation 的策略属性。

    @Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被虚拟机读取。

    定义 Annotation 时,@Retention 可有可无。若没有 @Retention,则默认是 RetentionPolicy.CLASS。

根据注解是否包含成员变量,可以把注解分为

  • 标记注解:没有成员变量的注解,这种注解仅利用自身是否存在来提供信息。
  • 元数据注解:包含成员变量的注解,因为它可以接受更多的元数据,所以也被称为元数据注解。

Annotation作用

编译检查

Annotation 具有”让编译器进行编译检查的作用”。@SuppressWarnings, @Deprecated 和 @Override 都具有编译检查作用。

☆在反射中解析并使用 Annotation

程序通过反射机制可以解析被修饰的方法中的注解数据,当程序获取特殊标记后,可以做出相应的处理。这在Spring等框架中经常使用。详见下面代码。

//AnnotationTest.java
import java.lang.annotation.Annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
import java.lang.reflect.Method;

/**
* Annotation在反射函数中的使用示例
*/
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
/*
* 说明:在这里,我们不能把value()看成一个方法,而是一个同名的变量
* 在使用的时候可以通过@MyAnnotation(value={"a","b"}来赋值
* default 给value制定了默认值
* 如果注解的属性只有一个,且叫value,那么使用该注解时,可以不用指定属性名,因为默认就是给value赋值:
* @MyAnnotation({"a","b"}) //这样也是可以的
*
String[] value() default "unknown";
}

/**
* Person类。它会使用MyAnnotation注解。
*/
class Person {

/**
* empty()方法同时被 "@Deprecated" 和 "@MyAnnotation(value={"a","b"})"所标注
* (01) @Deprecated,意味着empty()方法,不再被建议使用
* (02) @MyAnnotation, 意味着empty() 方法对应的MyAnnotation的value值是默认值"unknown"
*/
@MyAnnotation
@Deprecated
public void empty(){
System.out.println("\nempty");
}

/**
* sombody() 被 @MyAnnotation(value={"girl","boy"}) 所标注,
* @MyAnnotation(value={"girl","boy"}), 意味着MyAnnotation的value值是{"girl","boy"}
*/
@MyAnnotation(value={"girl","boy"})
public void somebody(String name, int age){
System.out.println("\nsomebody: "+name+", "+age);
}
}

public class AnnotationTest {

public static void main(String[] args) throws Exception {

// 新建Person
Person person = new Person();
// 获取Person的Class实例
Class<Person> c = Person.class;
// 获取 somebody() 方法的Method实例
Method mSomebody = c.getMethod("somebody", new Class[]{String.class, int.class});
// 执行该方法
mSomebody.invoke(person, new Object[]{"lily", 18});
iteratorAnnotations(mSomebody);


// 获取 somebody() 方法的Method实例
Method mEmpty = c.getMethod("empty", new Class[]{});
// 执行该方法
mEmpty.invoke(person, new Object[]{});
iteratorAnnotations(mEmpty);
}

public static void iteratorAnnotations(Method method) {

// 判断 somebody() 方法是否包含MyAnnotation注解
if(method.isAnnotationPresent(MyAnnotation.class)){
// 获取该方法的MyAnnotation注解实例
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
// 获取 myAnnotation的值,并打印出来
String[] values = myAnnotation.value();
for (String str:values)
System.out.printf(str+", ");
System.out.println();
}

// 获取方法上的所有注解,并打印出来
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}
}
}

运行结果:

somebody: lily, 18
girl, boy,
@com.skywang.annotation.MyAnnotation(value=[girl, boy])

empty
unknown,
@com.skywang.annotation.MyAnnotation(value=[unknown])
@java.lang.Deprecated()

根据Annotation生成帮助文档

@Documented

帮忙查看代码

通过 @Override, @Deprecated 等,我们能很方便的了解程序的大致结构。

附属文件的自动生成

例如部署描述符或者bean信息类。

测试、日志等代码的自动生成

//TODO

框架中常用的注解

Spring

//TODO

Mybatis

//TODO

小结

  • 注解就像标签,是程序判断执行的依据。比如,程序读到@Test就知道这个方法是待测试方法,而@Before的方法要在测试方法之前执行

  • 注解需要三要素:定义、使用、读取并执行

  • 注解分为自定义注解、JDK内置注解和第三方注解(框架)。自定义注解一般要我们自己定义、使用、并写程序读取,而JDK内置注解和第三方注解我们只要使用,定义和读取都交给它们
  • 大多数情况下,三角关系中我们只负责使用注解,无需定义和执行,框架会将注解类和读取注解的程序隐藏起来,除非阅读源码,否则根本看不到。平时见不到定义和读取的过程,光顾着使用注解,久而久之很多人就忘了注解如何起作用了!

参考资料

  1. 怎样理解 Java 注解和运用注解编程?
  2. 菜鸟教程-Java 注解(Annotation)
  3. 《Java疯狂讲义 第十四章 注解》
文章作者: Met Guo
文章链接: https://guoyujian.github.io/2021/12/23/Java-%E5%88%9D%E8%AF%86%E6%B3%A8%E8%A7%A3/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Gmet's Blog