查看原文
其他

理解 Spring 注解编程模型

点击上方 "程序员小乐"关注公众号, 星标或置顶一起成长

每天早上8点20分, 第一时间与你相约

每日英文

We've stuck together for a long time but finally we’ve been defeated by eternity.

我们坚持了好久,但最终还是输给了天长地久。


每日掏心话

人生就像饺子,岁月是皮,经历是馅。每个人都是孤独的个体,人生的痛苦永远多于快乐,正视人生的每一个挫折。


来自:justmehyp | 责编:乐乐

链接:cnblogs.com/justmehyp/p/11575394.html

程序员小乐(ID:study_tech)第 641 次推文   图片来自网络


往日回顾:硬核教程:五步掌握用VSCode进行高效Python开发



   正文   


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";}
@Childclass 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"。
理由如下:

  • @C.c 是 @B.a1 的别名,@B.a1 重写 @A.a1 ,所以这 3 者是一条链上的,它们的值应该相等, 是 "3"。

  • @C.b 重写 @B.b,@B.b 是 @A.a2 的别名,所以这 3 者 也是一条链上的,它们的值也应该相等,是 "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」 一节中,官方原文如下:

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。

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。


欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

GitHub 标星3.5W+,超实用技术面试手册,从工作申请、面试考题再到优势谈判

Java8 之熟透 Lambda 表达式

1 行Python代码能干哪些事,这 13个你知道吗?

Java提供的几种线程池

掌握 SpringMVC 运行原理,看这篇就对了!

关注「程序员小乐」,收看更多精彩内容
嘿,你在看吗?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存