重学|Spring 注解编程模型

Spring 自 1.x 版本开始逐渐支持注解,到 3.x 版本进入黄金时期,如今 Spring 已经进入到 5.x 的时代。由于 Java 注解本身的限制,例如 注解无法继承,Spring 对注解也进行了增强,Spring Framework 在 Github Wiki 已经详细的介绍了对注解的增强,Spring 称之为 注解编程模型,由于文档已经很详细,因此直接加以翻译,后面的文章会详细介绍底层的实现原理。

文章目录

    • 概览
      • 本文档的目的
      • 非本文档的目的
    • 术语
      • 元注解
      • 模式注解
      • 组合注解
      • 注解的存在
      • 属性别名和覆盖
    • 示例
      • 使用 @AliasFor 定义属性别名
      • Spring Composed 和 Spring Polyglot
    • 常见问题解答
      • 1)`@AliasFor`可以与`@Component`以及`@Qualifier`的`value`属性一起使用吗?
    • 有待讨论的主题

概览 多年以来,Spring 框架不断发展其对注解、元注解、组合注解的支持。本篇文档目的在于帮助开发人员(包括 Spring 的最终用户以及 Spring 框架和 Spring 组合项目的开发者)开发和使用注解。
参见:MergedAnnotation API 内部
本文档的目的
本文档的目的主要包括以下的说明:
  • 如何在 Spring 中使用注解。
  • 如何开发在 Spring 中使用的注解。
  • Spring 如何查找注解(例如,Spring 的注解搜索算法如何工作)。
非本文档的目的
本文档的目的不是解释 Spring 框架中特定注解的注解的语义或配置选项。对于特定注解的详细信息,鼓励开发人员查阅相应的 Java 文档或本参考手册的适用部分。
术语 元注解
元注解是定义在另一个注解上的注解。因此如果一个注解被另一个注解标注,则它被元标注。例如,任意被定义为可文档化的注解被来自于java.lang.annotation包中的@Documented元标注。
模式注解
模式注解是用于声明组件在应用中扮演角色的注解。例如 Spring 框架中的 @Repository 注解是任意满足仓库的角色或模式(也被称为数据访问对象或 DAO)的标记。
@Component 是 Spring 管理的组件的通用模式。任意被 @Component 标注的组件是组件扫描的候选项。类似的,任意被 @Component 元标注的注解标注的组件也是组件扫码的候选项。例如 @Service 被 @Component 元标注。
Spring的核心提供了一些开箱即用的模式注解,包括但不限于:@Component, @Service, @Repository, @Controller, @RestController, 以及 @Configuration。 @Repository, @Service 等是 @Component 的特例。
组合注解
组合注解是被一个或多个注解元标注的注解,它的目的是组合关联的元注解的行为到单个自定义的注解。例如,名称为 @TransactionalService 的注解被 Spring 的 @Transactional 和 @Service 注解元标注,它是一个组合了 @Transactional 和 @Service 语义的组合注解。从技术上讲,@TransactionalService 也是一个自定义的模式注解。
注解的存在
直接存在、间接存在、存在的术语具有与 Java 8 中的 java.lang.reflect.AnnotatedElement的类级别文档定义的相同的含义。
在 Spring 中,如果一个注解被声明作为存在于元素上的其他注解的元注解,则它被认为在元素上是元存在的。例如上述中的 @TransactionalService,我们可以认为 @Transactional 元存在于任意被 @TransactionalService 直接标注的类上。
属性别名和覆盖
属性别名是从一个注解属性到另一个注解属性的别名。一组别名中的属性可以相互替换,并且被认为是等同的。属性别名可以分为以下几类。
  1. 显式别名:如果一个注解中的两个属性通过 @AliasFor 声明为彼此的别名,则它们是显式别名字。
  2. 隐式别名:如果一个注解中的两个或多个属性通过 @AliasFor 声明为元注解中同一属性的显式重写,则它们是隐式别名。
  3. 可传递的隐式别名:给定一个注解中一个或多个属性,这些属性通过 @AliasFor 声明为元注解中属性的显式重写,如果这些属性根据传递性规则有效的重写了元注解中的相同属性,则它们是可传递的隐式别名。
属性重写是重写(或隐藏)元注解中注解属性的注解属性。属性覆盖可以分为以下几类。
  1. 隐式重写:给定注解 @One 中的属性 A 和注解 @Two 中的属性A,如果 @One 被 @Two 元标注,则 @One 中的属性 A 是完全基于命名约定(两个属性名称都为A)的 @Two 的属性 A 的隐式重写。
  2. 显式重写:如果通过 @AliasFor 将属性A 定义为元注解属性 B 的别名,则 A 是 B 的显式重写。
  3. 可传递的重写:如果 注解 @One 中的属性 A 是注解 @Two 的属性 B 的显式重写,并且 B 是注解 @Three 的属性 C 的显式重写,则 A 是 C 遵循传递性定律的可传递的重写。
