理解 Spring 註解編程模型

理解 Spring 註解編程模型

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

  • @C.c 是 @B.a1 的別名,@B.a1 重寫 @A.a1 ,因此這 3 者是一條鏈上的,它們的值應該相等, 是 "3"。
  • @C.b 重寫 @B.b,@B.b 是 @A.a2 的別名,因此這 3 者 也是一條鏈上的,它們的值也應該相等,是 "3"。

而結果倒是,我錯了,@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.

  1. Explicit Aliases: if two attributes in one annotation are declared as aliases for each other via @AliasFor, they are explicit aliases.
  2. Implicit Aliases: if two or more attributes in one annotation are declared as explicit overrides for the same attribute in a meta-annotation via @AliasFor, they are implicit aliases.
  3. Transitive Implicit Aliases: given two or more attributes in one annotation that are declared as explicit overrides for attributes in meta-annotations via @AliasFor, if the attributes effectively override the same attribute in a meta-annotation following the law of transitivity, they are transitive implicit aliases.

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.

  1. Implicit Overrides: given attribute A in annotation @One and attribute A in annotation @Two, if @One is meta-annotated with @Two, then attribute A in annotation @One is an implicit override for attribute A in annotation @Two based solely on a naming convention (i.e., both attributes are named A).
  2. Explicit Overrides: if attribute A is declared as an alias for attribute B in a meta-annotation via @AliasFor, then A is an explicit override for B.
  3. Transitive Explicit Overrides: if attribute A in annotation @One is an explicit override for attribute B in annotation @Two and B is an explicit override for attribute C in annotation @Three, then A is a transitive explicit override for C following the law of transitivity.

屬性別名,有 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"; }

解答步驟是:

  • 對於註解 @C,@C.c = "3", @C.b = "3"
  • 對於註解 @B, @B.a1 被 @C.c 顯式重寫, 因此 @B.a1 = @C.c = "3"; @B.b 被 @C.b 隱式重寫,因此 @B.b = @C.b = "3"
  • 對於註解 @A, @A.a1 被 @B.a1 隱式重寫,因此 @A.a1 = @B.a1 = "2"; @A.a2 被 @B.b 顯式重寫,因此 @A.a2 = @B.b = "2"

能夠看到 @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

相關文章
相關標籤/搜索