[Java注解学习]什么是注解?如何自定义注解?如何通过反射获取注解

723 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

1. 注解

1.1. 注解介绍

1. 什么是注解?

Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些

注解本质是一个继承了Annotation 的特殊接口:

Annotation类:

package java.lang.annotation;
​
public interface Annotation {
​
    boolean equals(Object obj);
​
    int hashCode();
​
    String toString();
​
    Class<? extends Annotation> annotationType();
}

Override类:

package java.lang;
​
import java.lang.annotation.*;
​
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

其实就是下面的意思:

public interface Override extends Annotation{
​
}

2. Annotation的作用

  • 不是程序本身,可以对程序作出解释
  • 可以被其他程序(比如:编译器等)读取

3. 两种注解解析方法

注解只有被解析之后才会生效,常见的解析方法有两种:

  • 编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
  • 运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value@Component)都是通过反射来进行处理的。

1.2. 内置注解

  • @Override: 此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明
  • @Deprecated: 此注释可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常他很危险或者存在更好的选择。
  • @SuppressWarnings: 用来一直抑制编译时的警告
  • ……

例子 什么是注解

public class Test01 extends Object {
    // @Override  重写的注解
    @Override
    public String toString() {
        return super.toString();
    }
    // Deprecated 不推荐程序员使用,但是可以使用,或者存在更好的方式
    @Deprecated
    public static void test(){
        System.out.println("Deprecated");
    }
    @SuppressWarnings(value = "all")
    public void test02(){
        List list = new ArrayList();
    }
    public static void main(String[] args) {
        test();
    }
}

1.3. 元注解

元注解的作用就是负责注解其他注解。

  • @Retention:指定其所修饰的注解的保留策略
  • @Document:该注解是一个标记注解,用于指示一个注解将被文档化
  • @Target:用来限制注解的使用范围
  • @Inherited:该注解使父类的注解能被其子类继承
  • @Repeatable:该注解是Java8新增的注解,用于开发重复注解
  • 类型注解(Type Annotation):该注解是Java8新增的注解,可以用在任何用到类型的地方

1. @Retention注解

@Retention注解注解用于指定被修饰的注解可以保留多长时间,即指定JVM策略在哪个时间点上删除当前注解。保留策略值有以下三个:

策略值功能描述
Retention.SOURCE注解只在源文件中保留,在编译期间删除
Retention.CLASS注解只在编译期间存在于.class文件中,运行时JVM不可获取注解信息,该策略值也是默认值
Retention.RUNTIME运行时JVM可以获取注解信息(反射),是最长注解持续期

2. @Document注解

@Document注解用于指定被修饰的注解可以被javadoc工具提取成文档。定义注解类时使用@Document注解进行修饰,则所有使用该注解修饰的程序元素的API文档中将会包含该注解说明。

3. @Target注解

@Target注解用来限制注解的使用范围,即指定被修饰的注解能用于哪些程序单元

枚举值功能描述
ElementType.Type可以修饰类、接口、注解或枚举类型
ElementType.FIELD可以修饰属性(成员变量),包括枚举常量
ElementType.METHOD可以修饰方法
ElementType.PAPAMETER可以修饰参数
ElementType.CONSTRUCTOR可以修饰构造方法
ElementType.LOCAL_VARIABLE可以修饰局部变量
ElementType.ANNOTATION_TYPE可以修饰注解类
ElementType.PACKAGE可以修饰包

4. @Inherited注解

@Inherited注解指定注解具有继承性,如果某个注解使用@Inherited进行修饰,则该类使用该注解时,其子类将自动被修饰

示例:

/*
    Retention 表示我们的注解在什么地方还有效
    RUNTIME>class>sources
 */
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE}) // Target 表示我们的注解可以用在哪个地方
@Documented // Documented 表示是否将我们的注解生成在JAVAdoc中
@Inherited // Inherited 子类可以继承父类的注解
public @interface MyAnnotation {
​
}

使用:

@MyAnnotation
public class test {
    @MyAnnotation
    public void testMethod(){}
}

1.4. 自定义注解

使用@interface自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口

  • @interface用来声明一个注解,格式:

    public @interface 注解名 {
    }
    
  • 其中的每一个方法实际上是声明了一个配置参数:

    • 方法的名称就是参数的名称
    • 返回值类型就是参数的类型(返回值只能是基本类型,Class , String , enum )
    • 可以通过default来声明参数的默认值
    • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值﹐我们定义注解元素时,经常使用空字符串,0作为默认值.

示例:

MyAnnotation2注解:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation2 {
    // 注解的参数:参数类型 + 参数名 ()
    String name() default "";
​
    int age();
​
    int id() default -1;
​
    String[] schools() default {"A学院", "B学院"};
​
}

MyAnnotation3注解:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
    String value();
}

使用:

public class Test2 {
    // 注解可以显示赋值:如果没有默认值,我们就必须给注释赋值
    @MyAnnotation2(age = 18, name = "我")
    public void test() {
    }
​
    @MyAnnotation3("我")
    public void test2() {
    }
}

1.5. 利用反射识别注解

1.5.1. AnnotatedElement接口

AnnotatedElement接口表示目前正在此 JVM 中运行的程序的一个已注释元素,该接口允许反射性地读取注释。

该接口主要有如下几个实现类:

  • Class:类定义
  • Constructor:构造器定义
  • Field:类的成员变量定义
  • Method:类的方法定义
  • Package:类的包定义

调用AnnotatedElement对象的如下方法可以访问Annotation信息:

  • getAnnotation(Class<T>annotationClass):返回该程序元素上存在的指定类型的注释,如果该类型的注释不存在,则返回null。
  • Annotation[] getAnnotations():返回此元素上存在的所有注释。
  • boolean isAnnotationPresent(Class<?extendsAnnotation>annotationClass)判断该程序元素上是否存在指定类型的注释,如果存在则返回true,否则返回false。
  • Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。

正是因为这些类实现了AnnotatedElement类,我们才可以通过反射获取。

使用我们上面定义的注解:

@MyAnnotation
@MyAnnotation2(name = "xiaoming",age = 18, id = 123, schools = {"xx小学", "xx中学"})
public class Person1 {
​
    @MyAnnotation3(value = "study")
    public void todo(){
        System.out.println("我正在学习");
    }
}
@MyAnnotation2(name = "laowang", age = 50)
public class Person2 {
​
    @MyAnnotation3(value = "work")
    public void toParams(String a, int b){
        System.out.println(a + "正在工作");
        System.out.println(b + "正在工作");
​
    }
}

1.5.2. 识别方法上的注解

public class 利用反射识别方法上的注解 {
    public static void main(String[] args) throws Exception {
        Class<Person1> person1Class = Person1.class;
        Person1 person1 = person1Class.newInstance();
        // 获取 todo 方法
        Method todo1 = person1Class.getDeclaredMethod("todo");
        // 输出方法上注解里的值
        iteratorAnnotations(todo1);
        // 执行方法
        todo1.invoke(person1);
        //-----------------------------------------------
        Class<Person2> person2Class = Person2.class;
        Person2 person2 = person2Class.newInstance();
        // 获取 todo 方法
        Method todo2 = person2Class.getDeclaredMethod("toParams", new Class[]{String.class, int.class});
        // 输出方法上注解里的值
        iteratorAnnotations(todo2);
        // 执行方法
        todo2.invoke(person2, new Object[] {"String", 1});
​
    }
​
    public static void iteratorAnnotations(Method method) {
        // method.isAnnotationPresent() 判断 method 方法是否包含括号里的注解
        if (method.isAnnotationPresent(MyAnnotation3.class)) {
            // 获取该方法的 MyAnnotation3 注解实例
            MyAnnotation3 annotation3 = method.getAnnotation(MyAnnotation3.class);
            // 获取 myAnnotation的值,并打印出来
            String value = annotation3.value();
            System.out.println(value);
        }
    }
}

1.5.3. 识别类上的注解

public class 利用反射识别类上的注解 {
    public static void main(String[] args) throws Exception {
        Class<Person1> person1Class = Person1.class;
        iteratorAnnotations(person1Class);
        //-----------------------------------------------
        Class<Person2> person2Class = Person2.class;
        iteratorAnnotations(person2Class);
​
    }
​
    public static void iteratorAnnotations(Class clazz) {
        // clazz.isAnnotationPresent() 判断 class 方法是否包含括号里的注解
        if (clazz.isAnnotationPresent(MyAnnotation2.class)) {
            // 获得这个注解实例
            MyAnnotation2 annotation2 = (MyAnnotation2) clazz.getAnnotation(MyAnnotation2.class);
            // HashMap方便输出
            HashMap<String, Object> map = new HashMap<>();
            map.put("name", annotation2.name());
            map.put("age", annotation2.age());
            map.put("id", annotation2.id());
            map.put("schools", Arrays.toString(annotation2.schools()));
            System.out.println(map);
        }
    }
}

如果不使用clazz.isAnnotationPresent()就使用instanceof,不过这样就需要获取所有注解后一个一个判断

1.5.4. 获取类上的所有注解

public class 获取类上的所有注解 {
    public static void main(String[] args) {
        // 得到Class类对象
        Class<Person1> person1Class = Person1.class;
​
        // 获取Test类上所有的注解
        Annotation[] annotations = person1Class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
            if(annotation instanceof MyAnnotation){
                System.out.println("    ---->我是MyAnnotation.class的注解");
            }
        }
​
    }
}

\