Spring 注解(一)Spring 注解编程模型

Spring 注解(一)Spring 注解编程模型

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

Spring 注解系列文章:

  1. Spring 注解(一)Spring 注解编程模型
  2. Spring 注解(二)注解工具类 AnnotationUtils 和 AnnotatedElementUtils

一、术语

1.1 元注解

元注解是一种标注在别的注解之上的注解。如果一个注解可以标注在别的注解上,那么这个注解已然是元注解。例如,任何需要被文档化的注解,都应该被 java.lang.annotation 包中的元注解 @Documented 标注。

1.2 Stereotype 注解

译者注:保留 Stereotype 原生词汇;可理解为模式化注解、角色类注解。

Stereotype 注解是一种在应用中,常被用于声明要扮演某种职责或者角色的注解。例如,@Repository 注解用于标注任何履行了 repository 职责角色的类(这种职责角色通常也会被称为 Data Access Object 或者 DAO)。

@Component 是被 Spring 管理的组件的对应注解。任何标注了 @Component 的组件都会在 spring 组件扫描时被扫描到。同样的,任何标注了 被元注解 @Component 标注过的注解 的组件,也会在 Spring 组件扫描时被扫描到。例如,@Service 就是一种被元注解 @Component 标注过的注解。

Spring 核心框架提供了一系列可直接使用的 stereotype 注解,包括但不限于 @Component, @Service, @Repository, @Controller, @RestController, and @Configuration。@Repository, @Service 等等注解都基于 @Component 的更精细化的组件注解。

1.3 组合注解

组合注解是一种被一个或者多个元注解标注过的注解,用以撮合多个元注解的特性到新的注解。例如,叫作 @TransactionalService 的注解就是被 spring 的 @Transactional 和 @Service 共同标注过,而且它把 @Transactional 和 @Service 的特性结合到了一起。另外,从技术上上讲,@TransactionalService 也是一个常见的 Stereotype 注解。

一个注解无论是直接标注还是间接标注一个 bean,这个注解在 java8 的 java.lang.reflect.AnnotatedElement 类注释中所约定的含义和特性都不会有任何改变。

在 Spring 中,如果一个元注解标注了其它注解,其它注解标注了一个 bean,那么我们就说这个元注解 meta-present(间接标注了)这个 bean。以上文提到的 @TransactionalService 为例,我们可以说,@Transactional 间接标注了任何一个标注过 @TransactionalService 的 bean。

二、组合注解实现的基础 @AliasFor

@AliasFor 将注解的一个成员名(Attribute Alias)变为另一个。一个成员有多个别名时,这些别名是平等可交换的。成员别名可以分为以下几种。

  1. Explicit Aliases(显式别名):如果一个注解中的两个成员通过 @AliasFor声明后互为别名,那么它们是明确的别名。
  2. Implicit Aliases(隐式别名):如果一个注解中的两个或者更多成员通过 @AliasFor 声明去覆盖同一个元注解的成员值,它们就是隐含别名。
  3. Transitive Implicit Aliases(传递的隐式别名):如果一个注解中的两个或者更多成员通过 @AliasFor 声明去覆盖元注解中的不同成员,但是实际上因为覆盖的传递性导致最终覆盖的是元注解中的同一个成员,那么它们就是可传递的隐含别名。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {

	@AliasFor("attribute")
	String value() default "";

	@AliasFor("value")
	String attribute() default "";

	Class<? extends Annotation> annotation() default Annotation.class;
}

可以看到 @AliasFor 自身也使用了别名。注意:显示别名必须成对出现。

注意:使用 @AliasFor 标注的有以下限制:一是属性都必须都有默认值且相等;二是属性的返回值类型也必须相等;三是属性双方都必须指定别名,且不能冲突。

2.1 Explicit Aliases(显式别名)

同一注解内两个属性互相声明为别名,如 @AliasFor 自身的 value 和 attribute 属性。同一注解内部可以省略 annotation 属性。

@Retention(RetentionPolicy.RUNTIME)
@interface ContextConfiguration {
    @AliasFor(annotation = ContextConfiguration.class, value = "locations")
    // @AliasFor("locations")
    String value() default "";

    @AliasFor(annotation = ContextConfiguration.class, value = "value")
    // @AliasFor("value")
    String[] locations() default {};
}

2.2 Implicit Aliases(隐式别名)

使用元注解隐式别名。@XmlTestConfig 的 xmlFiles 属性显示的覆盖了 @ContextConfiguration 的 locations 属性。

// 必须显示的标注元注解
@ContextConfiguration
public @interface XmlTestConfig {
	@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
	String[] xmlFiles();
}

可以多个字段同时指定别名,这种情况下 value、groovyScripts、xmlFiles 都互为别名。

@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration
public @interface MyTestConfig {
	@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
	String[] value() default {};

	@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
	String[] groovyScripts() default {};

	@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
	String[] xmlFiles() default {};
}

隐式别名可以不用成对出现。

2.3 Transitive Implicit Aliases(传递的隐式别名)

别名具有传递性。

@Retention(RetentionPolicy.RUNTIME)
@MyTestConfig
public @interface GroovyOrXmlTestConfig {
	@AliasFor(annotation = MyTestConfig.class, attribute = "groovyScripts")
	String[] groovy() default {};

	@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
	String[] xml() default {};
}

2.4 @AliasFor 和 @Inherited 区别

@Inherited 元注解表示被标注过的 class 的子类所继承。 但注意:一是类并不从它所实现的接口继承;二是方法并不从它所重载的方法继承。 更多详见 java @Inherited注解的作用

