Java注解基础介绍


一、定义声明

Java 中通过 @interface 创建注解,并通过 @Target@Retention 定义注解的作用对象与声明周期对象。

1. 作用对象

注解的作用对象表示该注解可作用于何种场景,即该注解是类注解亦或是方法注解等。

通过 @Target 注解定义注解的作用对象,其取值范围如下:

参数 描述
ElementType.TYPE 表明注解作用于类。
ElementType.CONSTRUCTOR 表明注解作用于构造方法。
ElementType.FIELD 表明注解作用于字段。
ElementType.METHOD 表明注解作用于方法。
ElementType.PARAMETER 表明注解作用于方法参数。

2. 作用范围

通过 @Retention 注解定义注解的声明周期,其取值范围如下:

参数 描述
RetentionPolicy.CLASS 默认的取值,表明作用于类文件。
RetentionPolicy.SOURCE 表明注解作用编译期间。
RetentionPolicy.RUNTIME 表明注解作用程序运行期间。

如下定义了一个方法注解,作用对象为方法,并设置作用于程序运行期间。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnn {

    String value() default "";

    String comment() default "";
}

3. 重复注解

默认注解不允许重复作用于同于一个目标,以上述的 @MethodAnn 为例,其无法在同一个方法上声明多次。

因此,在 JDK 8 中引入了新特性 @Repeatable 作用于注解使之允许重复作用于同一个对象。

还是以上述的 @MethodAnn 为例,因为涉及到重复注解所有需要先为定义容器注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnGroup {

    MethodAnn[] value() default {};
}

完成后修改之前定义的 @MethodAnn 注解,添加 @Repeatable 信息。

@Repeatable(MethodAnnGroup.class)
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnn {

    String value() default "";

    String comment() default "";
}

此时 @MethodAnn 即可重复作用于同一个目标,使用方式如下:

public class TestClass {
    
    @MethodAnnGroup({
        @MethodAnn("test-method-message1")
        @MethodAnn("test-method-message2")
    })
    public void method1() {
    }
}

二、属性反射

在完成上述注解的定义与使用之后,即可通过反射获取不同注解中配置的属性值。

新建测试类 TestService.java 对三类注解进行简单应用供后续使用。

@ClassAnn(value = "test-class", comment = "test-class-comment")
public class TestService {

    @FieldAnn("test-field1")
    public String field1;

    @FieldAnn("test-field2")
    public String field2;

    @MethodAnn("test-method1")
    public void method1() {

    }

    @MethodAnn("test-method2")
    public void method2(@ParameterAnn("abc") String msg1,
                        @ParameterAnn("def") String msg2) {
        System.out.println(msg1 + msg2);
    }
}

1. 类注解

在代码运行过程中,针对注解信息的获取通常由反射的方式从而实现。

下述为开发中常涉及到注解反射方法:

方法 作用
isAnnotationPresent(class) 判断字段是否存在注解。
getAnnotatedType() 获取注解作用对象信息,包含字段类型与字段名等信息。
getAnnotations() 获取字段上的所有注解,包含其父类中标识。
getAnnotation(class) 通过注解类信息获取指定注解,包含其父类中标识。
getDeclaredAnnotations() 获取字段上的所有注解,不包含其父类中标识。
getDeclaredAnnotation(class) 通过注解类信息获取指定注解,不包含其父类中标识。

下述是一个简单的使用示例能够直观的看出各自方法的作用:

    public void classDemo() {
        Class<TestService> clazz = TestService.class;
        Class<ClassAnn> annClazz = ClassAnn.class;
        // Class is existed "ClassAnn"?
        System.out.println("Exist annotation: " + clazz.isAnnotationPresent(annClazz));

        // Get declare annotation
        Annotation[] annotations = clazz.getDeclaredAnnotations();
        System.out.println("Annotations: " + Arrays.toString(annotations));

        // Get class annotation value
        ClassAnn classAnn = clazz.getAnnotation(annClazz);
        System.out.println("The annotation value: " + classAnn.value());
        System.out.println("The annotation comment: " + classAnn.comment());
    }

2. 字段注解

下述示例中通过反射获取所有字段列表,再由 getAnnotation() 获取字段注解并其配置值。

public void fieldDemo() {
    Class<TestService> clazz = TestService.class;
    Field[] fields = clazz.getDeclaredFields();
    for (Field f : fields) {
        boolean isPresent = f.isAnnotationPresent(fieldAnnClass);
        System.out.println("Exist annotation? " + isPresent);
        if (isPresent) {
            FieldAnn fieldAnn = f.getAnnotation(FieldAnn.class);
            System.out.println("Value: " + fieldAnn.value());
            System.out.println("Comment: " + fieldAnn.comment());
        }
    }
}

3. 方法注解

方法注解的获取思路与字段注解获取类似,这里就不再描述直接提供代码示例。

public void methodDemo() {
    Class<TestService> clazz = TestService.class;
    Method[] methods = clazz.getDeclaredMethods();
    for (Method m : methods) {
        boolean isPresent = m.isAnnotationPresent(methodAnnClass);
        System.out.println("Exist annotation? " + isPresent);
        if (isPresent) {
            MethodAnn methodAnn = m.getAnnotation(methodAnnClass);
            System.out.println("Value: " + methodAnn.value());
            System.out.println("Comment: " + methodAnn.comment());
        }
    }
}

4. 参数注解

方法中的参数注解获取对象较为复杂,通过反射返回的结果是一个二维数组 A[i][j],其中的 A[i][] 表示方法的第 i 个参数注解, A[][j] 存储的是对应注解的属性值。

参数注解的获取示例如下:

public class TestService {
    public void method2(@ParameterAnn("abc") String msg1,
                        @ParameterAnn("def") String msg2) {
        System.out.println(msg1 + msg2);
    }
}

public void parameterDemo() throws NoSuchMethodException {
    Class<TestService> clazz = TestService.class;
    // 根据方法名和参数类型获取指定方法
    Method method = clazz.getMethod("method2", String.class, String.class);
    // Annotation[i][j] i: 方法的第 i 个参数注解, j: 该参数注解的第 j 个值
    Annotation[][] annos = method.getParameterAnnotations();
    Arrays.stream(annos).forEach(record -> {
        ParameterAnn parameterAnn = null;
        for (Annotation ann : record) {
            if (ann instanceof ParameterAnn) {
                parameterAnn = (ParameterAnn) ann;
            }
        }
        System.out.print("\n" + parameterAnn);
        System.out.print("Annotation value: " + parameterAnn.value());
        System.out.print("Annotation comment: " + parameterAnn.comment());
    });
}

最终打印输出的结果如下:

@xyz.ibudai.ann.ParameterAnn(value="abc", comment=""), Annotation value: abc, Annotation comment: 
@xyz.ibudai.ann.ParameterAnn(value="def", comment=""), Annotation value: def, Annotation comment: 

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录