From 9e9049ca7f96d5edf09215991e166f5a8c3f9aed Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 22 Feb 2022 13:17:13 +0100 Subject: [PATCH] Add relative ordering to @AutoConfiguration The relative ordering is implemented with @AliasFor annotations on the @AutoConfiguration annotation. The production code already works without changes, only the test code had to be modified. It now uses AnnotationMetadata which already knows how to deal with @AliasFor instead of using the reflection API directly. See gh-29907 --- .../boot/autoconfigure/AutoConfiguration.java | 49 ++++++++ .../autoconfigure/AutoConfigureAfter.java | 2 +- .../autoconfigure/AutoConfigureBefore.java | 2 +- .../AutoConfigurationSorterTests.java | 105 ++++++++++++++---- 4 files changed, 134 insertions(+), 24 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfiguration.java index 3fee75c5618..a8ad538d823 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfiguration.java @@ -25,8 +25,11 @@ import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AliasFor; import org.springframework.core.io.support.SpringFactoriesLoader; /** @@ -55,6 +58,52 @@ import org.springframework.core.io.support.SpringFactoriesLoader; @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration(proxyBeanMethods = false) +@AutoConfigureBefore +@AutoConfigureAfter public @interface AutoConfiguration { + /** + * Explicitly specify the name of the Spring bean definition associated with the + * {@code @AutoConfiguration} class. If left unspecified (the common case), a bean + * name will be automatically generated. + *

+ * The custom name applies only if the {@code @AutoConfiguration} class is picked up + * via component scanning or supplied directly to an + * {@link AnnotationConfigApplicationContext}. If the {@code @AutoConfiguration} class + * is registered as a traditional XML bean definition, the name/id of the bean element + * will take precedence. + * @return the explicit component name, if any (or empty String otherwise) + * @see AnnotationBeanNameGenerator + */ + @AliasFor(annotation = Configuration.class) + String value() default ""; + + /** + * The auto-configure classes that should have not yet been applied. + * @return the classes + */ + @AliasFor(annotation = AutoConfigureBefore.class, attribute = "value") + Class[] before() default {}; + + /** + * The names of the auto-configure classes that should have not yet been applied. + * @return the class names + */ + @AliasFor(annotation = AutoConfigureBefore.class, attribute = "name") + String[] beforeName() default {}; + + /** + * The auto-configure classes that should have already been applied. + * @return the classes + */ + @AliasFor(annotation = AutoConfigureAfter.class, attribute = "value") + Class[] after() default {}; + + /** + * The names of the auto-configure classes that should have already been applied. + * @return the class names + */ + @AliasFor(annotation = AutoConfigureAfter.class, attribute = "name") + String[] afterName() default {}; + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureAfter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureAfter.java index e3bb47f88cf..f1e99e1f9a3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureAfter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureAfter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java index 5da0b3a3f95..eef105ff0ce 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java index 6d25109e970..93191028ed6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; @@ -28,6 +29,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.core.Ordered; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -43,6 +45,7 @@ import static org.mockito.Mockito.mock; * * @author Phillip Webb * @author Andy Wilkinson + * @author Moritz Halbritter */ class AutoConfigurationSorterTests { @@ -54,8 +57,14 @@ class AutoConfigurationSorterTests { private static final String A = AutoConfigureA.class.getName(); + private static final String A2 = AutoConfigureA2.class.getName(); + + private static final String A3 = AutoConfigureA3.class.getName(); + private static final String B = AutoConfigureB.class.getName(); + private static final String B2 = AutoConfigureB2.class.getName(); + private static final String C = AutoConfigureC.class.getName(); private static final String D = AutoConfigureD.class.getName(); @@ -64,15 +73,17 @@ class AutoConfigurationSorterTests { private static final String W = AutoConfigureW.class.getName(); + private static final String W2 = AutoConfigureW2.class.getName(); + private static final String X = AutoConfigureX.class.getName(); private static final String Y = AutoConfigureY.class.getName(); + private static final String Y2 = AutoConfigureY2.class.getName(); + private static final String Z = AutoConfigureZ.class.getName(); - private static final String A2 = AutoConfigureA2.class.getName(); - - private static final String W2 = AutoConfigureW2.class.getName(); + private static final String Z2 = AutoConfigureZ2.class.getName(); private AutoConfigurationSorter sorter; @@ -95,12 +106,24 @@ class AutoConfigurationSorterTests { assertThat(actual).containsExactly(C, B, A); } + @Test + void byAutoConfigureAfterAliasFor() { + List actual = this.sorter.getInPriorityOrder(Arrays.asList(A3, B2, C)); + assertThat(actual).containsExactly(C, B2, A3); + } + @Test void byAutoConfigureBefore() { List actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y, Z)); assertThat(actual).containsExactly(Z, Y, X); } + @Test + void byAutoConfigureBeforeAliasFor() { + List actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y2, Z2)); + assertThat(actual).containsExactly(Z2, Y2, X); + } + @Test void byAutoConfigureAfterDoubles() { List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, E)); @@ -170,29 +193,47 @@ class AutoConfigurationSorterTests { for (String className : classNames) { Class type = ClassUtils.forName(className, null); properties.put(type.getName(), ""); - AutoConfigureOrder order = type.getDeclaredAnnotation(AutoConfigureOrder.class); - if (order != null) { - properties.put(className + ".AutoConfigureOrder", String.valueOf(order.value())); - } - AutoConfigureBefore autoConfigureBefore = type.getDeclaredAnnotation(AutoConfigureBefore.class); - if (autoConfigureBefore != null) { - properties.put(className + ".AutoConfigureBefore", - merge(autoConfigureBefore.value(), autoConfigureBefore.name())); - } - AutoConfigureAfter autoConfigureAfter = type.getDeclaredAnnotation(AutoConfigureAfter.class); - if (autoConfigureAfter != null) { - properties.put(className + ".AutoConfigureAfter", - merge(autoConfigureAfter.value(), autoConfigureAfter.name())); - } + AnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(type); + addAutoConfigureOrder(properties, className, annotationMetadata); + addAutoConfigureBefore(properties, className, annotationMetadata); + addAutoConfigureAfter(properties, className, annotationMetadata); } return AutoConfigurationMetadataLoader.loadMetadata(properties); } - private String merge(Class[] value, String[] name) { - Set items = new LinkedHashSet<>(); - for (Class type : value) { - items.add(type.getName()); + private void addAutoConfigureAfter(Properties properties, String className, AnnotationMetadata annotationMetadata) { + Map autoConfigureAfter = annotationMetadata + .getAnnotationAttributes(AutoConfigureAfter.class.getName(), true); + if (autoConfigureAfter != null) { + properties.put(className + ".AutoConfigureAfter", + merge((String[]) autoConfigureAfter.get("value"), (String[]) autoConfigureAfter.get("name"))); } + } + + private void addAutoConfigureBefore(Properties properties, String className, + AnnotationMetadata annotationMetadata) { + Map autoConfigureBefore = annotationMetadata + .getAnnotationAttributes(AutoConfigureBefore.class.getName(), true); + if (autoConfigureBefore != null) { + properties.put(className + ".AutoConfigureBefore", + merge((String[]) autoConfigureBefore.get("value"), (String[]) autoConfigureBefore.get("name"))); + } + } + + private void addAutoConfigureOrder(Properties properties, String className, AnnotationMetadata annotationMetadata) { + Map autoConfigureOrder = annotationMetadata + .getAnnotationAttributes(AutoConfigureOrder.class.getName()); + if (autoConfigureOrder != null) { + Integer order = (Integer) autoConfigureOrder.get("order"); + if (order != null) { + properties.put(className + ".AutoConfigureOrder", String.valueOf(order)); + } + } + } + + private String merge(String[] value, String[] name) { + Set items = new LinkedHashSet<>(); + Collections.addAll(items, value); Collections.addAll(items, name); return StringUtils.collectionToCommaDelimitedString(items); } @@ -222,11 +263,21 @@ class AutoConfigurationSorterTests { } + @AutoConfiguration(after = AutoConfigureB2.class) + static class AutoConfigureA3 { + + } + @AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class }) static class AutoConfigureB { } + @AutoConfiguration(after = { AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class }) + static class AutoConfigureB2 { + + } + static class AutoConfigureC { } @@ -259,11 +310,21 @@ class AutoConfigurationSorterTests { } + @AutoConfiguration(before = AutoConfigureX.class) + static class AutoConfigureY2 { + + } + @AutoConfigureBefore(AutoConfigureY.class) static class AutoConfigureZ { } + @AutoConfiguration(before = AutoConfigureY2.class) + static class AutoConfigureZ2 { + + } + static class SkipCycleMetadataReaderFactory extends CachingMetadataReaderFactory { @Override