示例 Spring 框架和 Spring 组合项目中的许多注解都使用 @AliasFor 来声明属性别名和属性重写。常见的例子包括 Spring MVC 中的 @RequestMapping,@GetMapping 和 @PostMapping 以及 Spring Boot 中的 @SpringBootApplication 和 @SpringBootTest 。
以下各节提供了代码片段来演示这些特性。
使用 @AliasFor 定义属性别名
Spring Framework 4.2 引入了定义和查找注解属性别名的支持。注解 @AliasFor 可用于在单个注解中声明一对别名属性或将自定义组合注解中的一个属性声明为元注解中的属性的别名。
例如,spring-test 模块中的 @ContextConfiguration 声明如下。
public @interface ContextConfiguration {@AliasFor("locations") String[] value() default {}; @AliasFor("value") String[] locations() default {}; // ... }

locations 属性被声明为 value 属性的别名,反之亦然。因此 @ContextConfiguration 中的以下声明是等效的。
@ContextConfiguration("/test-config.xml") public class MyTests { /* ... */ }

@ContextConfiguration(value = "https://www.it610.com/test-config.xml") public class MyTests { /* ... */ }

@ContextConfiguration(locations = "/test-config.xml") public class MyTests { /* ... */ }

类似的,覆盖来自元注解属性的组合注解可以使用 @AliasFor 精确地控制在注解层次结构中哪些属性可以被重写。事实上,可以为元注解的 value 属性声明别名。
例如,可以开发一个如下所示使用自定义属性的组合注解。
@ContextConfiguration public @interface MyTestConfig {@AliasFor(annotation = ContextConfiguration.class, attribute = "value") String[] xmlFiles(); // ... }

上面的例子演示了开发人员如何实现它们自己自定义的组合注解。而下面的例子说明 Spring 本身在许多核心注解中使用了这个特性。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @RequestMapping(method = RequestMethod.GET) public @interface GetMapping { /** * Alias for {@link RequestMapping#name}. */ @AliasFor(annotation = RequestMapping.class) String name() default ""; /** * Alias for {@link RequestMapping#value}. */ @AliasFor(annotation = RequestMapping.class) String[] value() default {}; /** * Alias for {@link RequestMapping#path}. */ @AliasFor(annotation = RequestMapping.class) String[] path() default {}; // ... }

Spring Composed 和 Spring Polyglot
Spring Composed 项目是 Spring Framework 4.2.1 和更高版本中使用的一系列组合注解。你可以找到像 @Get, @Post, @Put 和 @Delete 等注解,它们是 @GetMapping, @PostMapping, @PutMapping, 以及 @DeleteMapping 注解灵感的来源,这些注解现在已经成为 Spring MVC 和 Spring WebFlux 的一部分。
随时查看 spring-composed 以获得更多的示例和灵感,了解如何实现自己定制的组合注解,以及一些极客的幽默和娱乐,它们进一步证实了 @AliasFor 的功能,请参阅 Spring Polyglot。
常见问题解答 1)@AliasFor可以与@Component以及@Qualifiervalue属性一起使用吗?
【重学|Spring 注解编程模型】简短的回答是:不可以。
@Qualifier 和模式注解(例如,@Component, @Repository, @Controller 以及自定义的模式注解)中的 value 属性不受 @AliasFor 的影响。原因是这些 value 属性的特殊处理在 @AliasFor 发明几年前就存在了。由于向后兼容性问题,无法将 @AliasFor 用于 value 属性。
有待讨论的主题
  • 在类、接口、方法、字段、方法参数、注解上的注解或元注解的通用搜索算法的文档。
    • 如果注解同时存在于元素和元注解上将会发生什么?
    • 存在于注解(包括自定义组合注解上的) @Inherited 怎样影响搜索算法?
  • 通过 @AliasFor 配置的注解属性别名的文档支持。
    • 如果属性和它的别名被定义在同一个注解实例(具有相同的值或具有不同的值)将会发生什么?
      • 通常将引发 AnnotationConfigurationException 异常。
  • 组合注解的文档支持。
  • 组合注解中元注解属性覆盖的文档支持。
    • 记录查找属性时使用的算法,特别说明:
      • 基于命名约定的隐式映射(即组合注解中声明的属性名称和类型与覆盖的元注解中的属性名称和类型完全一致)。
      • 使用 @AliasFor 显式映射。
    • 如果在注解层次中的某处声明了注解和它的别名之一将会发生什么?哪一个优先。
    • 通常来说,如何解决涉及注解属性的冲突?

    推荐阅读