注解

1. 什么是注解?

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

2. 注解的由来

以前,XML是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,XML的内容也越来越复杂,维护成本变高。

于是就有人提出来一种标记式高耦合的配置方式,注解。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解。

关于注解XML两种不同的配置模式,争论了好多年了,各有各的优劣,注解可以提供更大的便捷性,易于维护修改,但耦合度高,而 XML 相对于注解则是相反的。

3. JDK内置的注解

Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

作用在代码的注解是

  • @Override : 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated : 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings :指示编译器去忽略注解中声明的警告。

作用在其他注解的注解(或者说 元注解)是:

  • @Retention :标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented :标记这些注解是否包含在用户文档中。
  • @Target : 标记这个注解应该是哪种 Java 成员。
  • @Inherited : 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs :Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface :Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable :Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

4. 元注解解析

元注解,我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。

4.1 @Retention

Retention英文意思为保留,它定义了注解被保留的时间长短,分为编译期、类加载时期和运行时期。

@Retention注解中使用枚举RetentionPolicy来表示注解保留时期:

  • @Retention(RetentionPolicy.SOURCE):注解仅存在于源码中(即源文件保留)
  • @Retention(RetentionPolicy.CLASS):默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得(即class保留)
  • @Retention(RetentionPolicy.RUNTIME): 注解会在class字节码文件中存在,在运行时可以通过反射获取到(即运行时保留)

以SpringBoot启动类的@SpringBootApplication注解为例:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SpringBootApplication {
    //...
}

@SpringBootApplication注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。

4.2 @Target

Target的英文意思为目标,它定义了注解作用的范围,可以作用在类、方法、变量、枚举等等。

@Target通过枚举类ElementType表达作用的范围:

  • @Target(ElementType.TYPE) :作用在接口、类、枚举、注解
  • @Target(ElementType.FIELD) :作用在属性字段、枚举的常量
  • @Target(ElementType.METHOD): 作用在方法
  • @Target(ElementType.PARAMETER) :作用在方法参数
  • @Target(ElementType.CONSTRUCTOR) :作用在构造函数
  • @Target(ElementType.LOCAL_VARIABLE):作用在局部变量
  • @Target(ElementType.ANNOTATION_TYPE):作用于注解(@Retention注解中就使用该属性)
  • @Target(ElementType.PACKAGE) :作用于包
  • @Target(ElementType.TYPE_PARAMETER) :作用于泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) :类型使用.可以用于标注任意类型除了 Class (jdk1.8加入)

我们一般比较常用的是ElementType.TYPE类型。

4.3 @Documented

Documented顾名思义,它的作用是能够将注解中的元素包含到 Javadoc (API文档)中去。

4.4 @Inherited

Inherited的英文意思是继承,它表示,当一个父类被@Inherited注解修饰,那么它的子类可以继承父类的注解。

4.5 Repeatable

Repeatable的英文意思是可重复的,它说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。

5. 自定义注解并使用

基于前面的学习,我们对注解有了一个基本的认识:它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。,然后程序在编译时或运行时可以检测到这些注解从而执行一些特殊操作。

因此可以得出自定义注解使用的基本流程:

  • 第一步,定义注解——相当于定义标记;
  • 第二步,配置注解——把标记打在需要用到的程序代码中;
  • 第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。

5.1 定义注解

注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同:@interface在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口

public @interface MyAnnotation {
}

根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)

什么是注解类型元素呢?Java做了如下定义:

  • 所有基本类型(int,float,boolean,byte,double,char,long,short)
  • String
  • Class
  • enum
  • Annotation
  • 上述类型的一维数组

倘若使用了其他数据类型,编译器将会抛出一个编译错误。

我们来实现一下声明好的@MyAnnotation注解:

public @interface MyAnnotation {
    public String name();
	int age() default 20;
	int[] array();
}

注意:

  1. 访问修饰符必须为public,不写默认为public;
  2. 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
  3. ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
  4. default代表默认值,值必须和第2点定义的类型一致;
  5. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。

根据前面对元注解的学习,我们给@MyAnnotation做一下修饰:

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
public @interface MyAnnotation {
    public String name();
	int age() default 20;
	int[] array();
}

上面元注解的含义为:@MyAnnotation能保留到运行期、只能作用在方法上。

5.2 配置注解

定义好注解之后,我们就可以在类上配置了,新增一个测试类:

public class Person{
    public void show(Integer times){
        for(int i = 0; i < times; i++){
            System.out.println("person show!");
        }    
    }
}

由于我们在@MyAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在@MyAnnotation后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割。

所以注解的配置形式如下:

public class Person{
    @MyAnnotation(name = "tom",age = 20,score = {59,68,88})
    public void show(Integer times){
        for(int i = 0; i < times; i++){
            System.out.println("person show!");
        }    
    }
}

注意事项:

  1. 如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!
  2. 如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)
  3. 如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!
  4. 如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。

5.3 反射获取注解

我们定义的@MyAnnotation的保留时期是运行期,所以我们可以使用反射来获取注解。

示例代码:

public class TestAnnotation {
    public static void main(String[] args){
        try {
            //获取Person的Class对象
            Class personClass = Class.forName("com.lfx.test.Person");

            //这里形参写成Integer.class
            Method personMethod = personClass.getMethod("show",Integer.class);

            if(personMethod.isAnnotationPresent(MyAnnotation.class)){
                System.out.println("Person类上配置了MyAnnotation注解!");
                //获取该元素上指定类型的注解
                MyAnnotation myAnnotation = personMethod.getAnnotation(MyAnnotation.class);
                System.out.println("name: " + myAnnotation.name() + ", age: " + myAnnotation.age()
                    + ", score: " + myAnnotation.score()[0]);
            }else{
                System.out.println("Person类上没有配置MyAnnotation注解!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

获取在其他作用范围的注解的方法,已在反射一章做详细介绍,这里不再多言。

posted @ 2020-11-17 10:57  渺渺孤烟起  阅读(147)  评论(0)    收藏  举报