《SpringBoot系列八》:Spring注解别名@AliasFor和覆盖(含原理)
原创
©著作权归作者所有:来自51CTO博客作者秃秃爱健身的原创作品,请联系作者获取转载授权,否则将追究法律责任
一、@AliasFor概述和使用
所有注解均实现Annotation接口。较底层注解能够覆盖其元注解的同名属性,并且AnnotationAttributes采用注解就近覆盖的设计原则。
覆盖的分类:
- 隐性覆盖:元注解的层次高低关系、Override
- 显性覆盖:当A @AliasFor B时,属性A显性覆盖了属性B的内容。
@AliasFor可建立在不同注解层次的属性之间。
1. 同一注解内显式使用:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
//...
}
value和path互为别名,互为别名的限制条件如下:
- 互为别名的属性 其属性值类型、默认值,都是相同的。
- 互为别名的属性必须定义默认值。
- 互为别名的注解必须成对出现。
2. 显示的覆盖元注解中的属性:
先来看一段代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AopConfig.class)
public class AopUtilsTest {
定义一个标签:
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration
public @interface STC {
@AliasFor(value = "classes", annotation = ContextConfiguration.class)
Class<?>[] cs() default {};
}
- 因为@ContextConfiguration注解被定义为
@Inherited
的,所以@STC注解可理解为继承了@ContextConfiguration
注解; - @STC注解使用cs属性替换@ContextConfiguration注解中的classes属性。
- 使用
@AliasFor
标签,分别设置了value(即作为哪个属性的别名)和annotation(即作为哪个注解);
使用@STC注解替换@ContextConfiguration:
@RunWith(SpringJUnit4ClassRunner.class)
@STC(cs = AopConfig.class)
public class AopUtilsTest {
@Autowired
private IEmployeeService service;
}
3. 注解中隐式声明别名
@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 {};
}
因为value,groovyScripts,xmlFiles都定义了别名@AliasFor
(annotation = ContextConfiguration.class, attribute = “locations”),所以value、groovyScripts和xmlFiles也互为别名。
4. 别名的传递
@AliasFor
注解是允许别名之间的传递的,简单理解,如果A是B的别名,并且B是C的别名,那么A是C的别名。
@MyTestConfig
public @interface GroovyOrXmlTestConfig {
@AliasFor(annotation = MyTestConfig.class, attribute = "groovyScripts")
String[] groovy() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xml() default {};
}
@ContextConfiguration
public @interface MyTestConfig {
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] groovyScripts() default {};
}
- @GroovyOrXmlTestConfig把
@MyTestConfig
作为元注解; - groovy属性作为@MyTestConfig中的groovyScripts属性的别名;
- xml属性作为@ContextConfiguration中的locations属性的别名;
- 因为MyTestConfig中的groovyScripts属性是ContextConfiguration中的locations属性的别名;所以xml属性和groovy属性也互为别名。
二、@AliasFor在SpringBoot源码中的使用
public @interface SpringBootApplication {
....
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
.....
}
@SpringBootApplication(scanBasePackages = {"com.saint.spring.controller"})
@SpringBootApplication的scanBasePackages属性将 @ComponentScan扫描的位置重定向到我们指定的位置;即@SpringBootApplication利用@AliasFor注解别名了@CompoentScan注解的basePackages()属性。
三、@AliasFor实现原理
使用@AliasFor
最需要注意一点的,就是只能使用Spring的AnnotationUtils工具类来获取;因为AliasFor是Spring定义的标签。
- AnnotationUtils工具类中的
<A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement)
方法来处理@AliasFor注解; - 其返回一个经过处理(处理
@AliasFor
标签)之后的注解对象,即把A注解对象处理为支持@AliasFor
的A注解对象;
public static <A extends Annotation> A synthesizeAnnotation(
A annotation, @Nullable AnnotatedElement annotatedElement) {
if (annotation instanceof SynthesizedAnnotation || AnnotationFilter.PLAIN.matches(annotation)) {
return annotation;
}
return MergedAnnotation.from(annotatedElement, annotation).synthesize();
}
- 本质原理就是使用AOP来对A注解对象做动态代理,而用于处理代理的对象为SynthesizedMergedAnnotationInvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) {
if (ReflectionUtils.isEqualsMethod(method)) {
return annotationEquals(args[0]);
}
if (ReflectionUtils.isHashCodeMethod(method)) {
return annotationHashCode();
}
if (ReflectionUtils.isToStringMethod(method)) {
return annotationToString();
}
if (isAnnotationTypeMethod(method)) {
return this.type;
}
if (this.attributes.indexOf(method.getName()) != -1) {
return getAttributeValue(method);
}
throw new AnnotationConfigurationException(String.format(
"Method [%s] is unsupported for synthesized annotation type [%s]", method, this.type));
}
- 再看
getAttributeValue(method)
方法逻辑:
- 首先正常获取当前属性的值;
- 然后得到所有的标记为别名的属性名称;
- 接着遍历获取所有别名属性的值;
- 判断attributeValue、aliasValue、defaultValue是否相同,
@AliasFor
标签的传递性也是在这里体现;如果不相同,直接抛出异常;否则正常返回属性值;