在同一个元素上,标记两个相同类型的注解,会导致编译失败。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Value {
String value();
}
// 编译报错,不能标记多个
@Value(value = "1")
@Value(value = "2")
public class Source {}
@Repeatable 是 JDK1.8 增加的元注解,可以解决注解可以多次标记的问题。
如何声明
在注解上标记 @Repeatable 元注解,设置值为当前注解的集合注解,这个注解理论上来说没有其它的意义,仅仅是为了满足语法要求。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Values.class)
public @interface Value {
String value();
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Values {
Value[] value();
}
如何使用
// 编译通过
@Value(value = "1")
@Value(value = "2")
public class Source {}
如何解析
标记了 @Repeatable 的注解,在解析时与普通注解不太一样。JDK 会根据注解数量进行区别解析。
- 如果标记一个,则解析为 @Value
- 如果标记多个,则解析为 @Values
@Value(value = "1")
// @Value(value = "2")
public class Source {
public static void main(String[] args) throws Exception {
Annotation[] annotations = Source.class.getAnnotations();
Arrays.stream(annotations).forEach(System.out::println);
}
}
// 输出:@Value(value=1)
// 打开注释,输出:@Values(value=[@Value(value=1),@Value(value=2)])
因为 @Values 理论上是一个辅助型注解,没有实际的意义,所以在解析过程中,如果能够忽略掉,而只针对 @Value 这个我们实际使用的注解进行解析会更直观一点。
getAnnotationsByType 方法的使用
JDK 提供了 getAnnotationsByType 方法,忽略掉 @Values,收集所有的 @Value 注解作为一个数组进行返回,如果没有,则返回空数组。
@Value(value = "1")
// @Value(value = "2")
public class Source {
public static void main(String[] args) throws Exception {
Annotation[] annotations = Source.class.getAnnotationsByType(Value.class);
Arrays.stream(annotations).forEach(System.out::println);
}
}
// 输出:@Value(value=1)
// 打开注释,输出:@Value(value=1),@Value(value=2)
与 @Inherited 元注解结合使用
为 @Value 及 @Values 添加 @Inherited 元注解,使其拥有被继承的功能
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Values.class)
@Inherited
public @interface Value {
String value();
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Values {
Value[] value();
}
父类与子类分别声明 @Value 注解,观察是继承及重写的情况
@Value("1") // 代码 1
// @Value("2") // 代码 2
public class Base {}
// @Value("3") // 代码 3
// @Value("4") // 代码 4
public class Child extends Base {
public static void main(String[] args) throws Exception {
Annotation[] annotations = Child.class.getAnnotations();
Arrays.stream(annotations).forEach(System.out::println);
}
}
这里需要再次强调解析的规则
- 如果标记一个,则解析为 @Value
- 如果标记多个,则解析为 @Values
子类解析的 @Value 注解会重写父类解析的 @Value 注解,子类解析的 @Values 注解会重写父类解析的 @Values 注解,而解析的 @Value 注解与 @Values 注解则互相不关联。
场景一:只有代码 1
父类标记了一个 @Value 注解,解析为 @Value 注解,子类继承父类
@Value(value=1)
场景二:代码 1、2
父类标记了多个 @Value 注解,解析为 @Value 注解,子类继承父类
@Values(value=[@Value(value=1),@Value(value=2)])
场景三:代码 1、3
父类与子类都只标记了一个注解,都解析为 @Value 注解,子类覆盖父类
@Value(value=3)
场景四:代码 1、2、3
父类标记了多个 @Value 注解,解析为 @Values 注解,子类标记了一个 @Value 注解,解析为 @Value 注解,@value 与 @Values 互不干扰,子类继承父类的 @Values 注解,子类自己的 @Value 注解也独自生效
@Value(value=3)
@Values(value=[@Value(value=1),@Value(value=2)])
场景五:代码 1、3、4
父类标记了一个 @Value 注解,解析为 @Value 注解,子类标记了多个 @Value 注解,解析为 @Values 注解,@value 与 @Values 互不干扰,子类继承父类的 @Value 注解,子类自己的 @Values 注解也独自生效
子类继承父类的 @Value 注解,子类自己被解析为 @Values 注解
@Value(value=1)
@Values(value=[@Value(value=3),@Value(value=4)])
场景四:代码 1、2、3、4
父类与子类都标记了多个注解,都解析为 @Values 注解,子类覆盖父类
@Values(value=[@Value(value=3),@Value(value=4)])
getAnnotationsByType 方法的使用
getAnnotations、getAnnotation、isAnnotationPresent 方法解析过程是同一套体系,getAnnotationsByType 方法的解析过程则是另外一套体系。
不管怎么组合,只要子类标记了 @Value 注解,就会覆盖父类中的标记,如果子类未标记,则会继承父类中的标记。跟 @Values 注解完全无关。
@Value("1") // 代码 1
// @Value("2") // 代码 2
public class Base {}
@Value("3") // 代码 3
@Value("4") // 代码 4
public class Child extends Base {
public static void main(String[] args) throws Exception {
Annotation[] annotations = Child.class.getAnnotationsByType(Value.class);
Arrays.stream(annotations).forEach(System.out::println);
}
}
// 输出:@Value(value=3),@Value(value=4)