diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java index 2214e5feae..aa3dc1d3b3 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -52,6 +53,7 @@ class AutoConfigurationSorter { public List getInPriorityOrder(Collection classNames) throws IOException { + List autoConfigurationClasses = new ArrayList(); for (String className : classNames) { autoConfigurationClasses.add(new AutoConfigurationClass(className)); @@ -65,30 +67,52 @@ class AutoConfigurationSorter { List orderedClassNames = new ArrayList(); for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses) { - orderedClassNames.add(autoConfigurationClass.toString()); + orderedClassNames.add(autoConfigurationClass.getClassName()); } return orderedClassNames; + } private List sortByAfterAnnotation( Collection autoConfigurationClasses) throws IOException { - List tosort = new ArrayList( - autoConfigurationClasses); + + // Create a look up table of actual autoconfigs + Map tosort = new LinkedHashMap(); + for (AutoConfigurationClass current : autoConfigurationClasses) { + tosort.put(current, current); + } + addAftersFromBefores(tosort); + Set sorted = new LinkedHashSet(); Set processing = new LinkedHashSet(); while (!tosort.isEmpty()) { doSortByAfterAnnotation(tosort, sorted, processing, null); } + return new ArrayList(sorted); + } - private void doSortByAfterAnnotation(List tosort, + private void addAftersFromBefores( + Map map) throws IOException { + // Pick up any befores and add them to the corresponding after + for (AutoConfigurationClass current : map.keySet()) { + for (AutoConfigurationClass before : current.getBefore()) { + if (map.containsKey(before)) { + map.get(before).getAfter().add(current); + } + } + } + } + + private void doSortByAfterAnnotation( + Map tosort, Set sorted, Set processing, AutoConfigurationClass current) throws IOException { if (current == null) { - current = tosort.remove(0); + current = tosort.remove(tosort.keySet().iterator().next()); } processing.add(current); @@ -97,8 +121,8 @@ class AutoConfigurationSorter { Assert.state(!processing.contains(after), "Cycle @AutoConfigureAfter detected between " + current + " and " + after); - if (!sorted.contains(after) && tosort.contains(after)) { - doSortByAfterAnnotation(tosort, sorted, processing, after); + if (!sorted.contains(after) && tosort.containsKey(after)) { + doSortByAfterAnnotation(tosort, sorted, processing, tosort.get(after)); } } @@ -110,12 +134,16 @@ class AutoConfigurationSorter { private final String className; - private final int order; + private int order; private List after; + private List before; + private Map afterAnnotation; + private Map beforeAnnotation; + public AutoConfigurationClass(String className) throws IOException { this.className = className; @@ -127,12 +155,15 @@ class AutoConfigurationSorter { // Read @Order annotation Map orderedAnnotation = metadata .getAnnotationAttributes(Order.class.getName()); - this.order = (orderedAnnotation == null ? Ordered.LOWEST_PRECEDENCE - : (Integer) orderedAnnotation.get("value")); + this.order = (orderedAnnotation == null ? 0 : (Integer) orderedAnnotation + .get("value")); // Read @AutoConfigureAfter annotation this.afterAnnotation = metadata.getAnnotationAttributes( AutoConfigureAfter.class.getName(), true); + // Read @AutoConfigureBefore annotation + this.beforeAnnotation = metadata.getAnnotationAttributes( + AutoConfigureBefore.class.getName(), true); } @Override @@ -140,10 +171,14 @@ class AutoConfigurationSorter { return this.order; } + public String getClassName() { + return this.className; + } + public List getAfter() throws IOException { if (this.after == null) { if (this.afterAnnotation == null) { - this.after = Collections.emptyList(); + this.after = new ArrayList(); } else { this.after = new ArrayList(); @@ -155,6 +190,22 @@ class AutoConfigurationSorter { return this.after; } + public List getBefore() throws IOException { + if (this.before == null) { + if (this.beforeAnnotation == null) { + this.before = Collections.emptyList(); + } + else { + this.before = new ArrayList(); + for (String beforeClass : (String[]) this.beforeAnnotation + .get("value")) { + this.before.add(new AutoConfigurationClass(beforeClass)); + } + } + } + return this.before; + } + @Override public String toString() { return this.className; @@ -169,6 +220,7 @@ class AutoConfigurationSorter { public boolean equals(Object obj) { return this.className.equals(((AutoConfigurationClass) obj).className); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java new file mode 100644 index 0000000000..3ed56bdf79 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Hint for that an {@link EnableAutoConfiguration auto-configuration} should be applied + * after the specified auto-configuration classes. + * + * @author Phillip Webb + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +public @interface AutoConfigureBefore { + Class[] value(); +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTest.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java similarity index 53% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTest.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java index df4ad9bd5c..592b71fadf 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTest.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java @@ -16,20 +16,21 @@ package org.springframework.boot.autoconfigure; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.core.IsEqual; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.boot.autoconfigure.AutoConfigurationSorter; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.io.DefaultResourceLoader; -import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; /** @@ -37,7 +38,7 @@ import static org.junit.Assert.assertThat; * * @author Phillip Webb */ -public class AutoConfigurationSorterTest { +public class AutoConfigurationSorterTests { private static final String LOWEST = OrderLowest.class.getName(); private static final String HIGHEST = OrderHighest.class.getName(); @@ -45,6 +46,11 @@ public class AutoConfigurationSorterTest { private static final String B = AutoConfigureB.class.getName(); private static final String C = AutoConfigureC.class.getName(); private static final String D = AutoConfigureD.class.getName(); + private static final String E = AutoConfigureE.class.getName(); + private static final String W = AutoConfigureW.class.getName(); + private static final String X = AutoConfigureX.class.getName(); + private static final String Y = AutoConfigureY.class.getName(); + private static final String Z = AutoConfigureZ.class.getName(); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -60,19 +66,38 @@ public class AutoConfigurationSorterTest { public void byOrderAnnotation() throws Exception { List actual = this.sorter.getInPriorityOrder(Arrays.asList(LOWEST, HIGHEST)); - assertThat(actual, equalTo(Arrays.asList(HIGHEST, LOWEST))); + assertThat(actual, nameMatcher(HIGHEST, LOWEST)); } @Test public void byAutoConfigureAfter() throws Exception { List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C)); - assertThat(actual, equalTo(Arrays.asList(C, B, A))); + assertThat(actual, nameMatcher(C, B, A)); + } + + @Test + public void byAutoConfigureBefore() throws Exception { + List actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y, Z)); + assertThat(actual, nameMatcher(Z, Y, X)); + } + + @Test + public void byAutoConfigureAfterDoubles() throws Exception { + List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, E)); + assertThat(actual, nameMatcher(C, E, B, A)); + } + + @Test + public void byAutoConfigureMixedBeforeAndAfter() throws Exception { + List actual = this.sorter + .getInPriorityOrder(Arrays.asList(A, B, C, W, X)); + assertThat(actual, nameMatcher(C, W, B, A, X)); } @Test public void byAutoConfigureAfterWithMissing() throws Exception { List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B)); - assertThat(actual, equalTo(Arrays.asList(B, A))); + assertThat(actual, nameMatcher(B, A)); } @Test @@ -82,6 +107,39 @@ public class AutoConfigurationSorterTest { this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, D)); } + private Matcher> nameMatcher(String... names) { + + final List list = Arrays.asList(names); + + return new IsEqual>(list) { + + @Override + public void describeMismatch(Object item, Description description) { + @SuppressWarnings("unchecked") + List items = (List) item; + description.appendText("was ").appendValue(prettify(items)); + } + + @Override + public void describeTo(Description description) { + description.appendValue(prettify(list)); + } + + private String prettify(List items) { + List pretty = new ArrayList(); + for (String item : items) { + if (item.contains("$AutoConfigure")) { + item = item.substring(item.indexOf("$AutoConfigure") + + "$AutoConfigure".length()); + } + pretty.add(item); + } + return pretty.toString(); + } + }; + + } + @Order(Ordered.LOWEST_PRECEDENCE) public static class OrderLowest { } @@ -94,7 +152,8 @@ public class AutoConfigurationSorterTest { public static class AutoConfigureA { } - @AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class }) + @AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class, + AutoConfigureE.class }) public static class AutoConfigureB { } @@ -104,4 +163,23 @@ public class AutoConfigurationSorterTest { @AutoConfigureAfter(AutoConfigureA.class) public static class AutoConfigureD { } + + public static class AutoConfigureE { + } + + @AutoConfigureBefore(AutoConfigureB.class) + public static class AutoConfigureW { + } + + public static class AutoConfigureX { + } + + @AutoConfigureBefore(AutoConfigureX.class) + public static class AutoConfigureY { + } + + @AutoConfigureBefore(AutoConfigureY.class) + public static class AutoConfigureZ { + } + }