1. 什么是组合注解?
在spring中,有一类特别的注解:组合注解。举例来说,springmvc中,@Controller注解用来配置访问路径等,@ResponseBody注解用来表明不做视图渲染,直接展示方法的运行结果(一般是转成json返回),而@RestController组合了两者的功能,可以配置访问路径,同时也可以直接展示方法的运行结果。实际上,如果我们自己写一个注解,像这样:
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface MyComponent {@AliasFor(annotation = Component.class)String value() default "";}
然后这样使用:
@MyComponent("beanObj3")public class BeanObj3 {...}
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了,我们先来看看这个类:

可以看到,SimpleMetadataReaderFactory的类主要分为两部分:
- 构造方法
- 资源的获取
这里我们直接看获取的获取,也就是getMetadataReader(...)方法:getMetadataReader(Resource resource): 根据Resource读取数据 。返回MetadataReader。getMetadataReader(String className): 根据类名读取数据,传入的是全限定类名(即“包名.类名”)
public MetadataReader getMetadataReader(Resource resource) throws IOException {return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());}
3.2、MetadataReader
MetadataReader是个接口(这里返回的具体类型就是SimpleMetadataReader了),里面就3个方法:
getResource(): 获取资源getClassMetadata(): 获取类的元数据getAnnotationMetadata(): 获取注解的元数据
由于是获取注解的信息,这里我们只关注getAnnotationMetadata()方法:
public AnnotationMetadata getAnnotationMetadata() {return this.annotationMetadata;}
3.3、AnnotationMetadata#SimpleAnnotationMetadata
在spring包扫描阶段,读取类上的注解时,使用的都是**SimpleAnnotationMetadataReadingVisitor**,因为此时类并没有加载到jvm,如果使用**StandardAnnotationMetadata**读取,就会导致类提前加载。类提前加载有什么问题呢?java类是按需加载的,有的类可能在整个jvm生命周期内都没用到,如果全都加载了,就白白浪费内存了。
先来看看它实现类 SimpleAnnotationMetadata 的方法:
这些方法分为两类:
getXxx(...):根据注解获取对应的信息hasXxx(...):判断是否包含某注解
如果进一步看这几个方法的默认实现,发现都调用getAnnotations()方法:
public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {default Set<String> getAnnotationTypes() {// 调用了 getAnnotations()return getAnnotations().stream().filter(MergedAnnotation::isDirectlyPresent).map(annotation -> annotation.getType().getName()).collect(Collectors.toCollection(LinkedHashSet::new));}
再进一步查看getAnnotations()方法,进入了AnnotatedTypeMetadata:
public interface AnnotatedTypeMetadata {/*** 获取注解*/MergedAnnotations getAnnotations();}// SimpleAnnotationMetadata# getAnnotationspublic MergedAnnotations getAnnotations() {return this.annotations;}
这一样看,似乎注解得到的终极类就是MergedAnnotations了?我们继续探索。
3.4、MergedAnnotations主要的操作API接口
MergedAnnotations 才是最终的组合注解的集合了,我们来看看它的几个方法:
// 判断注解是否存在,会从所有的注解中判断<A extends Annotation> boolean isPresent(Class<A> annotationType);// 判断注解是否存在,会从所有的注解中判断,与上面的方法不同的是,这里传入的是字符串boolean isPresent(String annotationType);// 判断直接注解是否存在,也就是只判断当前类上有没有该注解,不判断注解的注解<A extends Annotation> boolean isDirectlyPresent(Class<A> annotationType);// 功能同上,这里传入的类型是字符串,格式为"包名.类名"boolean isDirectlyPresent(String annotationType);// 获取注解<A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType);// 获取注解,这里传入的类型是字符串,格式为"包名.类名"<A extends Annotation> MergedAnnotation<A> get(String annotationType);
从方法上大致可以看出,MergedAnnotations是组合注解的集合,提供的注解可以判断某注解是否存在,也可以获取其中的某个注解。
3.5、MergedAnnotation接口
MergedAnnotations 是注解的集合,那这个集合中放的是啥呢?从它的get(…)方法来看,它存放的是MergedAnnotation,我们再来看看MergedAnnotation支持的方法:
从以上的方法可以看到,MergedAnnotation 就是注解的数据抽象,它提供了丰富的api用来获取注解的数据。MergedAnnotation 类应该是在解析类的BeanDefinition阶段就完成了。
4、代码示例
// 得到 SimpleMetadataReaderFactory 实例,最终调用的是 SimpleAnnotationMetadataReadingVisitor 来读取SimpleMetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory();MetadataReader metadataReader = readerFactory.getMetadataReader(BeanObj3.class.getName());AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
5、AnnotationAttr
补充说明下AnnotationAttributes:
public class AnnotationAttributes extends LinkedHashMap<String, Object> {...}

从这里不难看出,AnnotationAttributes就是包含注解所有属性值的map,key为属性名,value为属性值。
6. spring提供的注解工具类
获取注解的属性步骤比较多,聪明如你,就想到可以将这些步骤封装到一个方法中进行处理,spring也是这么做的,这就得介绍spring中与注解相关的两个类:AnnotationUtils与AnnotatedElementUtils。AnnotationUtils是直接获取注解的值,不会处理属性覆盖,而AnnotatedElementUtils会处理属性覆盖。
AnnotationUtils/AnnotatedElementUtils与上面介绍的SimpleAnnotationMetadataReadingVisitor/StandardAnnotationMetadata是何关系呢?
在我们使用SimpleAnnotationMetadataReadingVisitor/StandardAnnotationMetadata时,我们需要得到MergedAnnotations再进行一系列操作(判断注解是否存在、获取注解的属性值等),如果进入AnnotationUtils/AnnotatedElementUtils的源码,就会发现它们的相关方法也是操作MergedAnnotations类
6.1、AnnotationUtils
// 在 BeanObj3 获取 @ComponentAnnotation annotation = AnnotationUtils.getAnnotation(BeanObj3.class, Component.class);// 获取 AnnotationAttributesAnnotationAttributes annotationAttributes= AnnotationUtils.getAnnotationAttributes(BeanObj3.class, annotation);// 获取 annotationAttributeMapMap<String, Object> annotationAttributeMap = AnnotationUtils.getAnnotationAttributes(annotation);// 获取value的值Object value = AnnotationUtils.getValue(annotation, "value");
从结果来看,直接通过 AnnotationUtils.getAnnotation(...) 也是能获取到@Component注解的,尽管BeanObj3并没有直接标记@Component.需要注意的是,这样获取到的@Component的value值是”123”,并不是@MyComponent设置的beanObj3,这也证明了AnnotationUtils获取属性值时并不进行属性覆盖操作。
