Spring 注解编程模型相关知识详解
|
Spring 中有一个概念叫「元注解」(Meta-Annotation),通过元注解,实现注解的「派生性」,官方的说法是「Annotation Hierarchy」。 什么是元注解 所谓元注解,即标注在注解上的注解。这种方式所形成的注解层级结构中,元注解在层级结构的上面,我叫它父注解(Super Annotation), 被注解的注解在层级结构的下面,叫它子注解(Sub Annotation)。引入元注解的目的是为了实现属性重写(Attribute Override) 的目的。 举个简单的例子: 有 一个类 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'。 上述例子中的类和注解,代码大致如下
@interface Parent {
String name() default "John";
}
@Parent
@interface Child {
String name() default "Jack";
}
@Child
class Home { }
注解层级结构: @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":
@Child("Jack")
class Home { }
这时,无论是获取 @Child.name 还是获取 @Child.value,其结果总是相同的,都是 "Jack"。说明了属性别名之间的等价性。 属性别名 和 属性重写 属性别名 和 属性重写 其实是两个完全不同的概念,但是如果不加区分,模糊概念的话,就会对一些现象不符合预期而感到意外。 考虑以下案例,分别给出 @A.a1、@A.a2、@B.a1、@B.b、@C.c、@C.b 的值:
@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.a1、@A.a2、@B.a1、@B.b、@C.c、@C.b 全都是 "3"。
而结果却是,我错了,@B.a1、@B.b、@C.c、@C.b 的值是 "3", 但 @A.a1、@A.a2 的值是 "2"。 至于为什么,我们先来认真理解一下 属性别名 和 属性重写 这 2 个概念吧。 援引官方 Wiki https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model,其中有关于这两个概念的澄清。在 「Attribute Aliases and Overrides」 一节中,官方原文如下:
属性别名,有 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 = 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);
}
(编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
