简体   繁体   中英

Sort spring @Beans by method level @Order annotation

My library has to deal with multiple beans (interceptors) that are specified in arbitrary order (because they are spread over multiple configuration files). Before I can apply them I have to sort them by their priority. I use AnnotationAwareOrderComparator.sort(beans) for that. This works well as long as the @Order annotation is added on the class level of that interceptor. But it does not work when I try to use it in @Configuration classes on @Bean methods:

@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 ...
    }

}

But if I add a test like this:

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

Then the test will fail. From my debugging I know that AnnotationAwareOrderComparator.findOrder(Object) always returns null (unspecified) for the order of those beans. Probably because the bean instances aren't proxied and thus neither implement order nor have the order annotation on their class level. Is there a BeanPostProcessor or an config option that I have to enable?

控制流

How do I tell spring to either preserve the annotated order or use the application context's bean definitions to sort the beans appropriately?

For your test case to work you need to use Ordered interface instead of the annotation. Lets examine the source code of AnnotationAwareOrderComparator. Notice that you are passing directly the objects to the sort method. The AnnotationAwareOrderComparator is using the findOrder upon the pased objects in order to find one of the three artifacts @Priority annotation @Order annotation or @Ordered interface. In your case you are passing the interceptor instances so the if check for Methidf and for Class will return false. You will hit the last if method: if (obj != null)

in which case it will just check you object clas for @Order annotation which will be null in your case. Effectivly your test case is just wrong. Notice though that your testcase will behave as expected if you implement Ordered interface.

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

I found a way to access the annotions that are only present on the bean factory methods:

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

Source: yidongnan/grpc-spring-boot-starter/InterceptorOrder

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM