本文基于Springboot 2.1.6.RELEASE 版本分析.
关于@AliasFor
注解,曾提过的一个issue Explicit attribute overrides configured via @AliasFor not supported for components picked up via component scanning, 不过从spring 5.2 版本开始已经被修复了
Spring中@AliasFor
注解的作用主要是将一个注解上的属性值传递给另一个注解或者将同一个注解类的属性设置互为别名.
但这并不是java原生支持的,需要通过Spring中提供的工具类
org.springframework.core.annotation.AnnotationUtils
或者org.springframework.core.annotation.AnnotatedElementUtils
来解析。AnnotatedElementUtils
内部还是调用的AnnotationUtils
同一个注解类的属性设置互为别名
示例: 如下是一个自定义注解,两个属性互为别名
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableCVS {
@AliasFor(value = "address")
String value() default "";
@AliasFor(value = "value")
String address() default "";
}
@Configuration
@EnableCVS(address = "hhh")
public class AppConfig {
}
因为两个属性互为别名,所以在value没有赋值的情况下,通过AnnotationUtils
仍然可以获取到值,而通过java原生的方式则无法获取。
通过上图也可以发现Spring其实是自己实现了jdk动态的拦截器来实现别名功能.
但是有个小小的细节: 如果同时设置address和value的值通过AnnotationUtils#findAnnotation(Class<?>, annotationType)
获取属性值,则会抛出如下异常
org.springframework.core.annotation.AnnotationConfigurationException:
In annotation [com.example.demo.config.EnableCVS] declared on class com.example.demo.config.AppConfig
and synthesized from [@com.example.demo.config.EnableCVS(value=haha, address=hhh)],
attribute 'address' and its alias 'value' are present with values of [hhh] and [haha],
but only one is permitted.
at org.springframework.core.annotation.AbstractAliasAwareAnnotationAttributeExtractor.getAttributeValue(AbstractAliasAwareAnnotationAttributeExtractor.java:103)
at org.springframework.core.annotation.SynthesizedAnnotationInvocationHandler.getAttributeValue(SynthesizedAnnotationInvocationHandler.java:91)
at org.springframework.core.annotation.SynthesizedAnnotationInvocationHandler.invoke(SynthesizedAnnotationInvocationHandler.java:80)
at com.sun.proxy.$Proxy8.address(Unknown Source)
将一个注解上的属性值传递给另一个注解
例如可以自定义一个注解@MyComponentScan
,自定义注解的属性可以传递给Spring中的@ComponentScan
的属性。让自定义注解拥有和@ComponentScan
一样的功能。
除此之外我们还可以通过@Import
注解来实现自己的逻辑,注册自定义bean,让@MyComponentScan
拥有比@ComponentScan
更多的功能。
SpringBoot的启动注解@SpringBootApplication
就是利用了该原理:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
源码及调用栈如下:
@AliasFor
官方文档解释如下:
@AliasFor是一个用于给其它注解属性声明别名的注解。
使用场景
-
给注解中的属性显式指明别名:在单个注释中,可以在一对属性上声明
@AliasFor
,以表示它们是彼此可以互换的别名。 (个人注释:等价的关系,给a赋值,则b也有相同的值,反之亦然,但在使用时不能同时赋值)。 -
给元注释中属性的显式别名:如果
@AliasFor
的注释属性设置为与声明它的注释不同的注释,则该属性将被解释为元注释中属性的别名(即显式元注释属性重写)。这样就可以精确地控制在注解层次结构中覆盖哪些属性。实际上,使用@AliasFor
甚至可以为元注释的value
属性声明别名。 -
注释中的隐式别名:如果注解中的一个或多个属性被声明和同另一注解中的属性相同(直接或传递),则这些属性将被视为彼此的一组隐式别名,作用类似于注释中显式别名的行为。
使用要求
与Java中的任何注释一样,仅仅存在@AliasFor
本身并不能强制实现别名语义。要强制执行别名语义,必须通过AnnotationUtils
中的实用工具方法加载注解。在幕后,Spring会将注解封装在动态代理中来合成注解,动态代理透明地强制使用标记@AliasFor
的注解属性别名语义。类似地,在注释层次结构中使用@AliasFor
时,AnnotatedElementUtils
支持显式元注释属性重写。通常您不需要自己手动合成注释,因为在Spring管理的组件上查找注释时,Spring将透明地为您这样做。
实施要求
注释中的显式别名:
- 构成别名对的每个属性都必须用@AliasFor注释,并且属性或值必须引用别名对中的另一个属性。
- 别名属性必须声明相同的返回类型。
- 别名属性必须声明默认值。
- 别名属性必须声明相同的默认值。
- 不应声明批注。
元批注中属性的显式别名:
- 作为元注释中属性的别名的属性必须使用
@AliasFor
进行注释,并且属性必须引用元注释中的属性。 - 别名属性必须声明相同的返回类型。
- 注释必须引用元注释。
- 引用的元注释必须在声明
@AliasFor
的注释类上存在。
批注中的隐式别名:
- 属于一组隐式别名的每个属性都必须使用
@AliasFor
进行注释,并且属性必须引用同一元注释中的同一属性(直接或通过注释层次结构中的其他显式元注释属性重写进行传递)。 - 别名属性必须声明相同的返回类型。
- 别名属性必须声明默认值。
- 别名属性必须声明相同的默认值。
- 注释必须引用适当的元注释。
- 引用的元注释必须在声明
@AliasFor
的注释类上存在。
示例:注释中的显式别名
在@ContextConfiguration
中,value
和locations
是彼此的显式别名。
public @interface ContextConfiguration {
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
// ...
}
示例:元注释中属性的显式别名
在@XmlTestConfig
中,xmlFiles
是@ContextConfiguration
中locations
的显式别名。换句话说,xmlFiles
覆盖@ContextConfiguration
中的locations
属性。
@ContextConfiguration
public @interface XmlTestConfig {
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xmlFiles();
}
示例:注释中的隐式别名
在@MyTestConfig
中,value
、groovyscript
和xmlFiles
都是@ContextConfiguration
中locations
属性的显式元注解属性重写。因此,这三个属性也是彼此的隐式别名。
@ContextConfiguration
public @interface MyTestConfig {
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] value() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] groovyScripts() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xmlFiles() default {};
}
示例:注释中的传递隐式别名
在@GroovyOrXmlTestConfig
中,groovy
是对@MyTestConfig
中groovyscript
属性的显式重写;然而,xml是@ContextConfiguration中locations属性的显式重写。此外,groovy
和xml
是相互传递的隐式别名,因为它们都有效地重写了@ContextConfiguration
中的locations
属性
@MyTestConfig
public @interface GroovyOrXmlTestConfig {
@AliasFor(annotation = MyTestConfig.class, attribute = "groovyScripts")
String[] groovy() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xml() default {};
}