Java注解基础介绍


说到注解即便你没有动手自己写过也一定在日常开发中经常用到,如果用的又恰好时 Spring Boot 框架,那肯定更是了如于心。

对于注解的而言,在我看来其核心的一点就标记,方式程序运行时通过此标记作更多的逻辑功能,而在标记的过程中其又能传递一些信息,即注解成员属性。

下面就让我们看一下注解的定义以及基础的使用方式

一、定义声明

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,且注解作用于程序运行期间。

注解的一大核心作用即信息传递,下述中为注解定义两个属性:valuecomment,可以看到注解的成员变量以 () 结束,其中 default 表示缺省时的默认值。

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

    String value() default "";

    String comment() default "";
}

3. 重复注解

默认注解不允许重复作用于同于一个目标,以上述的 @MyMethod 为例,其无法在同一个方法上声明多次。因此,在 JDK 8 中引入了新特性 @Repeatable 作用于注解使之允许重复作用于同一个对象。

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

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

    MyMethod[] value() default {};
}

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

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

    String value() default "";

    String comment() default "";
}

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

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

二、属性反射

在上述的内容中我们了解了注解的基本定义,但注解更多的功能是标记以及信息传递,其自身并不提供自发的额外功能。

因此,程序中所定义的注解可以理解为积木,还需要根据自身需求搭建对应“建筑”,而在 Java 中,则是通过反射的方式进行,可以说注解反射不分家。

在开始前新建测试类 TestService 对三类注解进行简单应用供后续使用。

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

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

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

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

    }

    @MyMethod("test-method2")
    public void method2(@MyParameter("abc") String msg1,
                        @MyParameter("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<MyClass> annClazz = MyClass.class;
    // Class is existed "MyClass"?
    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
    MyClass 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;
    // Get all field in target class
    Field[] fields = clazz.getDeclaredFields();
    for (Field f : fields) {
        boolean isPresent = f.isAnnotationPresent(MyField.class);
        System.out.println("Exist annotation? " + isPresent);
        if (isPresent) {
            MyField fieldAnn = f.getAnnotation(MyField.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(MyMethod.class);
        System.out.println("Exist annotation? " + isPresent);
        if (isPresent) {
            MyMethod methodAnn = m.getAnnotation(MyMethod.class);
            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(@MyParameter("abc") String msg1,
                        @MyParameter("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.getMyParameterotations();
    Arrays.stream(annos).forEach(record -> {
        MyParameter parameterAnn = null;
        for (Annotation ann : record) {
            if (ann instanceof MyParameter) {
                parameterAnn = (MyParameter) ann;
            }
        }
        System.out.print("\n" + parameterAnn);
        System.out.print("Annotation value: " + parameterAnn.value());
        System.out.print("Annotation comment: " + parameterAnn.comment());
    });
}

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

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

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