注意 @AliasFor 和 @Inherited 区别:

  • @Inherited 强调子类可以继承父类的注解信息(不能获取接口上的注解信息)。
  • @AliasFor 通过别名机制更强调两个注解的关系,类似 java 中类的继承。

如 Spring 中 @Controller、@Repository、@Service 都是基于 @Component 派发出了一系列的注解,但语义更强。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
	String value() default "";
}

// @Controller 是基于 @Component 派发出来的
@Component
public @interface Controller {
	@AliasFor(annotation = Component.class)
	String value() default "";
}

@Controller 是比 @Component 语义更强,但由此也带来了一系列的问题,现在我需要处理 2 个注解了,以前我只有处理 1 个,怎么解决这个问题呢? @AliasFor 就是用来解决这个问题的,可以只用处理 @Component 注解就将其派发的一系列注解也同时处理了。

三、AnnotationUtils 和 AnnotatedElementUtils

注意直接使用 JDK 的注解是不带有语义的,需要使用 Spring 提供的 AnnotationUtils 或 AnnotatedElementUtils 工具类。

  • AnnotationUtils 解决注解别名,包括显示别名、隐式别名、传递的隐式别名。还可以查的指定注解的属性信息,但不能解决元注解属性覆盖的问题。
  • AnnotatedElementUtils 为 Spring 的元注解编程模型定义了公共 API,并支持注解属性覆盖。如果不需要支持注解属性覆盖,请考虑使用 AnnotationUtils

3.1 AnnotationUtils

用于处理注解,元注解,桥接方法(编译器为通用声明生成)以及超级方法(用于可选注解继承)的常规实用程序方法。请注意,JDK 的内省工具本身并不提供此类的大多数功能。

  • getAnnotations(Method, Class) 在给定类级别查找
  • findAnnotation(Method, Class) 给定方法的整个继承层次结构中的查找查找

(1) 元注解支持

大多数 find() 方法和此类中的一些 get() 方法都支持查找用作元注解的注解。有关详细信息,请参阅此类中每个方法的 javadoc。对于在组合注解中使用属性覆盖的元注解的细粒度支持,请考虑使用 AnnotatedElementUtils 的更具体的方法。

(2) 属性别名

此类中返回注解,注解数组或 AnnotationAttributes 的所有公共方法都透明地支持通过 @AliasFor 配置的属性别名。有关详细信息,请参阅各种 synthesizeAnnotation(..) 方法。

(3) 搜索范围

一旦找到指定类型的第一个注解,此类中的方法使用的搜索算法将停止搜索注解。因此,将默默忽略指定类型的其他注解。

@Controller
public class AliasForTest {
    @Test
    public void test() {
        // 可以获取 @Component
        Assert.assertNotNull(AnnotationUtils.getAnnotation(AliasForTest.class, Component.class));
    }
}

3.2 AnnotatedElementUtils

用于在 AnnotatedElements 上查找注解,元注解和可重复注解的常规实用程序方法。 AnnotatedElementUtils 为 Spring 的元注解编程模型定义了公共 API,并支持注解属性覆盖。 如果您不需要支持注解属性覆盖,请考虑使用 AnnotationUtils。请注意,JDK 的内省工具本身不提供此类的功能。

(1) 查找与获取语义

  • 获取语​​义(Get semantics)仅限于搜索 AnnotatedElement 上存在的注解(即本地声明或继承)或在 AnnotatedElement 上方的注解层次结构中声明的注解。

  • 查找语义(Find semantics)更加详尽,提供了语义加上对以下内容的支持:
    如果带注解的元素是类,则在接口上搜索
    如果带注解的元素是类,则在超类上搜索
    解析桥接方法,如果带注解的元素是方法
    如果带注解的元素是方法,则在接口中搜索方法
    如果带注解的元素是方法,则在超类中搜索方法

(2) 支持 @Inherited

get 语义之后的方法将遵循 Java 的 @Inherited 批注的约定,另外本地声明的注解(包括自定义组合注解)将优于继承注解。相反,查找语义之后的方法将完全忽略 @Inherited 的存在,因为查找搜索算法手动遍历类型和方法层次结构,从而隐式支持注解继承而不需要 @Inherited。

@Test
@GetMapping(value = "/GetMapping", consumes = MediaType.APPLICATION_JSON_VALUE)
public void test() throws NoSuchMethodException {
    Method method = ReflectUtils.findDeclaredMethod(
            AliasForTest.class, "test", null);

    // AnnotationUtils 不支持注解属性覆盖
    RequestMapping requestMappingAnn1 = AnnotationUtils.getAnnotation(method, RequestMapping.class);
    Assert.assertEquals(new String[]{}, requestMappingAnn1.value());
    Assert.assertEquals(new String[]{}, requestMappingAnn1.consumes());

    // AnnotatedElementUtils 支持注解属性覆盖
    RequestMapping requestMappingAnn2 = AnnotatedElementUtils.getMergedAnnotation(method, RequestMapping.class);
    Assert.assertEquals(new String[]{"/GetMapping"}, requestMappingAnn2.value());
    Assert.assertEquals(new String[]{MediaType.APPLICATION_JSON_VALUE}, requestMappingAnn2.consumes());
}

参考:

  1. 《Spring注解编程模型》:http://ifeve.com/annotation-programming-model/

每天用心记录一点点。内容也许不重要,但习惯很重要!

posted on 2019-02-21 21:45  binarylei  阅读(1412)  评论(0编辑  收藏  举报

导航