1. 什么是组合注解?

在spring中,有一类特别的注解:组合注解。举例来说,springmvc中,@Controller注解用来配置访问路径等,@ResponseBody注解用来表明不做视图渲染,直接展示方法的运行结果(一般是转成json返回),而@RestController组合了两者的功能,可以配置访问路径,同时也可以直接展示方法的运行结果。实际上,如果我们自己写一个注解,像这样:

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface MyComponent {
  6. @AliasFor(annotation = Component.class)
  7. String value() default "";
  8. }

然后这样使用:

  1. @MyComponent("beanObj3")
  2. public class BeanObj3 {
  3. ...
  4. }

spring 依然会把BeanObj3初始化为spring bean

那么spring是如何做到这一步的呢?实际上,spring在处理@MyComponent时,会判断该注解中是否包含@Component注解,如果包含,就获取该注解的配置,然后按@Component的处理逻辑来进行处理。
同样地,spring在处理@RestController时,如果当前是处理@Controller的逻辑,就从@RestController中获取@Controller的配置然后进行处理,如果当前是处理@ResponseBody逻辑,就从@RestController中获取@ResponseBody的配置然后进行处理。

3. spring 读取注解信息

spring 5.2 中,对于注解信息的读取有提供了三个类:

  • AnnotationMetadataReadingVisitor:注解数据的读取类,基于asm实现,不过在spring5.2中已经废弃(标记了@Deprecated),建议使用SimpleAnnotationMetadataReadingVisitor,因此本文不作分析
  • SimpleAnnotationMetadataReadingVisitor:注解数据的读取类,基于asm实现,spring 5.2 中新增的类,用于替代AnnotationMetadataReadingVisitor,需要注意的是,SimpleAnnotationMetadataReadingVisitor的访问级别是默认的,无法在所在包之外访问,同时它也是final的,不能被继承,因此我们无法直接操作它,不过spring提供了一个类:SimpleMetadataReaderFactory,通过它就可以使用SimpleAnnotationMetadataReadingVisitor
  • StandardAnnotationMetadata:注解数据的读取类,基于反射实现

3.1、SimpleMetadataReaderFactory
spring 并没有提供直接操作SimpleAnnotationMetadataReadingVisitor的机会,而是封装到 SimpleMetadataReaderFactory了,我们先来看看这个类:

17-spring源码-组合注解的处理 - 图1
可以看到,SimpleMetadataReaderFactory的类主要分为两部分:

  1. 构造方法
  2. 资源的获取

这里我们直接看获取的获取,也就是getMetadataReader(...)方法:
getMetadataReader(Resource resource): 根据Resource读取数据 。返回MetadataReader
getMetadataReader(String className): 根据类名读取数据,传入的是全限定类名(即“包名.类名”)

  1. public MetadataReader getMetadataReader(Resource resource) throws IOException {
  2. return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
  3. }

3.2、MetadataReader

MetadataReader是个接口(这里返回的具体类型就是SimpleMetadataReader了),里面就3个方法:

  • getResource(): 获取资源
  • getClassMetadata(): 获取类的元数据
  • getAnnotationMetadata(): 获取注解的元数据

由于是获取注解的信息,这里我们只关注getAnnotationMetadata()方法:

  1. public AnnotationMetadata getAnnotationMetadata() {
  2. return this.annotationMetadata;
  3. }

3.3、AnnotationMetadata#SimpleAnnotationMetadata

在spring包扫描阶段,读取类上的注解时,使用的都是**SimpleAnnotationMetadataReadingVisitor**,因为此时类并没有加载到jvm,如果使用**StandardAnnotationMetadata**读取,就会导致类提前加载。类提前加载有什么问题呢?java类是按需加载的,有的类可能在整个jvm生命周期内都没用到,如果全都加载了,就白白浪费内存了。

先来看看它实现类 SimpleAnnotationMetadata 的方法:
17-spring源码-组合注解的处理 - 图2
这些方法分为两类:

  • getXxx(...):根据注解获取对应的信息
  • hasXxx(...):判断是否包含某注解

