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