Java

java 注解

2021-09-10  本文已影响0人  zhbi98

1. 什么是注解

注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”,类似于下方代码这样:
类或方法的定义之前都含有一条@开头的语句,例如@Resource("hello"),@Inject,@PostConstruct,@Override这些都是注解。

@Resource("hello")
public class Hello {
    @Inject
    int n;

    @PostConstruct
    public void hello(@Param String name) {
        System.out.println(name);
    }

    @Override
    public String toString() {
        return "hello";
    }
}

2. 注解的作用

注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。Java的注解可以分为三类。

2.1. 第一类注解

由编译器使用的注解

@Override:让编译器检查该方法是否正确地实现了覆写。
@SuppressWarnings:告诉编译器忽略此处代码产生的警告。
这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

2.2. 第二类注解

由工具处理.class文件使用的注解

有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。

2.3. 第三类注解

在程序运行期能够读取的注解

在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。

3. 元注解

元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能(用元注解说明我们自定义的注解可以用在哪里,用于类还是方法上)。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)共五种。

3.1. @Retention

Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期

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

如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用@Retention(RetentionPolicy.RUNTIME)

3.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类型

3.3. @Document

Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

3.4. @Inherited

Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也将继承父类的注解。
下面我们来看个@Inherited注解例子,这个例子通过自定义的注解和自定义的父类,子类进行测试。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {

}


// 父类标注自定义注解
@MyTestAnnotation
public class Father {
    
}


public class Son extends Father {

}

// 测试子类获取父类自定义注解
public class test {
   public static void main(String[] args){
        // 获取Son的class对象
        Class<Son> sonClass = Son.class;
        // 获取Son类上的注解MyTestAnnotation可以执行成功,这里得到的是父类的注解
        MyTestAnnotation annotation = sonClass.getAnnotation(MyTestAnnotation.class);
    }
}

3.5. @Repeatable

Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义
经过@Repeatable修饰后,在某个类型声明处,就可以添加多个相同的注解。

@Repeatable(Reports.class)
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

@Target(ElementType.TYPE)
public @interface Reports {
    Report[] value();
}

// 这里添加了两个相同的注解,这样达到重复的效果
@Report(type=1, level="debug")
@Report(type=2, level="warning")
    public class Hello {
}

4. 注解的属性(变量)

注解的属性(即成员变量)其实和类中定义的变量具有相同之处,只是注解中的变量都是成员变量(属性),并且注解中是没有方法的,只有成员变量。当然注解内部也可以什么属性也不定义。
\color{#FF0000}{属性名称后面需要跟随括号需要注意它们仅仅是一个变量,不是方法。}

4.1. 注解属性类型

注解属性类型可以为以下列出的类型

  • 1.基本数据类型,例如 int,char,float,double等
  • 2.String
  • 3.枚举类型
  • 4.注解类型
  • 5.Class类型
  • 6.以上类型的一维数组类型

4.2. 注解属性赋值

可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value。

public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

public @interface MyTestAnnotation {
    String name() default "Dog";
    int age() default 18;
}

4.3. 获取注解属性

获取注解属性是使用注解的关键,获取属性的值才是使用注解的目的,SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有RUNTIME类型的注解不但要使用,还经常需要编写。所以我们一般用得最多的注解类型是RUNTIME类型的注解,所以主要说明如何获取这类注解的内容。

那我们怎么才能获取注解定义的内容呢,获取注解的内容需要使用Java反射。那为什么又是使用反射来获取内容而不能是其他的办法呢?
因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括:
判断某个注解是否存在于Class、Field、Method或Constructor:

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

例如判断@GoogleAnnotation是否存在于GoogleAnnotationClass类:
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

Class<GoogleAnnotationClass> googleAnnotationClass = GoogleAnnotationClass.class;
boolean classAnnotationPresent = googleAnnotationClass.isAnnotationPresent(GoogleAnnotation.class);

使用反射API读取Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

在googleAnnotationClass类中获取GoogleAnnotation注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
获取所有注解,可以存入一个数组 public Annotation[] getAnnotations()

GoogleAnnotation annotation = googleAnnotationClass.getAnnotation(GoogleAnnotation.class);

获取类的注解:

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


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface GoogleAnnotation {
    String name() default "default";
    int age() default 255;
}


@GoogleAnnotation(name="Google", age=30)
class GoogleAnnotationClass {
    @Deprecated
    void fatherFunction() {
        System.out.println("Hello, World!");
    }

    @SuppressWarnings("deprecation")
    void fatherFunction2() {
        System.out.println("Hello, World!");
    }
}


public class AnnotationLearn  {
    public static void main(String[] args) throws NoSuchMethodException {
        // Get class annotation
        Class<GoogleAnnotationClass> googleAnnotationClass = GoogleAnnotationClass.class;
        
        // 判断GoogleAnnotationClass类中是否存在GoogleAnnotation注解
        boolean classAnnotationPresent = googleAnnotationClass.isAnnotationPresent(GoogleAnnotation.class);
        if (classAnnotationPresent == true) {
            GoogleAnnotation annotation = googleAnnotationClass.getAnnotation(GoogleAnnotation.class);

            System.out.println(annotation.name());
            System.out.println(annotation.age());
        }
    }
}
-------------------------------------------
Google
30

获取方法的注解:

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


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface GoogleAnnotation {
    String name() default "default";
    int age() default 255;
}


@GoogleAnnotation(name="Google", age=30)
class GoogleAnnotationClass {
    @Deprecated
    void fatherFunction() {
        System.out.println("Hello, World!");
    }

    @SuppressWarnings("deprecation")
    void fatherFunction2() {
        System.out.println("Hello, World!");
    }
}


public class AnnotationLearn  {
    public static void main(String[] args) throws NoSuchMethodException {
        // Get method annotation
        Method method = GoogleAnnotationClass.class.getDeclaredMethod("fatherFunction");
        
        // 判断fatherFunction方法中是否存在Deprecated注解
        boolean funAnnotationPresent = method.isAnnotationPresent(Deprecated.class);
        if (funAnnotationPresent == true) {
            Annotation ans = method.getAnnotation(Deprecated.class);
            System.out.println("method annotation:" + ans);
        }
    }
}
----------------------------------------------------------------------
method annotation:@java.lang.Deprecated()

5. 注解的实质

注解的本质就是一个Annotation接口,下面代码为一个自定义注解,可以看出它其实是一个接口,它使用了Retention元注解指明了这个注解的能够存在的环境为JVM运行期间,使用了Target元注解指明了这个注解能够作用于接口、类、枚举、注解。没错它也可以作用于注解,相当于注解的注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface GoogleAnnotation {
    String name() default "default";
    int age() default 255;
}

6. 注解使用场景

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。

注解有许多用处,主要如下:

  • 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
上一篇下一篇

猜你喜欢

热点阅读