1. 概述

在本教程中, 我们将了解 Spring 中的 @AliasFor 注解

首先,我们将看到使用它的框架内的示例。接下来,我们将看一些定制示例。

2. 注释

@AliasFor 从 4.2 版开始就成为框架的一部分。现在,几个核心Spring 注释已更新为包含此注释。

我们可以使用它来装饰单个注释或由元注释组成的注释中的属性。即,元注释是可以应用于另一个注释的注释。

在同一个注释中, 我们使用 @AliasFor 来声明属性的别名,以便我们可以互换地应用它们 。或者,我们可以在组合注释中使用它来覆盖其元注释中的属性。换句话说,当我们使用 @AliasFor 修饰组合注释中的属性时,它会覆盖其元注释中指定的属性

有趣的是,许多核心 Spring 注解(例如 @Bean@ComponentScan@Scope@RequestMapping@RestController ) 现在使用 @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. 注释中的显式别名

让我们考虑一个核心 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. 元注释中属性的显式别名

接下来,让我们看一个元注释的示例,并从中创建一个组合注释。然后, 我们将在自定义中看到别名的显式用法

首先,让我们将框架注释 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

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping
public @interface MyMapping {
    @AliasFor(annotation = RequestMapping.class, attribute = "method")
    RequestMethod[] action() default {};
}

我们可以看到,在 @MyMapping 中, action@RequestMapping 中属性 方法 的显式别名。也就是说, 我们的组合注释中的 操作 会覆盖元注释中的 方法

与注释中的别名类似,元注释属性别名也必须具有相同的返回类型。例如,我们例子中的 RequestMethod[] 。此外,属性 注释 应该引用元注释,就像我们使用 注释 = RequestMapping.class 一样。

为了进行演示,我们添加一个名为 MyMappingController 的 控制器类。我们将使用自定义注释来装饰它的方法。

具体来说,这里我们将仅向 @MyMapping 添加两个属性: routeaction

@Controller
public class MyMappingController {

    @MyMapping(action = RequestMethod.PATCH, route = "/test")
    public void mappingMethod() {}
    
}

最后,为了了解显式别名的行为方式,让我们添加一个简单的测试:

@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);
        }
    }
}

正如我们所看到的,我们的自定义注释的属性 操作 已经覆盖了元注释 @RequestMapping 的属性 方法

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 {};

在这种情况下, valuemappingroute@RequestMapping路径 的显式元注释覆盖。因此,它们也是彼此的隐式别名。换句话说, 对于 @MyMapping ,我们可以互换使用这三个属性。

为了演示这一点,我们将使用与上一节相同的控制器。这是另一个测试:

@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]);
        }
    }
}

值得注意的是,我们没有在控制器方法的注释中定义属性 映射 。然而, 它们仍然隐式地带有与 route 相同的值

六,结论

在本教程中, 我们了解了 Spring 框架中的 @AliasFor 注解 。在我们的示例中,我们研究了显式和隐式使用场景。

与往常一样,源代码可在 GitHub 上获取。