前言

@Repeatable注解是java8为了解决同一个注解不能重复在同一类/方法/属性上使用的问题。

举一个比较贴近开发的例子,在spring/springboot我们引入资源文件可以使用注解@PropertySource

@PropertySource("classpath:sso.properties")
public class Application {
}

那要引入多个资源文件怎么办,没事,我把PropertySource中的value设置成String[]数组就行了

public @interface PropertySource {
      ...
      String[] value();
}

那么就能这么用

@PropertySource({"classpath:sso.properties","classpath:dubbo.properties","classpath:systemInfo.properties"})
public class Application {
}

就spring引入配置文件来讲,肯定是没事问题的。但是如果注解中2个值存在依赖关系,那么这样就不行了。比如下面这个

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Validate {
    /**
     * 业务类型
     * @return
     */
    String bizCode();

    /**
     * 订单类型
     * @return
     */
    int orderType();
}

上面的@validate注解,bizcode和orderType是一对一的关系,我希望可以添加如下的注解

@Validate(bizCode = "fruit",orderType = 1)
@Validate(bizCode = "fruit",orderType = 2)
@Validate(bizCode = "vegetable",orderType = 2)
public class BizLogic2 {
}

很抱歉在java8之前,这种方式不行,不过你可以这么做,新建一个如下的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Validates {

    Validate[] value();

}

然后对应代码修改如下

@Validates(value = {
        @Validate(bizCode = "fruit",orderType = 1)
        @Validate(bizCode = "fruit",orderType = 2)
        @Validate(bizCode = "vegetable",orderType = 2)
})
public class BizLogic2 {
}

在java8的@Repeatable出来之后,我们在不改动@Validates的情况下,对@Validate进行修改,增加
@Repeatable(Validates.class)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Validates.class)
public @interface Validate {

    /**
     * 业务类型
     * @return
     */
    String bizCode();

    /**
     * 订单类型
     * @return
     */
    int orderType();

}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Validates {

    Validate[] value();

}

注意,一旦使用Repeatable注解指定了注解Validates,那么这个聚合注解默认约束value来存储子注解,用其他的名字代替value均会编译报错

那么就可以在类上使用多个@Validate注解了。

@Validate(bizCode = "fruit",orderType = 1)
@Validate(bizCode = "fruit",orderType = 2)
@Validate(bizCode = "vegetable",orderType = 2)
public class BizLogic2 {
}

还有一点需要注意,我的@Validate是被@Component元注解标注,当多个@Validate语法糖转换成@Validates之后,由于@Validates上没@Component,导致我的bean加载不到spring容器中去

原理

那么@Repeatable的原理是啥?
其实就是语法糖

对于下面的注解

@Repeatable(Persons.class)
public @interface Person {
    String role() default "";
    String name() default "";
}
public @interface Persons {
    Person[] value();
}

我们给下面这个类加上注解,重点关注这个类,打开这个编译后的类(即这个java文件对应的class文件)

@Person(role = "警察",name = "小花")
@Person(role = "小偷",name = "笑话")
public class AnnotationTest {
}

我们得到下面的代码

@Persons({@Person(
    role = "警察",
    name = "小花"
), @Person(
    role = "小偷",
    name = "笑话"
)})
public class AnnotationTest {
    public AnnotationTest() {
    }
}

从这里可以看出的确就是语法糖。

问题来了
我们打开下面这个编译后的类

@Person(role = "警察",name = "小花")
public class AnnotationTest {
}

得到的结果是这样

@Person(
    role = "警察",
    name = "小花"
)
public class AnnotationTest {
    public AnnotationTest() {
    }
}

那这样就存在问题了,我以为加了@Repeatable之后@Validate都会生成被语法糖@Validates包裹。没想到它居然这么智能,只有一个@Validate注解的时候居然不转换。
所以使用多个@Validate的时候就会留坑,你需要兼容1个或多个的场景。