java8注解@Repeatable使用技巧

前言

@Repeatable是java8為了解決同一個(gè)注解不能重復(fù)在同一類(lèi)/方法/屬性上使用的問(wèn)題。

應(yīng)用場(chǎng)景

舉一個(gè)比較貼近開(kāi)發(fā)的例子,在spring/springboot我們引入資源文件可以使用注解@PropertySource

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

那要引入多個(gè)資源文件怎么辦,沒(méi)事,我把PropertySource中的value設(shè)置成String[]數(shù)組就行了

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

那么就能這么用

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

就spring引入配置文件來(lái)講,肯定是沒(méi)事問(wèn)題的。但是如果注解中2個(gè)值存在依賴(lài)關(guān)系,那么這樣就不行了。比如下面這個(gè)

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

    /**
     * 業(yè)務(wù)類(lèi)型
     * @return
     */
    String bizCode();

    /**
     * 訂單類(lèi)型
     * @return
     */
    int orderType();

}

我玩策略模式的時(shí)候喜歡用注解和spring結(jié)合做自動(dòng)策略路由

上面的@validate注解,bizcode和orderType是一對(duì)一的關(guān)系,我希望可以添加如下的注解

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

很抱歉在java8之前,這種方式不行,不過(guò)你可以這么做,新建一個(gè)如下的注解

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

    Validate[] value();

}

注意這個(gè)聚合注解默認(rèn)約束value來(lái)存儲(chǔ)子注解

然后對(duì)應(yīng)代碼修改如下

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

在java8的@Repeatable出來(lái)之后,我們?cè)诓桓膭?dòng)@Validates的情況下,對(duì)@Validate進(jìn)行修改,增加@Repeatable(Validates.class)

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

    /**
     * 業(yè)務(wù)類(lèi)型
     * @return
     */
    String bizCode();

    /**
     * 訂單類(lèi)型
     * @return
     */
    int orderType();

}

那么就可以在類(lèi)上使用多個(gè)@Validate注解了。

回過(guò)頭來(lái)看下@PropertySource和@PropertySources也是如此

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource 

/**
 * Container annotation that aggregates several {@link PropertySource} annotations.
 *
 * <p>Can be used natively, declaring several nested {@link PropertySource} annotations.
 * Can also be used in conjunction with Java 8's support for <em>repeatable annotations</em>,
 * where {@link PropertySource} can simply be declared several times on the same
 * {@linkplain ElementType#TYPE type}, implicitly generating this container annotation.
 *
 * @author Phillip Webb
 * @since 4.0
 * @see PropertySource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources

從上面的注釋中可以看到,spring在4.0支持了java8這個(gè)特性

原理

那么@Repeatable的原理是啥?

語(yǔ)法糖而已!

我們反編譯下面代碼

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

發(fā)現(xiàn)反編譯變成了


語(yǔ)法糖無(wú)疑了。

問(wèn)題來(lái)了

那么再反編譯下面代碼試試

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

生成


那這樣就存在問(wèn)題了,我以為加了@Repeatable之后@Validate都會(huì)生成被語(yǔ)法糖@Validates包裹。沒(méi)想到它居然這么智能,只有一個(gè)@Validate注解的時(shí)候居然不轉(zhuǎn)換。

所以使用多個(gè)@Validate的時(shí)候就會(huì)留坑,你需要兼容1個(gè)或多個(gè)的場(chǎng)景。

推薦方式

直接使用@Validates讀取注解代碼如下

Validates validates = AnnotationUtils.getAnnotation(BizLogic2.class,Validates.class);
        Arrays.stream(validates.value()).forEach(a->{
            System.out.println(a.bizCode());
        });

帶兼容的邏輯如下

Validate validate = AnnotationUtils.getAnnotation(BizLogic.class,Validate.class);
        if(Objects.nonNull(validate)){
            System.out.println(validate.bizCode());
        }
        Validates validates = AnnotationUtils.getAnnotation(BizLogic2.class,Validates.class);
        Arrays.stream(validates.value()).forEach(a->{
            System.out.println(a.bizCode());
        });

如果你是自己寫(xiě)業(yè)務(wù)的,我覺(jué)得第一種方式更加方便。
但是如果你是開(kāi)發(fā)中間件的,那么必須兼容,你永遠(yuǎn)不知道你的傻逼用戶(hù)會(huì)干嗎,哈哈。

還有一點(diǎn)需要注意,我的@Validate是被@Component元注解,當(dāng)多個(gè)@Validate語(yǔ)法糖轉(zhuǎn)換成@Validates之后,由于@Validates上沒(méi)@Component,導(dǎo)致我的bean加載不到spring容器中去

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容