一、@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​​标签的传递性也是在这里体现;如果不相同,直接抛出异常;否则正常返回属性值;