1. 什么是组合注解?
在spring中,有一类特别的注解:组合注解。举例来说,springmvc中,@Controller
注解用来配置访问路径等,@ResponseBody
注解用来表明不做视图渲染,直接展示方法的运行结果(一般是转成json返回),而@RestController
组合了两者的功能,可以配置访问路径,同时也可以直接展示方法的运行结果。实际上,如果我们自己写一个注解,像这样:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @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# getAnnotations
public 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 获取 @Component
Annotation annotation = AnnotationUtils.getAnnotation(BeanObj3.class, Component.class);
// 获取 AnnotationAttributes
AnnotationAttributes annotationAttributes
= AnnotationUtils.getAnnotationAttributes(BeanObj3.class, annotation);
// 获取 annotationAttributeMap
Map<String, Object> annotationAttributeMap = AnnotationUtils.getAnnotationAttributes(annotation);
// 获取value的值
Object value = AnnotationUtils.getValue(annotation, "value");
从结果来看,直接通过 AnnotationUtils.getAnnotation(...)
也是能获取到@Component
注解的,尽管BeanObj3
并没有直接标记@Component.
需要注意的是,这样获取到的@Component
的value值是”123”,并不是@MyComponent
设置的beanObj3
,这也证明了AnnotationUtils获取属性值时并不进行属性覆盖操作。