说到注解即便你没有动手自己写过也一定在日常开发中经常用到,如果用的又恰好时 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
,且注解作用于程序运行期间。
注解的一大核心作用即信息传递,下述中为注解定义两个属性:value
与 comment
,可以看到注解的成员变量以 ()
结束,其中 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: