理解 Spring 注解编程模型
理解 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"。
理由如下:
- @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.
- Explicit Aliases: if two attributes in one annotation are declared as aliases for each other via @AliasFor, they are explicit aliases.
- 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.
- 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.
- 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).
- 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.
- 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。
理解 Spring 注解编程模型的更多相关文章
- Spring 注解(一)Spring 注解编程模型
Spring 注解(一)Spring 注解编程模型 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 注解系列 ...
- Spring 注解编程之注解属性别名与覆盖
前两篇文章咱聊了深入了解了 Spring 注解编程一些原理,这篇文章我们关注注解属性方法,聊聊 Spring 为注解的带来的功能,属性别名与覆盖. 注解属性方法 在进入了解 Spring 注解属性功能 ...
- 跟Evan学Sprign编程思想 | Spring注解编程模式【译】
Spring注解编程模式 概况 多年来,Spring Framework不断发展对注解.元注解和组合注解的支持. 本文档旨在帮助开发人员(Spring的最终用户以及Spring Framework和S ...
- 深入理解Spring注解机制(一):注解的搜索与处理机制
前言 众所周知,spring 从 2.5 版本以后开始支持使用注解代替繁琐的 xml 配置,到了 springboot 更是全面拥抱了注解式配置.平时在使用的时候,点开一些常见的等注解,会发现往往在一 ...
- Spring 注解编程之模式注解
Spring 框架中有很多可用的注解,其中有一类注解称模式注解(Stereotype Annotations),包括 @Component, @Service,@Controller,@Reposit ...
- Spring Security编程模型
1.采用spring进行权限控制 url权限控制 method权限控制 实现:aop或者拦截器(本质就是之前之后进行控制)--------------------proxy就是 2.权限模型: 本质理 ...
- Spring Cloud Alibaba学习笔记(10) - Spring消息编程模型下,使用RocketMQ收发消息
编写生产者 集成 添加依赖 <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId&g ...
- 深入理解spring注解之@ComponentScan注解
今天主要从以下几个方面来介绍一下@ComponentScan注解: @ComponentScan注解是什么 @ComponentScan注解的详细使用 1,@ComponentScan注解是什么 其实 ...
- Spring 注解(二)注解工具类 AnnotationUtils 和 AnnotatedElementUtils
Spring 注解(二)注解工具类 AnnotationUtils 和 AnnotatedElementUtils Spring 系列目录(https://www.cnblogs.com/binary ...
随机推荐
- Go最火的Gin框架简单入门
Gin 介绍 Gin 是一个 Golang 写的 web 框架,具有高性能的优点,,基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务.官方地址:htt ...
- csdn论坛页抓取
抓取csdn论坛 实现功能 获取论坛分类所有链接,并拼接成推荐精华页的完成的链接 获取推荐精华页的帖子状态,赏分,帖子标题,作者,发布时间,回复量,查看量,最后发表时间 置顶内容不爬取,只打印置顶内容 ...
- STM32F0系列芯片SPI发送一字节数据却输出16个CLK时钟的解决办法
问题 上一个项目在用寄存器操作STM32F0芯片的SPI_DR寄存器的时候,发现一个问题: 我给DR寄存器赋值一个uint8_t一字节大小的数据,SPI引脚能正确输出数据和时钟,但前面八位正确的数据输 ...
- [Python] 数据结构--实现顺序表、链表、栈和队列
说明: 本文主要展示Python实现的几种常用数据结构:顺序表.链表.栈和队列. 附有实现代码. 来源主要参考网络文章. 一.顺序表 1.顺序表的结构 一个顺序表的完整信息包括两部分,一部分是表中元素 ...
- Fire Balls 05——子弹的命中及后续效果
版权申明: 本文原创首发于以下网站: 博客园『优梦创客』的空间:https://www.cnblogs.com/raymondking123 优梦创客的官方博客:https://91make.top ...
- ValueError: Error when checking input: expected input_1 to have 2 dimensions, but got array with shape (100, 100, 100, 3)
报错 Traceback (most recent call last): File "D:/PyCharm 5.0.3/WorkSpace/3.Keras/1.Sequential与Mod ...
- Python 基础 (三)
字典排序 dict = {'a':1,'c':3,'b':2} 字典没有sort方法,可使用sorted排序,默认通过key排序 dict = sorted(dict),print(dict)key的 ...
- Mysql高手系列 - 第5天:DML操作汇总,确定你都会?
这是Mysql系列第5篇. 环境:mysql5.7.25,cmd命令中进行演示. DML(Data Manipulation Language)数据操作语言,以INSERT.UPDATE.DELETE ...
- Codeforces Round #461 (Div. 2)B-Magic Forest+位运算或优雅的暴力
Magic Forest 题意:就是在1 ~ n中找三个值,满足三角形的要求,同时三个数的异或运算还要为0: , where denotes the bitwise xor of integers ...
- codeforces 794 C. Naming Company(贪心)
题目链接:http://codeforces.com/contest/794/problem/C 题意:有两个人每个人都有一个长度为n的字符串,两人轮流拿出一个字符串,放在一个长度为n的字符串的指定位 ...