1.Spring当中原生的Bean导入的注解
Spring当中,最基础的注解是@Component
注解,标注这个注解的类将会被Spring扫描并加入到BeanFactory当中。
对于Spring当中的另外的一些注解,包括@Service
/@Controller
/@Repository
/Configuration
等一系列注解,都是因为它们间接标注了@Component
直接,所以也能被Spring扫描并加入到BeanFactory当中。
那么,你是否有想过,为什么标注@Configuration
注解,为什么就能实现间接@Component
注解的相关的信息?
// SearchApplication
@Configuration("app")
@FooAnnotation
@FooAnnotation2
class SearchApplication {
}
Spring当中的@Configuration
注解的定义如下,定义当中确实有标注@Component
注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
}
如果你尝试使用下面的代码,去获取@Component
注解,你会发现无法获取到,获取到的是null
。
// component=null
Component component = SearchApplication.class.getAnnotation(Component.class);
但是Spring当中却可以获取到@Component
注解的相关属性信息,你通过@Configuration
去配置的beanName是可以生效的。
原因在于Spring当中自己实现了MergedAnnotations
和MergedAnnotation
的概念,专门用来处理这种间接注解(Meta元注解)的情况。
2.MergedAnnotation和MergedAnnotations
2.1 MergedAnnotation相关API的使用
对于MergedAnnotations和MergedAnnotation的使用方式参考如下:
// SearchApplication
@Configuration("app")
@FooAnnotation
@FooAnnotation2
class SearchApplication {
}
// usage
MergedAnnotations mergedAnnotations = MergedAnnotations.from(SearchApplication.class);
MergedAnnotation<Component> mergedAnnotation = mergedAnnotations.get(Component.class);
可以基于MergedAnnotations.from
相关的API,去构建一个MergedAnnotations
,Spring当中的一个MergedAnnotations
用来维护一个方法/字段/构造器上标注的所有的注解信息。
基于Spring提供的MergedAnnotation
的相关的实现, 如果我们想要去获取到SearchApplication
这个Bean的beanName,我们只需要使用以下的代码即可获取到。
// value=app
String value = mergedAnnotation.getString("value")
上面的代码当中,需要手动通过MergedAnnotation
的相关的API去操作注解。
也许你会认为并不是那么直观,还是想要通过原生的@Component
注解去调用相关属性方法去进行获取,因此Spring还提供了synthesize方法直接拿到Spring合成的注解@Component
,通过返回的@Component
注解再尝试去获取value
属性,依旧可以正常到。
final Component synthesizedComponent = mergedAnnotation.synthesize();
// value=app
String value = synthesizedComponent.value();
我们以下面的SearchApplication
类的注解结构进行说明MergedAnnotation
和MergedAnnotation
是以什么样的方式存在的。
|---@Annotation1
|
|-----@FooAnnotation----------@Annotation2
|
|
SearchApplication------@Configuration----------@Component
|
|
|-----@FooAnnotation2---------@Annotation3
|
|---@Annotation4
对于@FooAnnotation
、@Configuration
、@FooAnnotation2
三个注解,分别形成了一个注解的继承树。
我们以@Configuration
注解为例,它将会以@Configuration
注解作为root节点,向下级节点使用BFS广度优先遍历的方式进行遍历,形成一棵多叉树的结构,对于树当中一个节点将会使用AnnotationTypeMapping
进行描述,对于这棵继承树则将会使用AnnotationTypeMappings
进行描述。并且对于AnnotationTypeMappings
当中的每个AnnotationTypeMapping
节点,都会持有父节点的引用,也会持有root节点的引用。
我们通过下面这样的代码就可以自动构建出来这样的注解的继承关系。
MergedAnnotations mergedAnnotations = MergedAnnotations.from(SearchApplication.class);
对于SearchApplication
类上的@FooAnnotation
、@Configuration
、@FooAnnotation2
三个注解都会分别构建出来一棵AnnotationTypeMappings
继承树。
对于之前我们提到的,通过获取@Component
注解,可以获取到@Service
注解当中的配置的beanName,最终就是通过AnnotationTypeMapping
形成的关系去解析得到的。
2.2 通过@AliasFor注解形成alias别名链条aliasedBy
Spring当中提供了@AliasFor
注解去提供属性名之间的映射关系,下面是Spring当中的@Service
注解的实现:
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
@Service
注解的value
属性,通过@AliasFor
注解去指向了@Component
注解的value
属性。
@AliasFor
注解有两个属性,通过这两个属性组合最终确定指向的目标注解的目标属性。(不允许指向属性自身)
- 一个是
annotation
代表指向的目标注解的类,默认不填是代表指向当前注解; - 一个是
attribute
代表指向的目标注解的属性名称,不填代表是指向同名的注解。
通过@Service
标注@Component
注解,并且在属性当中通过@AliasFor
注解,将当前@Service
注解value
属性去指向@Component
注解的value
属性,最终@Service
注解的value
属性,将会被映射到@Component
注解的value
属性当中去。
@Component.value --> @Service.value
在@Service
注解的内部,会维护一个aliasedBy
的Map
,结构是Map<Method, List<Method>>
,Key是指向的注解属性,比如这里的@Component.value
,Value是当前注解的所有当中,指向该属性的属性列表,因为存在有一个注解当中多个属性指向同一个注解。
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
@AliasFor(attribute = "value", annotation = Component.class)
String name() default "";
}
比如如上这样的情况的@Service
注解,它的aliasedBy
维护的情况就是如下这样的结构
@Component.value--->@Service.value
|
--->@Service.name
如果此时还存在有一个@MyService
的注解,定义如下:
@Service
public @interface MyService {
@AliasFor(annotation = Component.class)
String value() default "";
@AliasFor(attribute = "value", annotation = Component.class)
String name() default "";
}
那么它的aliasedBy
的结构如下
@Component.value--->@MyService.value
|
--->@MyService.name
当在一个自定义的业务Service当中去标注@MyService
注解时:
@MyService("foo")
class FooService {
}
当你尝试使用如下的代码尝试去获取@Component
注解的value
属性时:
// usage
MergedAnnotations mergedAnnotations = MergedAnnotations.from(FooService.class);
MergedAnnotation<Component> mergedAnnotation = mergedAnnotations.get(Component.class);
String value = mergedAnnotation.getString("value");
MergedAnnotations
会首先找到@Component
注解的所在位置AnnotationTypeMapping
,接着从继承树关系上去找到@Service
、@MyService
对应的AnnotationTypeMapping
,接着根据@Component.value
从@Service
和@MyService
注解对应的aliasedBy
当中去获取对应的alias关系,最终获取到的映射关系是如下的这个列表,都是候选的@Component.value
属性的数据来源。
// @Component.value属性本身
@Component.value
// @Service注解当中存在的指向@Component.value的属性
@Service.value
@Service.name
// @MyService注解当中存在的指向@Component.value的属性
@MyService.value
@MyService.name
我们可以看到,@Component.value
存在有多个别名属性,存在有这么多个属性值可能作为@Component.value
的数据来源,最终会选取哪个作为最终的数据呢?
- 越接近root节点的属性值优先级越高,上级节点的属性值会覆盖下级节点的属性值,因此会候选使用
@MyService.value
和@MyService.name
这两个属性作为@Component.value
属性的来源。 - 如果出现了同级别的多个属性可以作为候选属性(
@MyService.value
和@MyService.name
),那么选择规则如下:- 对于Spring当中会判断这两个属性值当中,哪个和默认值
defaultValue不一致
则会选择选择这个属性值。 - 如果两个属性值都是默认值,那么按照属性名进行字典序排序进行选择前面的。
- 如果两个属性值都不是默认值,如果这两个属性值一致那么选哪个都无所谓,如果两个属性值不一样,那么会抛出
AnnotationConfigurationException
异常。
- 对于Spring当中会判断这两个属性值当中,哪个和默认值
2.3 aliasMapping和conventionMapping
- aliasMapping,主要用于快速检索当root节点当中存在有对应的Meta注解的
@AliasFor
指向的别名映射的情况,比如上面提到的@Component.value
属性,在根级别注解@MyService
注解当中就能找到对应的属性@MyService.value
(假设@MyService.value
是最优的)。 - conventionMapping,主要针对root节点当中存在有同名的属性时,支持进行快速映射,比如针对
@Service.name
这个属性,不存在有@AliasFor
别名映射,此时可以快速根据name
属性去root节点上去找到@MyService.name
属性去作为@Service.name
的属性(需要注意的是:value属性作为Java注解的默认属性,不支持conventionMapping映射,只能支持aliasMapping映射)。