前言
@Repeatable
是java8為了解決同一個(gè)注解不能重復(fù)在同一類/方法/屬性上使用的問題滔驾。
應(yīng)用場景
舉一個(gè)比較貼近開發(fā)的例子谒麦,在spring/springboot我們引入資源文件可以使用注解@PropertySource
@PropertySource("classpath:sso.properties")
public class Application {
}
那要引入多個(gè)資源文件怎么辦,沒事哆致,我把PropertySource中的value設(shè)置成String[]數(shù)組就行了
public @interface PropertySource {
...
String[] value();
}
那么就能這么用
@PropertySource({"classpath:sso.properties","classpath:dubbo.properties","classpath:systemInfo.properties"})
public class Application {
}
就spring引入配置文件來講绕德,肯定是沒事問題的。但是如果注解中2個(gè)值存在依賴關(guān)系摊阀,那么這樣就不行了耻蛇。比如下面這個(gè)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
//@Repeatable(Validates.class)
public @interface Validate {
/**
* 業(yè)務(wù)類型
* @return
*/
String bizCode();
/**
* 訂單類型
* @return
*/
int orderType();
}
我玩策略模式的時(shí)候喜歡用注解和spring結(jié)合做自動策略路由
上面的@validate注解,bizcode和orderType是一對一的關(guān)系胞此,我希望可以添加如下的注解
@Validate(bizCode = "fruit",orderType = 1)
@Validate(bizCode = "fruit",orderType = 2)
@Validate(bizCode = "vegetable",orderType = 2)
public class BizLogic2 {
}
很抱歉在java8之前臣咖,這種方式不行,不過你可以這么做漱牵,新建一個(gè)如下的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Validates {
Validate[] value();
}
注意這個(gè)聚合注解默認(rèn)約束value來存儲子注解
然后對應(yīng)代碼修改如下
@Validates(value = {
@Validate(bizCode = "fruit",orderType = 1)
@Validate(bizCode = "fruit",orderType = 2)
@Validate(bizCode = "vegetable",orderType = 2)
})
public class BizLogic2 {
}
在java8的@Repeatable出來之后夺蛇,我們在不改動@Validates的情況下,對@Validate進(jìn)行修改酣胀,增加@Repeatable(Validates.class)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Validates.class)
public @interface Validate {
/**
* 業(yè)務(wù)類型
* @return
*/
String bizCode();
/**
* 訂單類型
* @return
*/
int orderType();
}
那么就可以在類上使用多個(gè)@Validate注解了刁赦。
回過頭來看下@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的原理是啥?
語法糖而已甚脉!
我們反編譯下面代碼
/*@Validates(value = {*/
@Validate(bizCode = "fruit",orderType = 1)
@Validate(bizCode = "fruit",orderType = 2)
@Validate(bizCode = "vegetable",orderType = 2)
/*})*/
public class BizLogic2 {
}
發(fā)現(xiàn)反編譯變成了
語法糖無疑了丸升。
問題來了
那么再反編譯下面代碼試試
/*@Validates(value = {*/
@Validate(bizCode = "fruit",orderType = 1)
// @Validate(bizCode = "fruit",orderType = 2)
//@Validate(bizCode = "vegetable",orderType = 2)
/*})*/
public class BizLogic2 {
}
生成
那這樣就存在問題了,我以為加了@Repeatable之后@Validate都會生成被語法糖@Validates包裹牺氨。沒想到它居然這么智能狡耻,只有一個(gè)@Validate注解的時(shí)候居然不轉(zhuǎn)換。
所以使用多個(gè)@Validate的時(shí)候就會留坑猴凹,你需要兼容1個(gè)或多個(gè)的場景酝豪。
推薦方式
直接使用@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());
});
如果你是自己寫業(yè)務(wù)的,我覺得第一種方式更加方便精堕。
但是如果你是開發(fā)中間件的,那么必須兼容蒲障,你永遠(yuǎn)不知道你的傻逼用戶會干嗎歹篓,哈哈。
還有一點(diǎn)需要注意揉阎,我的@Validate是被@Component元注解庄撮,當(dāng)多個(gè)@Validate語法糖轉(zhuǎn)換成@Validates之后,由于@Validates上沒@Component毙籽,導(dǎo)致我的bean加載不到spring容器中去