Commit 33658c29 authored by Dave Syer's avatar Dave Syer

Add @AutoConfigureBefore and simple implementation

[#54597932] [bs-273] Circular view reference for /error
parent 91a56f7b
...@@ -20,6 +20,7 @@ import java.io.IOException; ...@@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -52,6 +53,7 @@ class AutoConfigurationSorter { ...@@ -52,6 +53,7 @@ class AutoConfigurationSorter {
public List<String> getInPriorityOrder(Collection<String> classNames) public List<String> getInPriorityOrder(Collection<String> classNames)
throws IOException { throws IOException {
List<AutoConfigurationClass> autoConfigurationClasses = new ArrayList<AutoConfigurationClass>(); List<AutoConfigurationClass> autoConfigurationClasses = new ArrayList<AutoConfigurationClass>();
for (String className : classNames) { for (String className : classNames) {
autoConfigurationClasses.add(new AutoConfigurationClass(className)); autoConfigurationClasses.add(new AutoConfigurationClass(className));
...@@ -65,30 +67,52 @@ class AutoConfigurationSorter { ...@@ -65,30 +67,52 @@ class AutoConfigurationSorter {
List<String> orderedClassNames = new ArrayList<String>(); List<String> orderedClassNames = new ArrayList<String>();
for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses) { for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses) {
orderedClassNames.add(autoConfigurationClass.toString()); orderedClassNames.add(autoConfigurationClass.getClassName());
} }
return orderedClassNames; return orderedClassNames;
} }
private List<AutoConfigurationClass> sortByAfterAnnotation( private List<AutoConfigurationClass> sortByAfterAnnotation(
Collection<AutoConfigurationClass> autoConfigurationClasses) Collection<AutoConfigurationClass> autoConfigurationClasses)
throws IOException { throws IOException {
List<AutoConfigurationClass> tosort = new ArrayList<AutoConfigurationClass>(
autoConfigurationClasses); // Create a look up table of actual autoconfigs
Map<AutoConfigurationClass, AutoConfigurationClass> tosort = new LinkedHashMap<AutoConfigurationClass, AutoConfigurationClass>();
for (AutoConfigurationClass current : autoConfigurationClasses) {
tosort.put(current, current);
}
addAftersFromBefores(tosort);
Set<AutoConfigurationClass> sorted = new LinkedHashSet<AutoConfigurationClass>(); Set<AutoConfigurationClass> sorted = new LinkedHashSet<AutoConfigurationClass>();
Set<AutoConfigurationClass> processing = new LinkedHashSet<AutoConfigurationClass>(); Set<AutoConfigurationClass> processing = new LinkedHashSet<AutoConfigurationClass>();
while (!tosort.isEmpty()) { while (!tosort.isEmpty()) {
doSortByAfterAnnotation(tosort, sorted, processing, null); doSortByAfterAnnotation(tosort, sorted, processing, null);
} }
return new ArrayList<AutoConfigurationClass>(sorted); return new ArrayList<AutoConfigurationClass>(sorted);
} }
private void doSortByAfterAnnotation(List<AutoConfigurationClass> tosort, private void addAftersFromBefores(
Map<AutoConfigurationClass, AutoConfigurationClass> 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<AutoConfigurationClass, AutoConfigurationClass> tosort,
Set<AutoConfigurationClass> sorted, Set<AutoConfigurationClass> processing, Set<AutoConfigurationClass> sorted, Set<AutoConfigurationClass> processing,
AutoConfigurationClass current) throws IOException { AutoConfigurationClass current) throws IOException {
if (current == null) { if (current == null) {
current = tosort.remove(0); current = tosort.remove(tosort.keySet().iterator().next());
} }
processing.add(current); processing.add(current);
...@@ -97,8 +121,8 @@ class AutoConfigurationSorter { ...@@ -97,8 +121,8 @@ class AutoConfigurationSorter {
Assert.state(!processing.contains(after), Assert.state(!processing.contains(after),
"Cycle @AutoConfigureAfter detected between " + current + " and " "Cycle @AutoConfigureAfter detected between " + current + " and "
+ after); + after);
if (!sorted.contains(after) && tosort.contains(after)) { if (!sorted.contains(after) && tosort.containsKey(after)) {
doSortByAfterAnnotation(tosort, sorted, processing, after); doSortByAfterAnnotation(tosort, sorted, processing, tosort.get(after));
} }
} }
...@@ -110,12 +134,16 @@ class AutoConfigurationSorter { ...@@ -110,12 +134,16 @@ class AutoConfigurationSorter {
private final String className; private final String className;
private final int order; private int order;
private List<AutoConfigurationClass> after; private List<AutoConfigurationClass> after;
private List<AutoConfigurationClass> before;
private Map<String, Object> afterAnnotation; private Map<String, Object> afterAnnotation;
private Map<String, Object> beforeAnnotation;
public AutoConfigurationClass(String className) throws IOException { public AutoConfigurationClass(String className) throws IOException {
this.className = className; this.className = className;
...@@ -127,12 +155,15 @@ class AutoConfigurationSorter { ...@@ -127,12 +155,15 @@ class AutoConfigurationSorter {
// Read @Order annotation // Read @Order annotation
Map<String, Object> orderedAnnotation = metadata Map<String, Object> orderedAnnotation = metadata
.getAnnotationAttributes(Order.class.getName()); .getAnnotationAttributes(Order.class.getName());
this.order = (orderedAnnotation == null ? Ordered.LOWEST_PRECEDENCE this.order = (orderedAnnotation == null ? 0 : (Integer) orderedAnnotation
: (Integer) orderedAnnotation.get("value")); .get("value"));
// Read @AutoConfigureAfter annotation // Read @AutoConfigureAfter annotation
this.afterAnnotation = metadata.getAnnotationAttributes( this.afterAnnotation = metadata.getAnnotationAttributes(
AutoConfigureAfter.class.getName(), true); AutoConfigureAfter.class.getName(), true);
// Read @AutoConfigureBefore annotation
this.beforeAnnotation = metadata.getAnnotationAttributes(
AutoConfigureBefore.class.getName(), true);
} }
@Override @Override
...@@ -140,10 +171,14 @@ class AutoConfigurationSorter { ...@@ -140,10 +171,14 @@ class AutoConfigurationSorter {
return this.order; return this.order;
} }
public String getClassName() {
return this.className;
}
public List<AutoConfigurationClass> getAfter() throws IOException { public List<AutoConfigurationClass> getAfter() throws IOException {
if (this.after == null) { if (this.after == null) {
if (this.afterAnnotation == null) { if (this.afterAnnotation == null) {
this.after = Collections.emptyList(); this.after = new ArrayList<AutoConfigurationClass>();
} }
else { else {
this.after = new ArrayList<AutoConfigurationClass>(); this.after = new ArrayList<AutoConfigurationClass>();
...@@ -155,6 +190,22 @@ class AutoConfigurationSorter { ...@@ -155,6 +190,22 @@ class AutoConfigurationSorter {
return this.after; return this.after;
} }
public List<AutoConfigurationClass> getBefore() throws IOException {
if (this.before == null) {
if (this.beforeAnnotation == null) {
this.before = Collections.emptyList();
}
else {
this.before = new ArrayList<AutoConfigurationClass>();
for (String beforeClass : (String[]) this.beforeAnnotation
.get("value")) {
this.before.add(new AutoConfigurationClass(beforeClass));
}
}
}
return this.before;
}
@Override @Override
public String toString() { public String toString() {
return this.className; return this.className;
...@@ -169,6 +220,7 @@ class AutoConfigurationSorter { ...@@ -169,6 +220,7 @@ class AutoConfigurationSorter {
public boolean equals(Object obj) { public boolean equals(Object obj) {
return this.className.equals(((AutoConfigurationClass) obj).className); return this.className.equals(((AutoConfigurationClass) obj).className);
} }
} }
} }
/*
* 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();
}
...@@ -16,20 +16,21 @@ ...@@ -16,20 +16,21 @@
package org.springframework.boot.autoconfigure; package org.springframework.boot.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsEqual;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
/** /**
...@@ -37,7 +38,7 @@ import static org.junit.Assert.assertThat; ...@@ -37,7 +38,7 @@ import static org.junit.Assert.assertThat;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
public class AutoConfigurationSorterTest { public class AutoConfigurationSorterTests {
private static final String LOWEST = OrderLowest.class.getName(); private static final String LOWEST = OrderLowest.class.getName();
private static final String HIGHEST = OrderHighest.class.getName(); private static final String HIGHEST = OrderHighest.class.getName();
...@@ -45,6 +46,11 @@ public class AutoConfigurationSorterTest { ...@@ -45,6 +46,11 @@ public class AutoConfigurationSorterTest {
private static final String B = AutoConfigureB.class.getName(); private static final String B = AutoConfigureB.class.getName();
private static final String C = AutoConfigureC.class.getName(); private static final String C = AutoConfigureC.class.getName();
private static final String D = AutoConfigureD.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 @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
...@@ -60,19 +66,38 @@ public class AutoConfigurationSorterTest { ...@@ -60,19 +66,38 @@ public class AutoConfigurationSorterTest {
public void byOrderAnnotation() throws Exception { public void byOrderAnnotation() throws Exception {
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(LOWEST, List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(LOWEST,
HIGHEST)); HIGHEST));
assertThat(actual, equalTo(Arrays.asList(HIGHEST, LOWEST))); assertThat(actual, nameMatcher(HIGHEST, LOWEST));
} }
@Test @Test
public void byAutoConfigureAfter() throws Exception { public void byAutoConfigureAfter() throws Exception {
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C)); List<String> 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<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y, Z));
assertThat(actual, nameMatcher(Z, Y, X));
}
@Test
public void byAutoConfigureAfterDoubles() throws Exception {
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, E));
assertThat(actual, nameMatcher(C, E, B, A));
}
@Test
public void byAutoConfigureMixedBeforeAndAfter() throws Exception {
List<String> actual = this.sorter
.getInPriorityOrder(Arrays.asList(A, B, C, W, X));
assertThat(actual, nameMatcher(C, W, B, A, X));
} }
@Test @Test
public void byAutoConfigureAfterWithMissing() throws Exception { public void byAutoConfigureAfterWithMissing() throws Exception {
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B)); List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B));
assertThat(actual, equalTo(Arrays.asList(B, A))); assertThat(actual, nameMatcher(B, A));
} }
@Test @Test
...@@ -82,6 +107,39 @@ public class AutoConfigurationSorterTest { ...@@ -82,6 +107,39 @@ public class AutoConfigurationSorterTest {
this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, D)); this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, D));
} }
private Matcher<? super List<String>> nameMatcher(String... names) {
final List<String> list = Arrays.asList(names);
return new IsEqual<List<String>>(list) {
@Override
public void describeMismatch(Object item, Description description) {
@SuppressWarnings("unchecked")
List<String> items = (List<String>) item;
description.appendText("was ").appendValue(prettify(items));
}
@Override
public void describeTo(Description description) {
description.appendValue(prettify(list));
}
private String prettify(List<String> items) {
List<String> pretty = new ArrayList<String>();
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) @Order(Ordered.LOWEST_PRECEDENCE)
public static class OrderLowest { public static class OrderLowest {
} }
...@@ -94,7 +152,8 @@ public class AutoConfigurationSorterTest { ...@@ -94,7 +152,8 @@ public class AutoConfigurationSorterTest {
public static class AutoConfigureA { public static class AutoConfigureA {
} }
@AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class }) @AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class,
AutoConfigureE.class })
public static class AutoConfigureB { public static class AutoConfigureB {
} }
...@@ -104,4 +163,23 @@ public class AutoConfigurationSorterTest { ...@@ -104,4 +163,23 @@ public class AutoConfigurationSorterTest {
@AutoConfigureAfter(AutoConfigureA.class) @AutoConfigureAfter(AutoConfigureA.class)
public static class AutoConfigureD { 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 {
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment