1、概览 {#1概览}
本文将会带你了解 Spring 中的 @AliasFor
注解。
首先介绍一些框架中的使用示例,最后看看如何在自定义注解中使用 @AliasFor
。
2、注解 {#2注解}
@AliasFor 自 4.2 版起成为框架的一部分。多个 Spring 核心注解已更新为包含此注解。
可以将其用于装饰单个注解中的属性,或者在由元注解组成的注解中使用。元注解是可以应用于其他注解的注解。
在同一个注解中,使用 @AliasFor
来声明属性的别名,这样就可以交替使用它们。或者,可以在组合注解中使用 @AliasFor
来覆盖元注解中的属性。换句话说,当使用 @AliasFor
在组合注解中装饰属性时,它会覆盖其元注解中的指定属性。
许多核心 Spring 注解,如 @Bean
、@ComponentScan
、@Scope
、@RequestMapping
和 @RestController
现在都使用 @AliasFor
来配置其内部属性别名。
@AliasFor
注解的定义如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
Class<? extends Annotation> annotation() default Annotation.class;
}
既可以隐式地使用这个注解,也可以显式地使用它。隐式使用仅限于注解内部的别名。相比之下,显式使用还可以用于元注解中的属性。
3、注解中的显示别名 {#3注解中的显示别名}
参考 Spring 的核心注解 @ComponentScan
:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
...
}
可以看到,value
在这里被明确定义为 basePackages
的别名,反之亦然。这意味着可以交替使用它们。
因此,下面这两种用法实际上是一样的:
@ComponentScan(basePackages = "com.baeldung.aliasfor")
@ComponentScan(value = "com.baeldung.aliasfor")
此外,由于这两个属性都被标记为 default
,所以可以使用简写:
@ComponentScan("com.baeldung.aliasfor")
另外,对于这种情况,Spring 还有一些实现要求。别名属性应声明相同的默认值和返回类型。否则,框架就会抛出 AnnotationConfigurationException
异常。
4、显式地为元注解中的属性设置别名 {#4显式地为元注解中的属性设置别名}
接着,来看一个元注解的示例。
使用框架的 @RequestMapping
注解作为元注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
...
}
创建一个组合注解 @MyMapping
,使用框架的 @RequestMapping
注解作为元注解:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping
public @interface MyMapping {
@AliasFor(annotation = RequestMapping.class, attribute = "method")
RequestMethod[] action() default {};
}
你可以看到,在 @MyMapping
中,action
是 @RequestMapping
中属性 method
的显式别名。也就是说,组合注解中的 action
重写了元注解中的 method
。
与注解中的别名类似,元注解属性别名也必须具有相同的返回类型。例如,在本例中就是 RequestMethod[]
。另外,属性 annotation
应引用元注解,在本例中就是 annotation = RequestMapping.class
。
接下来,创建一个名为 MyMappingController
的 Controller 类,使用自定义注解来进行演示。
如下,这里只为 @MyMapping
添加两个属性:route
和 action
:
@Controller
public class MyMappingController {
@MyMapping(action = RequestMethod.PATCH, route = "/test")
public void mappingMethod() {}
}
最后,测试这个 Controller,看看自定义的组合注解是否生效:
@Test
public void givenComposedAnnotation_whenExplicitAlias_thenMetaAnnotationAttributeOverridden() {
for (Method method : controllerClass.getMethods()) {
if (method.isAnnotationPresent(MyMapping.class)) {
MyMapping annotation = AnnotationUtils.findAnnotation(method, MyMapping.class);
RequestMapping metaAnnotation =
AnnotationUtils.findAnnotation(method, RequestMapping.class);
assertEquals(RequestMethod.PATCH, annotation.action()[0]);
assertEquals(0, metaAnnotation.method().length);
}
}
}
测试通过,自定义注解的 action
属性成功地覆盖了元注解 @RequestMapping
的 method
属性。
5、注解中的隐式别名 {#5注解中的隐式别名}
为了理解这一点,在 @MyMapping
中添加一些别名:
@AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] value() default {};
@AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] mapping() default {};
@AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] route() default {};
在这种情况下,value
、mapping
和 route
是 @RequestMapping
中 path
的显式元注解重载。因此,它们也是彼此的隐式别名。换句话说,对于 @MyMapping
,可以交替使用这三个属性。
还是使用上节中的 Controller 进行测试:
@Test
public void givenComposedAnnotation_whenImplictAlias_thenAttributesEqual() {
for (Method method : controllerClass.getMethods()) {
if (method.isAnnotationPresent(MyMapping.class)) {
MyMapping annotationOnBean =
AnnotationUtils.findAnnotation(method, MyMapping.class);
assertEquals(annotationOnBean.mapping()[0], annotationOnBean.route()[0]);
assertEquals(annotationOnBean.value()[0], annotationOnBean.route()[0]);
}
}
}
注意,没有在 Controller 方法的注解中定义 value
和 mapping
属性。不过,它们仍然隐式地携带与 route
相同的值。
6、总结 {#6总结}
本文介绍了 Spring 框架中 @AliasFor
注解的显式和隐式使用场景,以及如何在自定义注解中使用 @AliasFor
注解来覆盖元注解或当前注解中的属性。
Ref:https://www.baeldung.com/spring-aliasfor-annotation