Register AutoConfigurations using fully qualified class name

Update `AbstractApplicationContextRunner` and `Configurations` to
allow registration of beans with a specific generated bean name. By
default, no name is generated, however, `AutoConfigurations` has been
updated to use bean names using the fully qualified class name.

The update brings `ApplicationContextRunners` closer the behavior of
a standard Spring Boot application where user `@Configuration` classes
are usually registered with a simple name and auto-configurations are
imported (via an `ImportSelector`) using a fully qualified name.

Fixes gh-17963

Co-authored-by: Stéphane Nicoll <stephane.nicoll@broadcom.com>
Co-authored-by: Andy Wilkinson <andy.wilkinson@broadcom.com>
Co-authored-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Phillip Webb
2024-11-18 11:25:29 -08:00
parent a705402e75
commit 26c775eff8
8 changed files with 207 additions and 26 deletions

View File

@@ -25,6 +25,7 @@ import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -65,6 +66,8 @@ public abstract class Configurations {
private final Set<Class<?>> classes;
private final Function<Class<?>, String> beanNameGenerator;
/**
* Create a new {@link Configurations} instance.
* @param classes the configuration classes
@@ -74,20 +77,28 @@ public abstract class Configurations {
Collection<Class<?>> sorted = sort(classes);
this.sorter = null;
this.classes = Collections.unmodifiableSet(new LinkedHashSet<>(sorted));
this.beanNameGenerator = null;
}
/**
* Create a new {@link Configurations} instance.
* @param sorter a {@link UnaryOperator} used to sort the configurations
* @param classes the configuration classes
* @param beanNameGenerator an optional function used to generate the bean name
* @since 3.4.0
*/
protected Configurations(UnaryOperator<Collection<Class<?>>> sorter, Collection<Class<?>> classes) {
Assert.notNull(sorter, "Sorter must not be null");
protected Configurations(UnaryOperator<Collection<Class<?>>> sorter, Collection<Class<?>> classes,
Function<Class<?>, String> beanNameGenerator) {
Assert.notNull(classes, "Classes must not be null");
sorter = (sorter != null) ? sorter : UnaryOperator.identity();
Collection<Class<?>> sorted = sorter.apply(classes);
this.sorter = sorter;
this.sorter = (sorter != null) ? sorter : UnaryOperator.identity();
this.classes = Collections.unmodifiableSet(new LinkedHashSet<>(sorted));
this.beanNameGenerator = beanNameGenerator;
}
protected final Set<Class<?>> getClasses() {
return this.classes;
}
/**
@@ -95,17 +106,13 @@ public abstract class Configurations {
* @param classes the classes to sort
* @return a sorted set of classes
* @deprecated since 3.4.0 for removal in 3.6.0 in favor of
* {@link #Configurations(UnaryOperator, Collection)}
* {@link #Configurations(UnaryOperator, Collection, Function)}
*/
@Deprecated(since = "3.4.0", forRemoval = true)
protected Collection<Class<?>> sort(Collection<Class<?>> classes) {
return classes;
}
protected final Set<Class<?>> getClasses() {
return this.classes;
}
/**
* Merge configurations from another source of the same type.
* @param other the other {@link Configurations} (must be of the same type as this
@@ -128,6 +135,17 @@ public abstract class Configurations {
*/
protected abstract Configurations merge(Set<Class<?>> mergedClasses);
/**
* Return the bean name that should be used for the given configuration class or
* {@code null} to use the default name.
* @param beanClass the bean class
* @return the bean name
* @since 3.4.0
*/
public String getBeanName(Class<?> beanClass) {
return (this.beanNameGenerator != null) ? this.beanNameGenerator.apply(beanClass) : null;
}
/**
* Return the classes from all the specified configurations in the order that they
* would be registered.
@@ -145,30 +163,40 @@ public abstract class Configurations {
* @return configuration classes in registration order
*/
public static Class<?>[] getClasses(Collection<Configurations> configurations) {
List<Configurations> ordered = new ArrayList<>(configurations);
ordered.sort(COMPARATOR);
List<Configurations> collated = collate(ordered);
List<Configurations> collated = collate(configurations);
LinkedHashSet<Class<?>> classes = collated.stream()
.flatMap(Configurations::streamClasses)
.collect(Collectors.toCollection(LinkedHashSet::new));
return ClassUtils.toClassArray(classes);
}
private static Stream<Class<?>> streamClasses(Configurations configurations) {
return configurations.getClasses().stream();
}
private static List<Configurations> collate(List<Configurations> orderedConfigurations) {
/**
* Collate the given configuration by sorting and merging them.
* @param configurations the source configuration
* @return the collated configurations
* @since 3.4.0
*/
public static List<Configurations> collate(Collection<Configurations> configurations) {
LinkedList<Configurations> collated = new LinkedList<>();
for (Configurations item : orderedConfigurations) {
if (collated.isEmpty() || collated.getLast().getClass() != item.getClass()) {
collated.add(item);
for (Configurations configuration : sortConfigurations(configurations)) {
if (collated.isEmpty() || collated.getLast().getClass() != configuration.getClass()) {
collated.add(configuration);
}
else {
collated.set(collated.size() - 1, collated.getLast().merge(item));
collated.set(collated.size() - 1, collated.getLast().merge(configuration));
}
}
return collated;
}
private static List<Configurations> sortConfigurations(Collection<Configurations> configurations) {
List<Configurations> sorted = new ArrayList<>(configurations);
sorted.sort(COMPARATOR);
return sorted;
}
private static Stream<Class<?>> streamClasses(Configurations configurations) {
return configurations.getClasses().stream();
}
}

View File

@@ -23,7 +23,9 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import org.junit.jupiter.api.Test;
@@ -87,6 +89,18 @@ class ConfigurationsTests {
OutputStream.class, String.class);
}
@Test
void getBeanNameWhenNoFunctionReturnsNull() {
Configurations configurations = new TestConfigurations(Short.class);
assertThat(configurations.getBeanName(Short.class)).isNull();
}
@Test
void getBeanNameWhenFunctionReturnsBeanName() {
Configurations configurations = new TestConfigurations(Sorter.instance, List.of(Short.class), Class::getName);
assertThat(configurations.getBeanName(Short.class)).isEqualTo(Short.class.getName());
}
@Order(Ordered.HIGHEST_PRECEDENCE)
static class TestConfigurations extends Configurations {
@@ -95,7 +109,12 @@ class ConfigurationsTests {
}
TestConfigurations(UnaryOperator<Collection<Class<?>>> sorter, Class<?>... classes) {
super(sorter, Arrays.asList(classes));
this(sorter, Arrays.asList(classes), null);
}
TestConfigurations(UnaryOperator<Collection<Class<?>>> sorter, Collection<Class<?>> classes,
Function<Class<?>, String> beanNameGenerator) {
super(sorter, classes, beanNameGenerator);
}
TestConfigurations(Collection<Class<?>> classes) {
@@ -117,7 +136,7 @@ class ConfigurationsTests {
}
protected TestSortedConfigurations(Collection<Class<?>> classes) {
super(Sorter.instance, classes);
super(Sorter.instance, classes, null);
}
@Override