如果进一步看这几个方法的默认实现,发现都调用getAnnotations()方法:

  1. public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {
  2. default Set<String> getAnnotationTypes() {
  3. // 调用了 getAnnotations()
  4. return getAnnotations().stream()
  5. .filter(MergedAnnotation::isDirectlyPresent)
  6. .map(annotation -> annotation.getType().getName())
  7. .collect(Collectors.toCollection(LinkedHashSet::new));
  8. }

再进一步查看getAnnotations()方法,进入了AnnotatedTypeMetadata:

  1. public interface AnnotatedTypeMetadata {
  2. /**
  3. * 获取注解
  4. */
  5. MergedAnnotations getAnnotations();
  6. }
  7. // SimpleAnnotationMetadata# getAnnotations
  8. public MergedAnnotations getAnnotations() {
  9. return this.annotations;
  10. }

这一样看,似乎注解得到的终极类就是MergedAnnotations了?我们继续探索。

3.4、MergedAnnotations主要的操作API接口

MergedAnnotations 才是最终的组合注解的集合了,我们来看看它的几个方法:

  1. // 判断注解是否存在,会从所有的注解中判断
  2. <A extends Annotation> boolean isPresent(Class<A> annotationType);
  3. // 判断注解是否存在,会从所有的注解中判断,与上面的方法不同的是,这里传入的是字符串
  4. boolean isPresent(String annotationType);
  5. // 判断直接注解是否存在,也就是只判断当前类上有没有该注解,不判断注解的注解
  6. <A extends Annotation> boolean isDirectlyPresent(Class<A> annotationType);
  7. // 功能同上,这里传入的类型是字符串,格式为"包名.类名"
  8. boolean isDirectlyPresent(String annotationType);
  9. // 获取注解
  10. <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType);
  11. // 获取注解,这里传入的类型是字符串,格式为"包名.类名"
  12. <A extends Annotation> MergedAnnotation<A> get(String annotationType);

从方法上大致可以看出,MergedAnnotations是组合注解的集合,提供的注解可以判断某注解是否存在,也可以获取其中的某个注解。

3.5、MergedAnnotation接口

MergedAnnotations 是注解的集合,那这个集合中放的是啥呢?从它的get(…)方法来看,它存放的是MergedAnnotation,我们再来看看MergedAnnotation支持的方法:
17-spring源码-组合注解的处理 - 图3
从以上的方法可以看到,MergedAnnotation 就是注解的数据抽象,它提供了丰富的api用来获取注解的数据。MergedAnnotation 类应该是在解析类的BeanDefinition阶段就完成了。

4、代码示例

  1. // 得到 SimpleMetadataReaderFactory 实例,最终调用的是 SimpleAnnotationMetadataReadingVisitor 来读取
  2. SimpleMetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory();
  3. MetadataReader metadataReader = readerFactory.getMetadataReader(BeanObj3.class.getName());
  4. AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

5、AnnotationAttr

补充说明下AnnotationAttributes:

  1. public class AnnotationAttributes extends LinkedHashMap<String, Object> {
  2. ...
  3. }

17-spring源码-组合注解的处理 - 图4
从这里不难看出,AnnotationAttributes就是包含注解所有属性值的map,key为属性名,value为属性值。

6. spring提供的注解工具类

获取注解的属性步骤比较多,聪明如你,就想到可以将这些步骤封装到一个方法中进行处理,spring也是这么做的,这就得介绍spring中与注解相关的两个类:AnnotationUtilsAnnotatedElementUtilsAnnotationUtils是直接获取注解的值,不会处理属性覆盖,而AnnotatedElementUtils会处理属性覆盖。

AnnotationUtils/AnnotatedElementUtils与上面介绍的SimpleAnnotationMetadataReadingVisitor/StandardAnnotationMetadata是何关系呢?
在我们使用SimpleAnnotationMetadataReadingVisitor/StandardAnnotationMetadata时,我们需要得到MergedAnnotations再进行一系列操作(判断注解是否存在、获取注解的属性值等),如果进入AnnotationUtils/AnnotatedElementUtils的源码,就会发现它们的相关方法也是操作MergedAnnotations

6.1、AnnotationUtils

  1. // 在 BeanObj3 获取 @Component
  2. Annotation annotation = AnnotationUtils.getAnnotation(BeanObj3.class, Component.class);
  3. // 获取 AnnotationAttributes
  4. AnnotationAttributes annotationAttributes
  5. = AnnotationUtils.getAnnotationAttributes(BeanObj3.class, annotation);
  6. // 获取 annotationAttributeMap
  7. Map<String, Object> annotationAttributeMap = AnnotationUtils.getAnnotationAttributes(annotation);
  8. // 获取value的值
  9. Object value = AnnotationUtils.getValue(annotation, "value");

从结果来看,直接通过 AnnotationUtils.getAnnotation(...) 也是能获取到@Component注解的,尽管BeanObj3并没有直接标记@Component.需要注意的是,这样获取到的@Component的value值是”123”,并不是@MyComponent设置的beanObj3,这也证明了AnnotationUtils获取属性值时并不进行属性覆盖操作