Java 元注解及 Spring 组合注解应用

Java 1.5(Tiger) 个人认为最为激动人心的两个特性是泛型与注解(Java Versions, Features and History)。泛型自然是不必说了,注解对 Java 世界的改变比泛型伟大的多(现在框架的注解配置),在 Java 1.5 之前我们只能在 Javadoc 注释中做文章,于是只能用 XDoclet 那样不伦不类的东西。Java 的注解发展到现在几乎可以使用在书写代码时的任何地方,见 java.lang.annotation.ElementType 中的类型,囊括了 TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER(since 1.8), TYPE_USE(since 1.8)。

Java 1.5 基本确定了注解的基本框架,包括元注解(meta-annotation); 直到 Java 8 又扩展了注解的使用范围,列举如下:

创建类实例
new@Interned MyObject();

类型映射
myString = (@NonNull String) str;

implements 语句中
class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }

throw exception声明
void monitorTemperature() throws@Critical TemperatureException { ... }

解析前面 ElementType Java 8 增加的 TYPE_PARAMETER和 TYPE_USE 注解使用新场合。ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中。ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(如: 声明语句、泛型和强制转换语句中的类型)

另外就是更方便使用的重复注解 -- @Repeatable

本文不会讲解 Java 注解的基本知识和创建自定的注解,主要关注标题中的 Java 元注解及 Spring 对元注解的广泛应用 -- 即 Spring 组合注解

Java 元注解(meta-annotation)

所谓的元注解(meta-annotation) 也就是注解的注解,注解本身就是元数据,像 meta-data。具体到 Java 的注解就是注解可以应用到别的注解上去,@Target 包含了 ANNOTATION_TYPE。所以在我们定义普通注解时用到的 @Retention, @Target, @Documented, @Inherited, @Repeatable 就是一拨 Java 内置元注解,下面是 @Target 的定义

体验一下 @Inherited 注解的作用

@Inherited 标明注解是能够被传递到子类的,即注解在父类的注解也会作用到它的子类上去,比如 Spring 的 @Transactional 注解就被 @Inherited 标识了

具体表现就是

UserRepository 继承了 BaseRepository, 所以 UserRepository 也就启用了事物。

自己来一下

在注解中如果去掉 @Inherited, 上面的 Child.class.getAnnotation(BB.class) 将返回 null.

要是换成实现一个接口,事情就不一样了

Child 不管是在 @BB 有没有 @Inherited 标识都继承不到 @BB 注解。

@Target({ElementType.ANNOTATION_TYPE})

ElementType.ANNOTATION_TYPE 的用处,好像也就是一个约束,只限定被它声明了注解只能用于其他的注解类型上去,看下面的图片

User 类上的 @BB 处报错: '@BB' not applicable to type. 就是说 @Target 为 ElementType.ANNOTATION_TYPE 的注解只能用于其他注解上,而 @Target 为 ElementType.TYPE 可以用在许多地方,类,接口,枚举或其他注解上。除此之外,ElementType.ANNOTATION_TYPE 也没别的太多意思,它与下面的组合注解没有什么关系。

Spring 中的元注解与组合注解

再重新回味一下,Java 原生的元注解(Meta-annotations) 基本就是指内置的 @Retention, @Target, @Documented, @Inherited, @Repeatable 那一干注解,以及自定义注解加上 @Target({ElementType.ANNOTATION_TYPE}) 实现的自定义元注解。而 Spring 的元注解概念是不一样的,它认为能用于注解的注解就是元注解,即 @Target({ElementType.Type}) 标识的也是元注解,因为其他的注解也是 Type.

Spring 中元注解与组合注解又是很紧密的两个概念,从官方的文档中

由于 @Component 注解到了 @Service 注解,所以这里的 @Component 称之元注解,而 @Service 而称之为组合注解,即组合了 @Component 的注解,当然还能组合更多的元注解。在 Spring 中有大量组合注解的例子,像 @GetMapping

为什么说 @Component 和 @RequestMapping 不是一般意义上 Java 的元注解呢,只要查看下 @Component 的定义就知道,

创建自己的组合注解

比如想要声明一个 Lazy 的 Bean,在 JavaConfig 方法上要同时用到两个注解 @Lazy 和 @Bean

那么我们是否能创建一个组合注解,只用一个注解就能声明出一个 Lazy 的 Bean 来呢,没问题,就是下面的 @LazyBean

然后前面的声明 Bean 的代码就能够写成

节约了一行代码,简洁明了,表现力也增强了。如果需要组合更多的元注解时,代码效果上就会更佳了。

从 Spring 组合注解的结果推导出它的内部实现,被 @LazyBean 标的 bean, 相当于同时被 @Bean 和 @Lazy 标注了。

借机加强理解一下 Spring 中元注解与组合注解的概念:

  1. 这儿的 @Bean 和 @Lazy 是用来注解 @LazyBean 的,所以 @Bean 和 @Lazy  称之为元注解
  2. @LazyBean 是由 @Bean 和  @Lazy 组合而成的,因此 @LazyBean 就是一个组合注解

Spring 的组合注解是如何工作的

如果使用正常的 Java 注解反射 API getAnnotation()isAnnotationPresent() 只能发现组合后的注解 @LazyBean, 而元注解是这样得不到的。但 Spring 在发现注解的时候走入的更深,注解的注解(元注解)和组合后的注解都能捞出来。看下面的例子

上面的 @CC 由 @AA 和 @BB 组合而成,用 @CC 注解到 User 类上,常规的 Java 反射类只能找到 @CC,要反射出 @AA 和 @BB,必须对 @CC 类进一步反射。而 Spring 提供了 AnnotationUtilsAnnotatedElementUtils 工具类来查找注解类,如下

如某个类(User) 被某个组合注解(@CC) 修饰了,Spring 认该类(User) 被组成 @CC 的所有元注解(@AA 和 @BB) 修饰了。

组合注解时的属性值传递与覆盖 

如果组合注解的元注解有属性值时,直接写就行了,例如:

用上面的组合注解 @LazyBean 标注到某个方法上,注册 SpringBean 时的名称就是 @Bean("FixedLazyBean") 中的 "FixedLazyBean"。

假如在使用 @LazyBean 注解时还要能够动态指定 Spring bean 名称,那么 @LazyBean 中就需要一个属性覆盖 @Bean 的 value 属性(或者说传递给 @Bean 的 value 属性),这时修又要用到一个 Spring 特定的注解 @AliasFor -- Spring 4.2 新加的特性(New Features and Enhancements in Spring Framework 4.2)。对合并属性的获得也是用 AnnotatedElementUtils 中的方法 findMergedAnnotationAttributes(...), 还是看例子:

应用 @LazyBean 时指定 Bean name 

这样就会向 Spring 上下文中注册一个名称为 "newName" 的字符串。

Spring 的 @AliasFor 使用时有不少要求,请参见它的 Javadoc 文档 Annotation Type AliasFor. 创建一个组合注解后最好在使用之前测试它是否完全达到预期,尤其是在覆盖注解的 value 属性时要多加留意。

链接:

  1. Java 8 新特性:扩展注解(类型注解和重复注解)
  2. 1.10.2. Using Meta-annotations and Composed Annotations
  3. Implementing custom annotations for Spring MVC
  4. Spring Annotation Programming Model
  5. Spring 4 Meta Annotations

本文链接 https://yanbin.blog/java-spring-meta-annotation/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments