Spring 中有一個概念叫「元註解」(Meta-Annotation),經過元註解,實現註解的「派生性」,官方的說法是「Annotation Hierarchy」。html
所謂元註解,即標註在註解上的註解。這種方式所造成的註解層級結構中,元註解在層級結構的上面,我叫它父註解(Super Annotation), 被註解的註解在層級結構的下面,叫它子註解(Sub Annotation)。引入元註解的目的是爲了實現屬性重寫(Attribute Override) 的目的。java
舉個簡單的例子:
有 一個類 Home 和 2 個註解,1 個叫 @Parent,另外一個叫 @Child ,@Parent 標註在 @Child 上,@Child 標註在 Home 上,它們都只有一個屬性,叫 name, 若是 @Parent.name 的默認值是 'John',而 @Child.name 的默認值是 'Jack'。
這時,從 Home 上獲取 @Child.name,應該返回 'Jack',這毫無懸念。
那麼,若是獲取 @Parent.name,應該返回什麼呢?根據 Spring 註解的「派生性」,@Child.name override @Parent.name,因此返回結果也是 'Jack'。git
上述例子中的類和註解,代碼大體以下github
@interface Parent { String name() default "John"; } @Parent @interface Child { String name() default "Jack"; } @Child class Home { }
註解層級結構:spring
@Parent @Child
相對於「屬性重寫」,還有另外一個概念是「屬性別名」(Alias),屬性別名之間是互相等價的。
咱們給上面的 @Child 加一個屬性 value,而且使用 @AliasFor ,使 @Child.name 和 @Child.value 互相成爲別名,而且默認值爲空字符串:編程
@interface Child { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; }
標註在 Home 上時,給 @Child.value 設置值爲 "Jack":ide
@Child("Jack") class Home { }
這時,不管是獲取 @Child.name 仍是獲取 @Child.value,其結果老是相同的,都是 "Jack"。說明了屬性別名之間的等價性。spring-boot
屬性別名 和 屬性重寫 實際上是兩個徹底不一樣的概念,可是若是不加區分,模糊概念的話,就會對一些現象不符合預期而感到意外。 考慮如下案例,分別給出 @A.a一、@A.a二、@B.a一、@B.b、@C.c、@C.b 的值:post
@interface A { String a1() default "1"; String a2() default "1"; } @A @interface B { String a1() default "2"; @AliasFor(value = "a2", annotation = A.class) String b() default "2"; } @B @interface C { @AliasFor(value = "a1", annotation = B.class) String c() default "3"; String b() default "3"; }
在我沒有弄清概念以前,我以爲答案應該是:@A.a一、@A.a二、@B.a一、@B.b、@C.c、@C.b 全都是 "3"。
理由以下:ui
而結果倒是,我錯了,@B.a一、@B.b、@C.c、@C.b 的值是 "3", 但 @A.a一、@A.a2 的值是 "2"。
至於爲何,咱們先來認真理解一下 屬性別名 和 屬性重寫 這 2 個概念吧。
援引官方 Wiki https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model
, 其中有關於這兩個概念的澄清。在 「Attribute Aliases and Overrides」 一節中,官方原文以下:
An attribute alias is an alias from one annotation attribute to another annotation attribute. Attributes within a set of aliases can be used interchangeably and are treated as equivalent. Attribute aliases can be categorized as follows.
An attribute override is an annotation attribute that overrides (or shadows) an annotation attribute in a meta-annotation. Attribute overrides can be categorized as follows.
屬性別名,有 3 種, 分別是 顯式別名,隱式別名 和 傳遞隱式別名, 「屬性別名」 只能發生在同一個註解內部。好比:
顯式別名(互相@AliasFor),@A.a1 和 @A.a2,
@interface A { @AliasFor("a2") String a1() default ""; @AliasFor("a1") String a2() default ""; }
隱式別名(@AliasFor到同一個屬性),@B.b1 和 @B.b2
@interface A { String a() default ""; } @A @interface B { @AliasFor(value = "a", annotation = A.class) String b1() default ""; @AliasFor(value = "a", annotation = A.class) String b2() default ""; }
傳遞隱式別名(最終@AliasFor到同一個屬性) @C.c1 和 @C.c2
@interface A { String a() default ""; } @A @interface B { @AliasFor(value = "a", annotation = A.class) String b() default ""; } @B @interface C { @AliasFor(value = "a", annotation = A.class) String c1() default ""; @AliasFor(value = "b", annotation = B.class) String c2() default ""; }
屬性重寫,也有 3 種,分別是 隱式重寫,顯式重寫 和 傳遞顯式重寫,「屬性重寫」只能發生在註解之間。好比:
隱式重寫(同名屬性), @B.a 重寫 @A.a
@interface A { String a() default ""; } @A @interface B { String a() default ""; }
顯式重寫(須要@AliasFor),@B.b 重寫 @A.a
@interface A { String a() default ""; } @A @interface B { @AliasFor(value = "a", annotation = A.class) String b() default ""; }
傳遞顯式重寫(須要 @AliasFor),因爲 @C.c 重寫 @B.b, @B.b 重寫 @A.a, 因此 @C.c 也 重寫 @A.a
@interface A { String a() default ""; } @A @interface B { @AliasFor(value = "a", annotation = A.class) String b() default ""; } @B @interface C { @AliasFor(value = "b", annotation = B.class) String c() default ""; }
理解清楚以後,咱們回到剛纔的題目,樣例重貼以下:
@interface A { String a1() default "1"; String a2() default "1"; } @A @interface B { String a1() default "2"; @AliasFor(value = "a2", annotation = A.class) String b() default "2"; } @B @interface C { @AliasFor(value = "a1", annotation = B.class) String c() default "3"; String b() default "3"; }
解答步驟是:
能夠看到 @A 和 @C 之間沒有任何關係。這裏也根本沒有「屬性別名」的存在,不是用了 @AliasFor 就是 「屬性別名」的。
對於「顯式傳遞重寫」,像上面 "@A.a1 被 @B.a1 隱式重寫, @B.a1 被 @C.c 顯式重寫",或者 "@A.a2 被 @B.b 顯式重寫, B.b 被 @C.b 隱式重寫", 重寫關係是不會傳遞的。
屬性別名,有 3 種, 分別是 顯式別名,隱式別名 和 傳遞隱式別名, 「屬性別名」 只能發生在同一個註解內部。 屬性重寫,也有 3 種,分別是 隱式重寫,顯式重寫 和 傳遞顯式重寫,「屬性重寫」只能發生在註解之間。
Spring 對於註解編程模型的代碼實現,主要在 AnnotatedElementUtils 這個類中,作試驗可使用這個方法:AnnotatedElementUtils#getMergedAnnotationAttributes。
須要注意的是,「隱式重寫」不適用於 value 屬性,貌似 value 屬性是一個相對特殊的屬性。
如下示例, @B.value 不會 隱式重寫 @A.value
@interface A { String value() default "a"; } @A @interface B { String value() default "b"; }
但只要屬性名不是 value,均可以 隱式重寫 , @B.xxx 隱式重寫 @A.xxx
@interface A { String xxx() default "a"; } @A @interface B { String xxx() default "b"; }
我跟了如下源碼,發現源碼中確實對 value 屬性作了特殊判斷,代碼位置在 org.springframework.core.annotation.AnnotatedElementUtils.MergedAnnotationAttributesProcessor#postProcess
方法中,代碼片斷以下;
// Implicit annotation attribute override based on convention else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) { overrideAttribute(element, annotation, attributes, attributeName, attributeName); }
其中,AnnotationUtils.VALUE 是一個常量,其值爲 "value"。暫時沒有找到官方說明爲何要對 value 屬性作特殊處理。猜想是不少註解只有一個屬性, 爲了編程方便,由於不須要 @A(value = "hello world) 這樣使用, 只須要 @A("hello world") 便可。這種狀況下,若是隱式重寫,可能不是編碼者想要的結果。
值得一提的是,顯式重寫 沒有這種特殊處理,如下示例 @B.value 會顯式重寫 @A.value:
@interface A { String value() default "a"; } @A @interface B { @AliasFor(annotation = A.class) String value() default "b"; }
本文討論所涉及的 Spring Boot 版本 >= 2.0.2.RELEASE。
原文出處:https://www.cnblogs.com/justmehyp/p/11575394.html