按方法级别的@Order注解对Spring @Beans进行排序

6

我的库必须处理多个bean(拦截器),这些bean(拦截器)按任意顺序指定(因为它们分布在多个配置文件中)。在应用它们之前,我必须按它们的优先级对它们进行排序。我使用AnnotationAwareOrderComparator.sort(beans)进行排序。只要该拦截器的类级别上添加了@Order注释,这种方法就有效。但是,在@Configuration类的@Bean方法中尝试使用它时,它不起作用:

@Configuration
public class Config {

    @Bean
    @Order(1)
    public ServerInterceptor exceptionTranslatingServerInterceptor() {
        return ...;
    }

    @Bean
    @Order(2)
    public ServerInterceptor authenticatingServerInterceptor() {
        return ...;
    }

    @Bean
    @Order(3)
    public ServerInterceptor authorizationCheckingServerInterceptor() {
        return ...
    }

}

但是如果我添加这样一个测试:
@Test
void testOrderingOfTheDefaultInterceptors() {
    List<ServerInterceptor> expected = new ArrayList<>();
    expected.add(applicationContext.getBean(ExceptionTranslatingServerInterceptor.class));
    expected.add(applicationContext.getBean(AuthenticatingServerInterceptor.class));
    expected.add(applicationContext.getBean(AuthorizationCheckingServerInterceptor.class));

    List<ServerInterceptor> actual = new ArrayList<>(this.registry.getServerInterceptors());
    assertEquals(expected, actual); // Accidentally passes
    // System.out.println(actual);

    Collections.shuffle(actual);
    AnnotationAwareOrderComparator.sort(actual);
    assertEquals(expected, actual); // Fails
    // System.out.println(actual);
}

那么测试将会失败。 从我的调试中,我知道对于这些bean的顺序,AnnotationAwareOrderComparator.findOrder(Object)总是返回null(未指定)。可能是因为bean实例没有被代理,因此既不实现顺序也没有在它们的类级别上使用顺序注释。是否有BeanPostProcessor或配置选项需要启用?

Controll-Flow

我该如何告诉Spring要么保留已注释的顺序,要么使用应用程序上下文的bean定义适当地排序bean?

2个回答

1
为使您的测试用例正常工作,您需要使用 Ordered 接口而不是注释。让我们检查 AnnotationAwareOrderComparator 的源代码。请注意,您直接将对象传递给 sort 方法。AnnotationAwareOrderComparator 在传递的对象上使用 findOrder 来查找三个工件之一:@Priority 注释、@Order 注释或 @Ordered 接口。在您的情况下,您正在传递拦截器实例,因此 Methidf 和 Class 的 if 检查将返回 false。您将进入最后一个 if 方法:if (obj != null),这种情况下,它只会检查您对象类中的 @Order 注释,在您的情况下将为 null。实际上,您的测试用例是错误的。请注意,如果实现 Ordered 接口,则您的测试用例将按预期运行。
public static void sort(List list) {
        if (list.size() > 1) {
            Collections.sort(list, INSTANCE);
        }
    }

protected Integer findOrder(Object obj) {
        // Check for regular Ordered interface
        Integer order = super.findOrder(obj);
        if (order != null) {
            return order;
        }

        // Check for @Order and @Priority on various kinds of elements
        if (obj instanceof Class) {
            return OrderUtils.getOrder((Class) obj);
        }
        else if (obj instanceof Method) {
            Order ann = AnnotationUtils.findAnnotation((Method) obj, Order.class);
            if (ann != null) {
                return ann.value();
            }
        }
        else if (obj instanceof AnnotatedElement) {
            Order ann = AnnotationUtils.getAnnotation((AnnotatedElement) obj, Order.class);
            if (ann != null) {
                return ann.value();
            }
        }
        else if (obj != null) {
            return OrderUtils.getOrder(obj.getClass());
        }

        return null;
    }

1
那我为什么能在那个方法上放置订单注释呢?虽然Javadoc中没有说明不能将其用于@Bean声明。Spring在许多地方使用代理和类似的结构,为什么不用于这种情况呢?子类化bean类只是为了设置顺序感觉不对。 - ST-DDT
1
@ST-DDT 你可以使用它,在启动时它会被考虑进去。但这是在你实际拥有实例之前。在你的测试用例中,你没有正确使用的不是Order,而是AnnotationAwareOrderComparator。你期望它能在初始化实例时工作,但实际上并不是这样。根据文档,在启动时应该正确应用Order。因此,如果你在测试中将Autowire放在拦截器列表的列表中,它们应该按正确顺序排序。 - Alexander Petrov

0
我找到了一种访问仅存在于Bean工厂方法上的注释的方法:
    public static Comparator<Object> beanFactoryAwareOrderComparator(final ApplicationContext context,
            final Class<?> beanType) {
        final Map<?, String> beans = HashBiMap.create(context.getBeansOfType(beanType)).inverse();
        return OrderComparator.INSTANCE.withSourceProvider(bean -> {

            // The AnnotationAwareOrderComparator does not have the "withSourceProvider" method
            // The OrderComparator.withSourceProvider does not properly account for the annotations
            final Integer priority = AnnotationAwareOrderComparator.INSTANCE.getPriority(bean);
            if (priority != null) {
                return (Ordered) () -> priority;
            }

            // Consult the bean factory method for annotations
            final String beanName = beans.get(bean);
            if (beanName != null) {
                final Order order = context.findAnnotationOnBean(beanName, Order.class);
                if (order != null) {
                    return (Ordered) order::value;
                }
            }

            // Nothing present
            return null;
        });
    }

来源:yidongnan/grpc-spring-boot-starter/InterceptorOrder


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接