Allow auto-configure sort with incomplete chain

Update `AutoConfigurationSorter` so that all `@AutoConfigureBefore` and
`@AutoConfigureAfter` classes are considered even if they are ultimately
not part of the requested set.

Prior to this commit, given classes ordered with annotations such that
A -> B -> C a call to sort only [A, B] could return the incorrect order.

Fixes gh-12660
This commit is contained in:
Phillip Webb 2018-03-29 14:36:29 -07:00
parent 4b4a8acb9d
commit 7649eb6230
2 changed files with 97 additions and 28 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -52,7 +52,7 @@ class AutoConfigurationSorter {
}
public List<String> getInPriorityOrder(Collection<String> classNames) {
final AutoConfigurationClasses classes = new AutoConfigurationClasses(
AutoConfigurationClasses classes = new AutoConfigurationClasses(
this.metadataReaderFactory, this.autoConfigurationMetadata, classNames);
List<String> orderedClassNames = new ArrayList<>(classNames);
// Initially sort alphabetically
@ -71,11 +71,13 @@ class AutoConfigurationSorter {
private List<String> sortByAnnotation(AutoConfigurationClasses classes,
List<String> classNames) {
List<String> toSort = new ArrayList<>(classNames);
toSort.addAll(classes.getAllNames());
Set<String> sorted = new LinkedHashSet<>();
Set<String> processing = new LinkedHashSet<>();
while (!toSort.isEmpty()) {
doSortByAfterAnnotation(classes, toSort, sorted, processing, null);
}
sorted.retainAll(classNames);
return new ArrayList<>(sorted);
}
@ -104,9 +106,32 @@ class AutoConfigurationSorter {
AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory,
AutoConfigurationMetadata autoConfigurationMetadata,
Collection<String> classNames) {
addToClasses(metadataReaderFactory, autoConfigurationMetadata, classNames,
true);
}
public Set<String> getAllNames() {
return this.classes.keySet();
}
private void addToClasses(MetadataReaderFactory metadataReaderFactory,
AutoConfigurationMetadata autoConfigurationMetadata,
Collection<String> classNames, boolean required) {
for (String className : classNames) {
this.classes.put(className, new AutoConfigurationClass(className,
metadataReaderFactory, autoConfigurationMetadata));
if (!this.classes.containsKey(className)) {
AutoConfigurationClass autoConfigurationClass = new AutoConfigurationClass(
className, metadataReaderFactory, autoConfigurationMetadata);
boolean available = autoConfigurationClass.isAvailable();
if (required || available) {
this.classes.put(className, autoConfigurationClass);
}
if (available) {
addToClasses(metadataReaderFactory, autoConfigurationMetadata,
autoConfigurationClass.getBefore(), false);
addToClasses(metadataReaderFactory, autoConfigurationMetadata,
autoConfigurationClass.getAfter(), false);
}
}
}
}
@ -136,11 +161,11 @@ class AutoConfigurationSorter {
private final AutoConfigurationMetadata autoConfigurationMetadata;
private AnnotationMetadata annotationMetadata;
private volatile AnnotationMetadata annotationMetadata;
private final Set<String> before;
private volatile Set<String> before;
private final Set<String> after;
private volatile Set<String> after;
AutoConfigurationClass(String className,
MetadataReaderFactory metadataReaderFactory,
@ -148,15 +173,37 @@ class AutoConfigurationSorter {
this.className = className;
this.metadataReaderFactory = metadataReaderFactory;
this.autoConfigurationMetadata = autoConfigurationMetadata;
this.before = readBefore();
this.after = readAfter();
}
public boolean isAvailable() {
try {
if (!wasProcessed()) {
getAnnotationMetadata();
}
return true;
}
catch (Exception ex) {
return false;
}
}
public Set<String> getBefore() {
if (this.before == null) {
this.before = (wasProcessed()
? this.autoConfigurationMetadata.getSet(this.className,
"AutoConfigureBefore", Collections.emptySet())
: getAnnotationValue(AutoConfigureBefore.class));
}
return this.before;
}
public Set<String> getAfter() {
if (this.after == null) {
this.after = (wasProcessed()
? this.autoConfigurationMetadata.getSet(this.className,
"AutoConfigureAfter", Collections.emptySet())
: getAnnotationValue(AutoConfigureAfter.class));
}
return this.after;
}
@ -171,22 +218,6 @@ class AutoConfigurationSorter {
: (Integer) attributes.get("value"));
}
private Set<String> readBefore() {
if (wasProcessed()) {
return this.autoConfigurationMetadata.getSet(this.className,
"AutoConfigureBefore", Collections.emptySet());
}
return getAnnotationValue(AutoConfigureBefore.class);
}
private Set<String> readAfter() {
if (wasProcessed()) {
return this.autoConfigurationMetadata.getSet(this.className,
"AutoConfigureAfter", Collections.emptySet());
}
return getAnnotationValue(AutoConfigureAfter.class);
}
private boolean wasProcessed() {
return (this.autoConfigurationMetadata != null
&& this.autoConfigurationMetadata.wasProcessed(this.className));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
@ -29,6 +30,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.core.Ordered;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@ -82,7 +84,7 @@ public class AutoConfigurationSorterTests {
@Before
public void setup() {
this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(),
this.sorter = new AutoConfigurationSorter(new SkipCycleMetadataReaderFactory(),
this.autoConfigurationMetadata);
}
@ -140,6 +142,8 @@ public class AutoConfigurationSorterTests {
@Test
public void byAutoConfigureAfterWithCycle() {
this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(),
this.autoConfigurationMetadata);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("AutoConfigure cycle detected");
this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, D));
@ -147,7 +151,7 @@ public class AutoConfigurationSorterTests {
@Test
public void usesAnnotationPropertiesWhenPossible() throws Exception {
MetadataReaderFactory readerFactory = mock(MetadataReaderFactory.class);
MetadataReaderFactory readerFactory = new SkipCycleMetadataReaderFactory();
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A2, B, C, W2, X);
this.sorter = new AutoConfigurationSorter(readerFactory,
this.autoConfigurationMetadata);
@ -156,6 +160,27 @@ public class AutoConfigurationSorterTests {
assertThat(actual).containsExactly(C, W2, B, A2, X);
}
@Test
public void useAnnotationWithNoDirectLink() throws Exception {
MetadataReaderFactory readerFactory = new SkipCycleMetadataReaderFactory();
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A, B, E);
this.sorter = new AutoConfigurationSorter(readerFactory,
this.autoConfigurationMetadata);
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, E));
assertThat(actual).containsExactly(E, A);
}
@Test
public void useAnnotationWithNoDirectLinkAndCycle() throws Exception {
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A, B, D);
this.sorter = new AutoConfigurationSorter(readerFactory,
this.autoConfigurationMetadata);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("AutoConfigure cycle detected");
this.sorter.getInPriorityOrder(Arrays.asList(D, B));
}
private AutoConfigurationMetadata getAutoConfigurationMetadata(String... classNames)
throws Exception {
Properties properties = new Properties();
@ -263,4 +288,17 @@ public class AutoConfigurationSorterTests {
}
private static class SkipCycleMetadataReaderFactory
extends CachingMetadataReaderFactory {
@Override
public MetadataReader getMetadataReader(String className) throws IOException {
if (className.equals(D)) {
throw new IOException();
}
return super.getMetadataReader(className);
}
}
}