Spring 注解属性别名与覆盖
在Spring 中将多个注解组合注解到一个注解上,这个注解就可以发挥被注解的多个注解功能。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}
一直以为这是Java注解自带的功能, 但是发现这是Spring封装后的功能;
@SpringBootApplication
public class AnnotatedElementUtilsTest {
public static void main(String[] args) {
Annotation[] annotations = AnnotatedElementUtilsTest.class.getAnnotations();
for(Annotation annotation : annotations) {
System.err.println(annotation.annotationType());
}
}
}
最后发现打印结果只有interface org.springframework.boot.autoconfigure.SpringBootApplication
。
@SpringBootApplication
public class AnnotatedElementUtilsTest {
public static void main(String[] args) {
MergedAnnotations annotations = MergedAnnotations.from(AnnotatedElementUtilsTest.class, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none());
annotations.stream().map(MergedAnnotation::synthesize).forEach(k -> {
System.err.println(k.annotationType());
});
}
}
打印结果为:
interface org.springframework.boot.autoconfigure.SpringBootApplication
interface org.springframework.boot.SpringBootConfiguration
interface org.springframework.boot.autoconfigure.EnableAutoConfiguration
interface org.springframework.context.annotation.ComponentScan
interface org.springframework.context.annotation.Configuration
interface org.springframework.boot.autoconfigure.AutoConfigurationPackage
interface org.springframework.context.annotation.Import
interface org.springframework.stereotype.Component
interface org.springframework.context.annotation.Import
interface org.springframework.stereotype.Indexed
可以看出, Spring 会把所有注解都找到。
那么,组合注解之间如何传值的呢?比如,一个类标有SpringBootApplication注解,属性只能填到SpringBootApplication注解相应字段中,那SpringBootApplication注解属性值如何传到SpringBootConfiguration,EnableAutoConfiguration,ComponentScan等其他注解中呢?
组合注解的传值主要通过注解属性别名与覆盖机制实现。
通过org.springframework.core.annotation.AliasFor可以将注解中一个属性声明成另外一个属性的别名。
例如,定义注解MarkAnnotation:
public @interface MarkAnnotation {
String[] value() default {};
@AliasFor("value")
String[] path() default {};
}
@MarkAnnotation(value="k")
public class AnnotatedElementUtilsTest {
public static void main(String[] args) {
MarkAnnotation markAnnotation = AnnotatedElementUtils.findMergedAnnotation(AnnotatedElementUtilsTest.class, MarkAnnotation.class);
System.err.println(markAnnotation.value()[0]); // k
System.err.println(markAnnotation.path()[0]); // k
}
}
但是,注解属性之间互为别名需要满足一下条件:
- 属性类型相同
- 属性方法必须存在默认值
- 属性默认值必须相同
否则,运行过程中会报错。
去掉MarkAnnotation注解value属性的默认值,再次运行以上代码,会报Exception in thread "main" org.springframework.core.annotation.AnnotationConfigurationException: Misconfigured aliases: attribute 'path' in annotation [MarkAnnotation] and attribute 'value' in annotation [MarkAnnotation] must declare default values
. 。
那么,注解属性如何覆盖被注解注解属性呢?
属性覆盖可以分为三类:
- 隐式覆盖(Implicit Overrides)
- 显示覆盖(Explicit Overrides)
- 传递式显式覆盖(Transitive Explicit Overrides)
隐式覆盖
当一个注解 @One 被元注解 @Two 标注,两个注解存在同样的属性方法 name。@Two#name 将会被 @One#name 属性覆盖。
再定义注解MarkedAnnotation,其具有和MarkAnnotation相同类型及默认值的属性path, 并注解MarkAnnotation。
@MarkAnnotation
public @interface MarkedAnnotation {
String[] path() default {};
}
如果一个类标有MarkedAnnotation, 通过以上内容我们知道,这个类会获得MarkAnnotation,且其path属性的值和MarkedAnnotation的path属性值相同。
@MarkedAnnotation(path="k")
public class AnnotatedElementUtilsTest {
public static void main(String[] args) {
MarkAnnotation markAnnotation = AnnotatedElementUtils.findMergedAnnotation(AnnotatedElementUtilsTest.class, MarkAnnotation.class);
System.err.println(markAnnotation.path()[0]); // k
}
}
显示覆盖
AliasFor除了可以在同一个注解之间声明别名, 还可以将一个注解的属性声明为另一个注解属性的别名。
当一个注解 @One 被元注解 @Two 标注,同时在@one中存在属性nameA使用AliasFor声明为注解@Two注解中nameB的别名,那么@Two#nameB 将会被 @One#nameA 属性覆盖。
修改MarkedAnnotation注解如下:
@MarkAnnotation
public @interface MarkedAnnotation {
@AliasFor(annotation=MarkAnnotation.class, value="path")
String[] url() default {};
}
@MarkedAnnotation(url="k")
public class AnnotatedElementUtilsTest {
public static void main(String[] args) {
MarkAnnotation markAnnotation = AnnotatedElementUtils.findMergedAnnotation(AnnotatedElementUtilsTest.class, MarkAnnotation.class);
System.err.println(markAnnotation.path()[0]); // k
}
}
传递式显式覆盖
如果注解 @One#name 显示覆盖了 @Two#nameAlias,而 @Two#nameAlias 显示覆盖了 @Three#nameAlias,最后因为传递性,@One#name 实际覆盖了@Three#nameAlias。
新增注解BaseAnnotation
@MarkedAnnotation
public @interface BaseAnnotation {
@AliasFor(annotation=MarkedAnnotation.class, value="url")
String[] value() default {};
}
@BaseAnnotation("k")
public class AnnotatedElementUtilsTest {
public static void main(String[] args) {
MarkAnnotation markAnnotation = AnnotatedElementUtils.findMergedAnnotation(AnnotatedElementUtilsTest.class, MarkAnnotation.class);
System.err.println(markAnnotation.path()[0]); // k
MarkedAnnotation markedAnnotation = AnnotatedElementUtils.findMergedAnnotation(AnnotatedElementUtilsTest.class, MarkedAnnotation.class);
System.err.println(markedAnnotation.url()[0]); // k
}
}