java:@Repeatable注解使用
1 前言
java8新增了注解@Repeatable,在hibernate-validator的源码注解如@MAX、@NotNull等中,有@Repeatable注解的使用,源码示例如下:
/*
* Jakarta Bean Validation API
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package javax.validation.constraints;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotNull.List;
/**
* The annotated element must not be {@code null}.
* Accepts any type.
*
* @author Emmanuel Bernard
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@link NotNull} annotations on the same element.
*
* @see javax.validation.constraints.NotNull
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
NotNull[] value();
}
}
2 使用
参考hibernate-validator注解,使用@Repeatable:
2.1 新建注解RepeaDemo,暂时不添加@Repeatable:
package com.xiaoxu.tool.demo;
import java.lang.annotation.*;
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeaDemo {
String id();
String name();
}
新建demo类:
package com.xiaoxu.tool.demo;
import java.lang.reflect.Field;
import java.util.Arrays;
/**
* @author xiaoxu
* @date 2022-05-17
* spring_boot:com.xiaoxu.tool.demo.TestRepeatable
*/
public class TestRepeatable {
@RepeaDemo(id = "1001",name = "荔枝")
private String fruit1;
@RepeaDemo(id = "1002",name = "葡萄")
private String fruit2;
public static void demo1(){
Class<?> a = TestRepeatable.class;
Field[] fruits = a.getDeclaredFields();
Arrays.stream(fruits).forEach(f->{
if(f.isAnnotationPresent(RepeaDemo.class)){
RepeaDemo anno = f.getAnnotation(RepeaDemo.class);
String id = anno.id();
String name = anno.name();
System.out.printf("水果id:%s,水果名称:%s%n",id,name);
}
});
}
public static void main(String[] args) {
demo1();
}
}
执行结果如下:
水果id:1001,水果名称:荔枝
水果id:1002,水果名称:葡萄
但是问题是,如果希望在字段上增加多个注解,那么会提示Duplicate annotation.错误:
于是考虑新增一个RepeaDemos注解,用于存储@RepeaDemo注解数组,就可以达到多个注解的使用了,如下:
package com.xiaoxu.tool.demo;
import java.lang.annotation.*;
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeaDemos {
RepeaDemo[] repeaDemos();
}
修改TestRepeatable代码:
package com.xiaoxu.tool.demo;
import java.lang.reflect.Field;
import java.util.Arrays;
/**
* @author xiaoxu
* @date 2022-05-17
* spring_boot:com.xiaoxu.tool.demo.TestRepeatable
*/
public class TestRepeatable {
@RepeaDemos(repeaDemos = {
@RepeaDemo(id = "1001",name = "荔枝"),
@RepeaDemo(id = "1003",name = "沃柑")
})
private String fruit1;
@RepeaDemo(id = "1002",name = "葡萄")
private String fruit2;
public static void demo1(){
Class<?> a = TestRepeatable.class;
Field[] fruits = a.getDeclaredFields();
Arrays.stream(fruits).forEach(f->{
if(f.isAnnotationPresent(RepeaDemo.class)){
RepeaDemo anno = f.getAnnotation(RepeaDemo.class);
String id = anno.id();
String name = anno.name();
System.out.printf("水果id:%s,水果名称:%s%n",id,name);
}
});
}
public static void demo2(){
Class<?> a = TestRepeatable.class;
Field[] fruits = a.getDeclaredFields();
Arrays.stream(fruits).forEach(f->{
if(f.isAnnotationPresent(RepeaDemos.class)){
RepeaDemos anno = f.getAnnotation(RepeaDemos.class);
RepeaDemo[] res= anno.repeaDemos();
Arrays.stream(res).forEach(r-> System.out.printf("水果id:%s,水果名称:%s%n",r.id(),r.name()));
}
if(f.isAnnotationPresent(RepeaDemo.class)){
RepeaDemo anno = f.getAnnotation(RepeaDemo.class);
String id = anno.id();
String name = anno.name();
System.out.printf("水果id:%s,水果名称:%s%n",id,name);
}
});
}
public static void main(String[] args) {
demo2();
}
}
执行结果如下:
水果id:1001,水果名称:荔枝
水果id:1003,水果名称:沃柑
水果id:1002,水果名称:葡萄
2.2 @Repeatable注解使用:
对于上述注解使用,@Repeatable注解的作用就是使得@RepeaDemo注解可以多个添加在字段上,而最外层无须包裹@RepeaDemos注解,当然,@RepeaDemos注解任然需要保留,修改如下:
修改@RepeaDemos注解(注意@RepeaDemos注解,必须改为value()才可以):
package com.xiaoxu.tool.demo;
import java.lang.annotation.*;
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeaDemos {
RepeaDemo[] value();
}
修改@RepeaDemo注解,增加@Repeatable,参数为RepeaDemos.class:
package com.xiaoxu.tool.demo;
import java.lang.annotation.*;
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeaDemos.class)
@Documented
public @interface RepeaDemo {
String id();
String name();
}
修改demo类:
package com.xiaoxu.tool.demo;
import java.lang.reflect.Field;
import java.util.Arrays;
/**
* @author xiaoxu
* @date 2022-05-17
* spring_boot:com.xiaoxu.tool.demo.TestRepeatable
*/
public class TestRepeatable {
// @RepeaDemos(value = {
// @RepeaDemo(id = "1001",name = "荔枝"),
// @RepeaDemo(id = "1003",name = "沃柑")
// })
@RepeaDemo(id = "1001",name = "荔枝")
@RepeaDemo(id = "1003",name = "沃柑")
private String fruit1;
@RepeaDemo(id = "1002",name = "葡萄")
private String fruit2;
public static void demo1(){
Class<?> a = TestRepeatable.class;
Field[] fruits = a.getDeclaredFields();
Arrays.stream(fruits).forEach(f->{
if(f.isAnnotationPresent(RepeaDemo.class)){
RepeaDemo anno = f.getAnnotation(RepeaDemo.class);
String id = anno.id();
String name = anno.name();
System.out.printf("水果id:%s,水果名称:%s%n",id,name);
}
});
}
public static void demo2(){
Class<?> a = TestRepeatable.class;
Field[] fruits = a.getDeclaredFields();
Arrays.stream(fruits).forEach(f->{
if(f.isAnnotationPresent(RepeaDemos.class)){
RepeaDemos anno = f.getAnnotation(RepeaDemos.class);
RepeaDemo[] res= anno.value();
Arrays.stream(res).forEach(r-> System.out.printf("水果id:%s,水果名称:%s%n",r.id(),r.name()));
}
if(f.isAnnotationPresent(RepeaDemo.class)){
RepeaDemo anno = f.getAnnotation(RepeaDemo.class);
String id = anno.id();
String name = anno.name();
System.out.printf("水果id:%s,水果名称:%s%n",id,name);
}
});
}
public static void main(String[] args) {
demo2();
}
}
执行效果如下:
水果id:1001,水果名称:荔枝
水果id:1003,水果名称:沃柑
水果id:1002,水果名称:葡萄
可见,如下两种方式,获取的结果是一致的,其实@Repeatable注解就是一种语法糖(类似于python的装饰器语法糖等等),本质上在java编译时,使用如下的第2种注解方式,等同于使用第1种:
//1:
@RepeaDemos(value = {
@RepeaDemo(id = "1001",name = "荔枝"),
@RepeaDemo(id = "1003",name = "沃柑")
})
//2:
@RepeaDemo(id = "1001",name = "荔枝")
@RepeaDemo(id = "1003",name = "沃柑")
2.3 小结
1 @Repeatable注解使用前,需先增加一个注解的属性值为子注解数组,且名称为value()的父注解;
2 @Repeatable注解需要在子注解上标注,且@Repeatable注解的值为父注解class;
3 @Repeatable注解本质上是一种语法糖。