Provide support for deprecated auto-configuration classes
Support `AutoConfiguration.replacements` file that can be placed alongside an `AutoConfiguration.imports` to replace deprecated auto-configurations. Closes gh-14860
This commit is contained in:
parent
ddd0d898c2
commit
72588fcda7
|
|
@ -86,6 +86,8 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
|
||||
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
|
||||
|
||||
private final Class<?> autoConfigurationAnnotation;
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private Environment environment;
|
||||
|
|
@ -96,6 +98,17 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
|
||||
private volatile ConfigurationClassFilter configurationClassFilter;
|
||||
|
||||
private volatile AutoConfigurationReplacements autoConfigurationReplacements;
|
||||
|
||||
public AutoConfigurationImportSelector() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
AutoConfigurationImportSelector(Class<?> autoConfigurationAnnotation) {
|
||||
this.autoConfigurationAnnotation = (autoConfigurationAnnotation != null) ? autoConfigurationAnnotation
|
||||
: AutoConfiguration.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata annotationMetadata) {
|
||||
if (!isEnabled(annotationMetadata)) {
|
||||
|
|
@ -179,11 +192,12 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
* @return a list of candidate configurations
|
||||
*/
|
||||
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
|
||||
ImportCandidates importCandidates = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader());
|
||||
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
|
||||
getBeanClassLoader());
|
||||
List<String> configurations = importCandidates.getCandidates();
|
||||
Assert.notEmpty(configurations,
|
||||
"No auto configuration classes found in "
|
||||
+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
|
||||
"No auto configuration classes found in " + "META-INF/spring/"
|
||||
+ this.autoConfigurationAnnotation.getName() + ".imports. If you "
|
||||
+ "are using a custom packaging, make sure that file is correct.");
|
||||
return configurations;
|
||||
}
|
||||
|
|
@ -227,7 +241,7 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
excluded.addAll(asList(attributes, "exclude"));
|
||||
excluded.addAll(asList(attributes, "excludeName"));
|
||||
excluded.addAll(getExcludeAutoConfigurationsProperty());
|
||||
return excluded;
|
||||
return getAutoConfigurationReplacements().replaceAll(excluded);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -268,6 +282,16 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
return configurationClassFilter;
|
||||
}
|
||||
|
||||
private AutoConfigurationReplacements getAutoConfigurationReplacements() {
|
||||
AutoConfigurationReplacements autoConfigurationReplacements = this.autoConfigurationReplacements;
|
||||
if (autoConfigurationReplacements == null) {
|
||||
autoConfigurationReplacements = AutoConfigurationReplacements.load(this.autoConfigurationAnnotation,
|
||||
this.beanClassLoader);
|
||||
this.autoConfigurationReplacements = autoConfigurationReplacements;
|
||||
}
|
||||
return autoConfigurationReplacements;
|
||||
}
|
||||
|
||||
protected final <T> List<T> removeDuplicates(List<T> list) {
|
||||
return new ArrayList<>(new LinkedHashSet<>(list));
|
||||
}
|
||||
|
|
@ -409,6 +433,8 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
|
||||
private AutoConfigurationMetadata autoConfigurationMetadata;
|
||||
|
||||
private AutoConfigurationReplacements autoConfigurationReplacements;
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
|
|
@ -430,7 +456,15 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
() -> String.format("Only %s implementations are supported, got %s",
|
||||
AutoConfigurationImportSelector.class.getSimpleName(),
|
||||
deferredImportSelector.getClass().getName()));
|
||||
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
|
||||
AutoConfigurationImportSelector autoConfigurationImportSelector = (AutoConfigurationImportSelector) deferredImportSelector;
|
||||
AutoConfigurationReplacements autoConfigurationReplacements = autoConfigurationImportSelector
|
||||
.getAutoConfigurationReplacements();
|
||||
Assert.state(
|
||||
this.autoConfigurationReplacements == null
|
||||
|| this.autoConfigurationReplacements.equals(autoConfigurationReplacements),
|
||||
"Auto-configuration replacements must be the same for each call to process");
|
||||
this.autoConfigurationReplacements = autoConfigurationReplacements;
|
||||
AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector
|
||||
.getAutoConfigurationEntry(annotationMetadata);
|
||||
this.autoConfigurationEntries.add(autoConfigurationEntry);
|
||||
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
|
||||
|
|
@ -452,7 +486,6 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
processedConfigurations.removeAll(allExclusions);
|
||||
|
||||
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
|
||||
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
|
||||
.toList();
|
||||
|
|
@ -467,7 +500,8 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
|
|||
|
||||
private List<String> sortAutoConfigurations(Set<String> configurations,
|
||||
AutoConfigurationMetadata autoConfigurationMetadata) {
|
||||
return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
|
||||
return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata,
|
||||
this.autoConfigurationReplacements::replace)
|
||||
.getInPriorityOrder(configurations);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2012-2024 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
|
||||
*
|
||||
* https://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.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.context.annotation.ImportCandidates;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Contains auto-configuration replacements used to handle deprecated or moved
|
||||
* auto-configurations which may still be referenced by
|
||||
* {@link AutoConfigureBefore @AutoConfigureBefore},
|
||||
* {@link AutoConfigureAfter @AutoConfigureAfter} or exclusions.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class AutoConfigurationReplacements {
|
||||
|
||||
private static final String LOCATION = "META-INF/spring/%s.replacements";
|
||||
|
||||
private final Map<String, String> replacements;
|
||||
|
||||
private AutoConfigurationReplacements(Map<String, String> replacements) {
|
||||
this.replacements = Map.copyOf(replacements);
|
||||
}
|
||||
|
||||
Set<String> replaceAll(Set<String> classNames) {
|
||||
Set<String> replaced = new LinkedHashSet<>(classNames.size());
|
||||
for (String className : classNames) {
|
||||
replaced.add(replace(className));
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
String replace(String className) {
|
||||
return this.replacements.getOrDefault(className, className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return this.replacements.equals(((AutoConfigurationReplacements) obj).replacements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.replacements.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the relocations from the classpath. Relactions are stored in files named
|
||||
* {@code META-INF/spring/full-qualified-annotation-name.replacements} on the
|
||||
* classpath. The file is loaded using {@link Properties#load(java.io.InputStream)}
|
||||
* with each entry containing an auto-configuration class name as the key and the
|
||||
* replacement class name as the value.
|
||||
* @param annotation annotation to load
|
||||
* @param classLoader class loader to use for loading
|
||||
* @return list of names of annotated classes
|
||||
*/
|
||||
static AutoConfigurationReplacements load(Class<?> annotation, ClassLoader classLoader) {
|
||||
Assert.notNull(annotation, "'annotation' must not be null");
|
||||
ClassLoader classLoaderToUse = decideClassloader(classLoader);
|
||||
String location = String.format(LOCATION, annotation.getName());
|
||||
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
|
||||
Map<String, String> replacements = new HashMap<>();
|
||||
while (urls.hasMoreElements()) {
|
||||
URL url = urls.nextElement();
|
||||
replacements.putAll(readReplacements(url));
|
||||
}
|
||||
return new AutoConfigurationReplacements(replacements);
|
||||
}
|
||||
|
||||
private static ClassLoader decideClassloader(ClassLoader classLoader) {
|
||||
if (classLoader == null) {
|
||||
return ImportCandidates.class.getClassLoader();
|
||||
}
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
private static Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
|
||||
try {
|
||||
return classLoader.getResources(location);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalArgumentException("Failed to load configurations from location [" + location + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static Map<String, String> readReplacements(URL url) {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
|
||||
Properties properties = new Properties();
|
||||
properties.load(reader);
|
||||
return (Map) properties;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalArgumentException("Unable to load replacements from location [" + url + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.boot.autoconfigure;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
|
@ -27,6 +28,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
|
|
@ -47,11 +49,14 @@ class AutoConfigurationSorter {
|
|||
|
||||
private final AutoConfigurationMetadata autoConfigurationMetadata;
|
||||
|
||||
private final UnaryOperator<String> replacementMapper;
|
||||
|
||||
AutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory,
|
||||
AutoConfigurationMetadata autoConfigurationMetadata) {
|
||||
AutoConfigurationMetadata autoConfigurationMetadata, UnaryOperator<String> replacementMapper) {
|
||||
Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null");
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.autoConfigurationMetadata = autoConfigurationMetadata;
|
||||
this.replacementMapper = replacementMapper;
|
||||
}
|
||||
|
||||
List<String> getInPriorityOrder(Collection<String> classNames) {
|
||||
|
|
@ -108,7 +113,7 @@ class AutoConfigurationSorter {
|
|||
() -> "AutoConfigure cycle detected between " + current + " and " + after);
|
||||
}
|
||||
|
||||
private static class AutoConfigurationClasses {
|
||||
private class AutoConfigurationClasses {
|
||||
|
||||
private final Map<String, AutoConfigurationClass> classes = new LinkedHashMap<>();
|
||||
|
||||
|
|
@ -157,7 +162,7 @@ class AutoConfigurationSorter {
|
|||
|
||||
}
|
||||
|
||||
private static class AutoConfigurationClass {
|
||||
private class AutoConfigurationClass {
|
||||
|
||||
private final String className;
|
||||
|
||||
|
|
@ -192,20 +197,36 @@ class AutoConfigurationSorter {
|
|||
|
||||
Set<String> getBefore() {
|
||||
if (this.before == null) {
|
||||
this.before = (wasProcessed() ? this.autoConfigurationMetadata.getSet(this.className,
|
||||
"AutoConfigureBefore", Collections.emptySet()) : getAnnotationValue(AutoConfigureBefore.class));
|
||||
this.before = getClassNames("AutoConfigureBefore", AutoConfigureBefore.class);
|
||||
}
|
||||
return this.before;
|
||||
}
|
||||
|
||||
Set<String> getAfter() {
|
||||
if (this.after == null) {
|
||||
this.after = (wasProcessed() ? this.autoConfigurationMetadata.getSet(this.className,
|
||||
"AutoConfigureAfter", Collections.emptySet()) : getAnnotationValue(AutoConfigureAfter.class));
|
||||
this.after = getClassNames("AutoConfigureAfter", AutoConfigureAfter.class);
|
||||
}
|
||||
return this.after;
|
||||
}
|
||||
|
||||
private Set<String> getClassNames(String metadataKey, Class<? extends Annotation> annotation) {
|
||||
Set<String> annotationValue = wasProcessed()
|
||||
? this.autoConfigurationMetadata.getSet(this.className, metadataKey, Collections.emptySet())
|
||||
: getAnnotationValue(annotation);
|
||||
return applyReplacements(annotationValue);
|
||||
}
|
||||
|
||||
private Set<String> applyReplacements(Set<String> values) {
|
||||
if (AutoConfigurationSorter.this.replacementMapper == null) {
|
||||
return values;
|
||||
}
|
||||
Set<String> replaced = new LinkedHashSet<>(values);
|
||||
for (String value : values) {
|
||||
replaced.add(AutoConfigurationSorter.this.replacementMapper.apply(value));
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
private int getOrder() {
|
||||
if (wasProcessed()) {
|
||||
return this.autoConfigurationMetadata.getInteger(this.className, "AutoConfigureOrder",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.context.annotation.Configurations;
|
||||
|
|
@ -36,22 +37,33 @@ import org.springframework.util.ClassUtils;
|
|||
*/
|
||||
public class AutoConfigurations extends Configurations implements Ordered {
|
||||
|
||||
private static final AutoConfigurationSorter SORTER = new AutoConfigurationSorter(new SimpleMetadataReaderFactory(),
|
||||
null);
|
||||
private static final SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
|
||||
private static final int ORDER = AutoConfigurationImportSelector.ORDER;
|
||||
|
||||
static final AutoConfigurationReplacements replacements = AutoConfigurationReplacements
|
||||
.load(AutoConfiguration.class, null);
|
||||
|
||||
private final UnaryOperator<String> replacementMapper;
|
||||
|
||||
protected AutoConfigurations(Collection<Class<?>> classes) {
|
||||
super(classes);
|
||||
this(replacements::replace, classes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<?>> sort(Collection<Class<?>> classes) {
|
||||
List<String> names = classes.stream().map(Class::getName).toList();
|
||||
List<String> sorted = SORTER.getInPriorityOrder(names);
|
||||
return sorted.stream()
|
||||
.map((className) -> ClassUtils.resolveClassName(className, null))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
AutoConfigurations(UnaryOperator<String> replacementMapper, Collection<Class<?>> classes) {
|
||||
super(sorter(replacementMapper), classes);
|
||||
this.replacementMapper = replacementMapper;
|
||||
}
|
||||
|
||||
private static UnaryOperator<Collection<Class<?>>> sorter(UnaryOperator<String> replacementMapper) {
|
||||
AutoConfigurationSorter sorter = new AutoConfigurationSorter(metadataReaderFactory, null, replacementMapper);
|
||||
return (classes) -> {
|
||||
List<String> names = classes.stream().map(Class::getName).map(replacementMapper::apply).toList();
|
||||
List<String> sorted = sorter.getInPriorityOrder(names);
|
||||
return sorted.stream()
|
||||
.map((className) -> ClassUtils.resolveClassName(className, null))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -61,7 +73,7 @@ public class AutoConfigurations extends Configurations implements Ordered {
|
|||
|
||||
@Override
|
||||
protected AutoConfigurations merge(Set<Class<?>> mergedClasses) {
|
||||
return new AutoConfigurations(mergedClasses);
|
||||
return new AutoConfigurations(this.replacementMapper, mergedClasses);
|
||||
}
|
||||
|
||||
public static AutoConfigurations of(Class<?>... classes) {
|
||||
|
|
|
|||
|
|
@ -16,15 +16,22 @@
|
|||
|
||||
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;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
|
@ -34,6 +41,8 @@ import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration
|
|||
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
|
||||
import org.springframework.boot.context.annotation.ImportCandidates;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DeferredImportSelector.Group;
|
||||
import org.springframework.context.annotation.DeferredImportSelector.Group.Entry;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
|
@ -50,7 +59,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
|||
*/
|
||||
class AutoConfigurationImportSelectorTests {
|
||||
|
||||
private final TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector();
|
||||
private final TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector(null);
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
|
|
@ -60,9 +69,7 @@ class AutoConfigurationImportSelectorTests {
|
|||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.importSelector.setBeanFactory(this.beanFactory);
|
||||
this.importSelector.setEnvironment(this.environment);
|
||||
this.importSelector.setResourceLoader(new DefaultResourceLoader());
|
||||
setupImportSelector(this.importSelector);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -151,6 +158,17 @@ class AutoConfigurationImportSelectorTests {
|
|||
ThymeleafAutoConfiguration.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void removedExclusionsAreApplied() {
|
||||
TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector(
|
||||
TestAutoConfiguration.class);
|
||||
setupImportSelector(importSelector);
|
||||
AnnotationMetadata metadata = AnnotationMetadata.introspect(BasicEnableAutoConfiguration.class);
|
||||
assertThat(importSelector.selectImports(metadata)).contains(ReplacementAutoConfiguration.class.getName());
|
||||
this.environment.setProperty("spring.autoconfigure.exclude", DeprecatedAutoConfiguration.class.getName());
|
||||
assertThat(importSelector.selectImports(metadata)).doesNotContain(ReplacementAutoConfiguration.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonAutoConfigurationClassExclusionsShouldThrowException() {
|
||||
assertThatIllegalStateException()
|
||||
|
|
@ -208,6 +226,22 @@ class AutoConfigurationImportSelectorTests {
|
|||
assertThat(this.importSelector.getExclusionFilter().test("com.example.C")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void soringConsidersReplacements() {
|
||||
TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector(
|
||||
TestAutoConfiguration.class);
|
||||
setupImportSelector(importSelector);
|
||||
AnnotationMetadata metadata = AnnotationMetadata.introspect(BasicEnableAutoConfiguration.class);
|
||||
assertThat(importSelector.selectImports(metadata)).containsExactly(
|
||||
AfterDeprecatedAutoConfiguration.class.getName(), ReplacementAutoConfiguration.class.getName());
|
||||
Group group = BeanUtils.instantiateClass(importSelector.getImportGroup());
|
||||
((BeanFactoryAware) group).setBeanFactory(this.beanFactory);
|
||||
group.process(metadata, importSelector);
|
||||
Stream<Entry> imports = StreamSupport.stream(group.selectImports().spliterator(), false);
|
||||
assertThat(imports.map(Entry::getImportClassName)).containsExactly(ReplacementAutoConfiguration.class.getName(),
|
||||
AfterDeprecatedAutoConfiguration.class.getName());
|
||||
}
|
||||
|
||||
private String[] selectImports(Class<?> source) {
|
||||
return this.importSelector.selectImports(AnnotationMetadata.introspect(source));
|
||||
}
|
||||
|
|
@ -216,10 +250,20 @@ class AutoConfigurationImportSelectorTests {
|
|||
return ImportCandidates.load(AutoConfiguration.class, getClass().getClassLoader()).getCandidates();
|
||||
}
|
||||
|
||||
private void setupImportSelector(TestAutoConfigurationImportSelector importSelector) {
|
||||
importSelector.setBeanFactory(this.beanFactory);
|
||||
importSelector.setEnvironment(this.environment);
|
||||
importSelector.setResourceLoader(new DefaultResourceLoader());
|
||||
}
|
||||
|
||||
private final class TestAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
|
||||
|
||||
private AutoConfigurationImportEvent lastEvent;
|
||||
|
||||
TestAutoConfigurationImportSelector(Class<?> autoConfigurationAnnotation) {
|
||||
super(autoConfigurationAnnotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
|
||||
return AutoConfigurationImportSelectorTests.this.filters;
|
||||
|
|
@ -320,4 +364,23 @@ class AutoConfigurationImportSelectorTests {
|
|||
|
||||
}
|
||||
|
||||
static class DeprecatedAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
static class ReplacementAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@AutoConfigureAfter(DeprecatedAutoConfiguration.class)
|
||||
static class AfterDeprecatedAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface TestAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2012-2024 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
|
||||
*
|
||||
* https://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;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutoConfigurationReplacements}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfigurationReplacementsTests {
|
||||
|
||||
private final AutoConfigurationReplacements replacements = AutoConfigurationReplacements
|
||||
.load(TestAutoConfigurationReplacements.class, null);
|
||||
|
||||
@Test
|
||||
void replaceWhenMatchReplacesClassName() {
|
||||
assertThat(this.replacements.replace("com.example.A1")).isEqualTo("com.example.A2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceWhenNoMatchReturnsOriginalClassName() {
|
||||
assertThat(this.replacements.replace("com.example.Z1")).isEqualTo("com.example.Z1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceAllReplacesAllMatching() {
|
||||
Set<String> classNames = new LinkedHashSet<>(
|
||||
List.of("com.example.A1", "com.example.B1", "com.example.Y1", "com.example.Z1"));
|
||||
assertThat(this.replacements.replaceAll(classNames)).containsExactly("com.example.A2", "com.example.B2",
|
||||
"com.example.Y1", "com.example.Z1");
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface TestAutoConfigurationReplacements {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -62,10 +63,14 @@ class AutoConfigurationSorterTests {
|
|||
|
||||
private static final String A3 = AutoConfigureA3.class.getName();
|
||||
|
||||
private static final String A_WITH_REPLACED = AutoConfigureAWithReplaced.class.getName();
|
||||
|
||||
private static final String B = AutoConfigureB.class.getName();
|
||||
|
||||
private static final String B2 = AutoConfigureB2.class.getName();
|
||||
|
||||
private static final String B_WITH_REPLACED = AutoConfigureBWithReplaced.class.getName();
|
||||
|
||||
private static final String C = AutoConfigureC.class.getName();
|
||||
|
||||
private static final String D = AutoConfigureD.class.getName();
|
||||
|
|
@ -86,13 +91,16 @@ class AutoConfigurationSorterTests {
|
|||
|
||||
private static final String Z2 = AutoConfigureZ2.class.getName();
|
||||
|
||||
private static final UnaryOperator<String> REPLACEMENT_MAPPER = (name) -> name.replace("Deprecated", "");
|
||||
|
||||
private AutoConfigurationSorter sorter;
|
||||
|
||||
private AutoConfigurationMetadata autoConfigurationMetadata = mock(AutoConfigurationMetadata.class);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.sorter = new AutoConfigurationSorter(new SkipCycleMetadataReaderFactory(), this.autoConfigurationMetadata);
|
||||
this.sorter = new AutoConfigurationSorter(new SkipCycleMetadataReaderFactory(), this.autoConfigurationMetadata,
|
||||
REPLACEMENT_MAPPER);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -117,11 +125,17 @@ class AutoConfigurationSorterTests {
|
|||
void byAutoConfigureAfterAliasForWithProperties() throws Exception {
|
||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
|
||||
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A3, B2, C);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER);
|
||||
List<String> actual = getInPriorityOrder(A3, B2, C);
|
||||
assertThat(actual).containsExactly(C, B2, A3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void byAutoConfigureAfterWithDeprecated() {
|
||||
List<String> actual = getInPriorityOrder(A_WITH_REPLACED, B_WITH_REPLACED, C);
|
||||
assertThat(actual).containsExactly(C, B_WITH_REPLACED, A_WITH_REPLACED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void byAutoConfigureBefore() {
|
||||
List<String> actual = getInPriorityOrder(X, Y, Z);
|
||||
|
|
@ -138,7 +152,7 @@ class AutoConfigurationSorterTests {
|
|||
void byAutoConfigureBeforeAliasForWithProperties() throws Exception {
|
||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
|
||||
this.autoConfigurationMetadata = getAutoConfigurationMetadata(X, Y2, Z2);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER);
|
||||
List<String> actual = getInPriorityOrder(X, Y2, Z2);
|
||||
assertThat(actual).containsExactly(Z2, Y2, X);
|
||||
}
|
||||
|
|
@ -175,7 +189,8 @@ class AutoConfigurationSorterTests {
|
|||
|
||||
@Test
|
||||
void byAutoConfigureAfterWithCycle() {
|
||||
this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(), this.autoConfigurationMetadata);
|
||||
this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(), this.autoConfigurationMetadata,
|
||||
REPLACEMENT_MAPPER);
|
||||
assertThatIllegalStateException().isThrownBy(() -> getInPriorityOrder(A, B, C, D))
|
||||
.withMessageContaining("AutoConfigure cycle detected");
|
||||
}
|
||||
|
|
@ -184,7 +199,7 @@ class AutoConfigurationSorterTests {
|
|||
void usesAnnotationPropertiesWhenPossible() throws Exception {
|
||||
MetadataReaderFactory readerFactory = new SkipCycleMetadataReaderFactory();
|
||||
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A2, B, C, W2, X);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER);
|
||||
List<String> actual = getInPriorityOrder(A2, B, C, W2, X);
|
||||
assertThat(actual).containsExactly(C, W2, B, A2, X);
|
||||
}
|
||||
|
|
@ -193,7 +208,7 @@ class AutoConfigurationSorterTests {
|
|||
void useAnnotationWithNoDirectLink() throws Exception {
|
||||
MetadataReaderFactory readerFactory = new SkipCycleMetadataReaderFactory();
|
||||
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A, B, E);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER);
|
||||
List<String> actual = getInPriorityOrder(A, E);
|
||||
assertThat(actual).containsExactly(E, A);
|
||||
}
|
||||
|
|
@ -202,7 +217,7 @@ class AutoConfigurationSorterTests {
|
|||
void useAnnotationWithNoDirectLinkAndCycle() throws Exception {
|
||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
|
||||
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A, B, D);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata);
|
||||
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER);
|
||||
assertThatIllegalStateException().isThrownBy(() -> getInPriorityOrder(D, B))
|
||||
.withMessageContaining("AutoConfigure cycle detected");
|
||||
}
|
||||
|
|
@ -307,6 +322,11 @@ class AutoConfigurationSorterTests {
|
|||
|
||||
}
|
||||
|
||||
@AutoConfigureAfter(AutoConfigureBWithReplaced.class)
|
||||
public static class AutoConfigureAWithReplaced {
|
||||
|
||||
}
|
||||
|
||||
@AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class })
|
||||
static class AutoConfigureB {
|
||||
|
||||
|
|
@ -317,10 +337,21 @@ class AutoConfigurationSorterTests {
|
|||
|
||||
}
|
||||
|
||||
@AutoConfigureAfter({ DeprecatedAutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class })
|
||||
public static class AutoConfigureBWithReplaced {
|
||||
|
||||
}
|
||||
|
||||
static class AutoConfigureC {
|
||||
|
||||
}
|
||||
|
||||
// @DeprecatedAutoConfiguration(replacement =
|
||||
// "org.springframework.boot.autoconfigure.AutoConfigurationSorterTests$AutoConfigureC")
|
||||
public static class DeprecatedAutoConfigureC {
|
||||
|
||||
}
|
||||
|
||||
@AutoConfigureAfter(AutoConfigureA.class)
|
||||
static class AutoConfigureD {
|
||||
|
||||
|
|
@ -354,6 +385,12 @@ class AutoConfigurationSorterTests {
|
|||
|
||||
}
|
||||
|
||||
// @DeprecatedAutoConfiguration(replacement =
|
||||
// "org.springframework.boot.autoconfigure.AutoConfigurationSorterTests$AutoConfigureY")
|
||||
public static class DeprecatedAutoConfigureY {
|
||||
|
||||
}
|
||||
|
||||
@AutoConfigureBefore(AutoConfigureY.class)
|
||||
static class AutoConfigureZ {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2024 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,8 @@
|
|||
|
||||
package org.springframework.boot.autoconfigure;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.annotation.Configurations;
|
||||
|
|
@ -36,6 +38,26 @@ class AutoConfigurationsTests {
|
|||
AutoConfigureA.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasReplacementForAutoConfigureAfterShouldCreateOrderedConfigurations() {
|
||||
Configurations configurations = new AutoConfigurations(this::replaceB,
|
||||
Arrays.asList(AutoConfigureA.class, AutoConfigureB2.class));
|
||||
assertThat(Configurations.getClasses(configurations)).containsExactly(AutoConfigureB2.class,
|
||||
AutoConfigureA.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasReplacementForClassShouldReplaceClass() {
|
||||
Configurations configurations = new AutoConfigurations(this::replaceB,
|
||||
Arrays.asList(AutoConfigureA.class, AutoConfigureB.class));
|
||||
assertThat(Configurations.getClasses(configurations)).containsExactly(AutoConfigureB2.class,
|
||||
AutoConfigureA.class);
|
||||
}
|
||||
|
||||
private String replaceB(String className) {
|
||||
return (!AutoConfigureB.class.getName().equals(className)) ? className : AutoConfigureB2.class.getName();
|
||||
}
|
||||
|
||||
@AutoConfigureAfter(AutoConfigureB.class)
|
||||
static class AutoConfigureA {
|
||||
|
||||
|
|
@ -45,4 +67,8 @@ class AutoConfigurationsTests {
|
|||
|
||||
}
|
||||
|
||||
static class AutoConfigureB2 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
|
||||
|
|
@ -29,8 +30,9 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
|
|||
*/
|
||||
public class TestAutoConfigurationSorter extends AutoConfigurationSorter {
|
||||
|
||||
public TestAutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory) {
|
||||
super(metadataReaderFactory, AutoConfigurationMetadataLoader.loadMetadata(new Properties()));
|
||||
public TestAutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory,
|
||||
UnaryOperator<String> replacementMapper) {
|
||||
super(metadataReaderFactory, AutoConfigurationMetadataLoader.loadMetadata(new Properties()), replacementMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$AfterDeprecatedAutoConfiguration
|
||||
org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$ReplacementAutoConfiguration
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$DeprecatedAutoConfiguration=\
|
||||
org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$ReplacementAutoConfiguration
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
com.example.A1=com.example.A2
|
||||
com.example.B1=com.example.B2
|
||||
|
|
@ -52,6 +52,26 @@ The order in which those beans are subsequently created is unaffected and is det
|
|||
|
||||
|
||||
|
||||
[[features.developing-auto-configuration.locating-auto-configuration-candidates.deprecating]]
|
||||
=== Deprecating and Replacing Auto-configuration Classes
|
||||
|
||||
You may need to occasionally deprecate auto-configuration classes and offer an alternative.
|
||||
For example, you may want to change the package name where your auto-configuration class resides.
|
||||
|
||||
Since auto-configuration classes may be referenced in `before`/`after` ordering and `excludes`, you'll need to add an additional file that tells Spring Boot how to deal with replacements.
|
||||
To define replacements, create a `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements` file indicating the link between the old class and the new one.
|
||||
|
||||
For example:
|
||||
|
||||
[source]
|
||||
----
|
||||
com.mycorp.libx.autoconfigure.LibXAutoConfiguration=com.mycorp.libx.autoconfigure.core.LibXAutoConfiguration
|
||||
----
|
||||
|
||||
NOTE: The `AutoConfiguration.imports` file should also be updated to _only_ reference the replacement class.
|
||||
|
||||
|
||||
|
||||
[[features.developing-auto-configuration.condition-annotations]]
|
||||
== Condition Annotations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
|
|
@ -25,6 +25,7 @@ import java.util.LinkedHashSet;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
|
@ -60,11 +61,32 @@ public abstract class Configurations {
|
|||
private static final Comparator<Object> COMPARATOR = OrderComparator.INSTANCE
|
||||
.thenComparing((other) -> other.getClass().getName());
|
||||
|
||||
private final UnaryOperator<Collection<Class<?>>> sorter;
|
||||
|
||||
private final Set<Class<?>> classes;
|
||||
|
||||
/**
|
||||
* Create a new {@link Configurations} instance.
|
||||
* @param classes the configuration classes
|
||||
*/
|
||||
protected Configurations(Collection<Class<?>> classes) {
|
||||
Assert.notNull(classes, "Classes must not be null");
|
||||
Collection<Class<?>> sorted = sort(classes);
|
||||
this.sorter = null;
|
||||
this.classes = Collections.unmodifiableSet(new LinkedHashSet<>(sorted));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Configurations} instance.
|
||||
* @param sorter a {@link UnaryOperator} used to sort the configurations
|
||||
* @param classes the configuration classes
|
||||
* @since 3.4.0
|
||||
*/
|
||||
protected Configurations(UnaryOperator<Collection<Class<?>>> sorter, Collection<Class<?>> classes) {
|
||||
Assert.notNull(sorter, "Sorter must not be null");
|
||||
Assert.notNull(classes, "Classes must not be null");
|
||||
Collection<Class<?>> sorted = sorter.apply(classes);
|
||||
this.sorter = sorter;
|
||||
this.classes = Collections.unmodifiableSet(new LinkedHashSet<>(sorted));
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +94,10 @@ public abstract class Configurations {
|
|||
* Sort configuration classes into the order that they should be applied.
|
||||
* @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)}
|
||||
*/
|
||||
@Deprecated(since = "3.4.0", forRemoval = true)
|
||||
protected Collection<Class<?>> sort(Collection<Class<?>> classes) {
|
||||
return classes;
|
||||
}
|
||||
|
|
@ -90,6 +115,9 @@ public abstract class Configurations {
|
|||
protected Configurations merge(Configurations other) {
|
||||
Set<Class<?>> mergedClasses = new LinkedHashSet<>(getClasses());
|
||||
mergedClasses.addAll(other.getClasses());
|
||||
if (this.sorter != null) {
|
||||
mergedClasses = new LinkedHashSet<>(this.sorter.apply(mergedClasses));
|
||||
}
|
||||
return merge(mergedClasses);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
|
|
@ -67,9 +67,8 @@ public final class ImportCandidates implements Iterable<String> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Loads the names of import candidates from the classpath.
|
||||
*
|
||||
* The names of the import candidates are stored in files named
|
||||
* Loads the names of import candidates from the classpath. The names of the import
|
||||
* candidates are stored in files named
|
||||
* {@code META-INF/spring/full-qualified-annotation-name.imports} on the classpath.
|
||||
* Every line contains the full qualified name of the candidate class. Comments are
|
||||
* supported using the # character.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
|
|
@ -24,6 +24,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
@ -43,32 +44,61 @@ class ConfigurationsTests {
|
|||
|
||||
@Test
|
||||
void createWhenClassesIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new TestConfigurations(null))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new TestConfigurations((Collection<Class<?>>) null))
|
||||
.withMessageContaining("Classes must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShouldSortClasses() {
|
||||
TestSortedConfigurations configurations = new TestSortedConfigurations(
|
||||
@Deprecated(since = "3.4.0", forRemoval = true)
|
||||
void createShouldSortClassesUsingSortMethod() {
|
||||
TestDeprecatedSortedConfigurations configurations = new TestDeprecatedSortedConfigurations(
|
||||
Arrays.asList(OutputStream.class, InputStream.class));
|
||||
assertThat(configurations.getClasses()).containsExactly(InputStream.class, OutputStream.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassesShouldMergeByClassAndSort() {
|
||||
Configurations c1 = new TestSortedConfigurations(Arrays.asList(OutputStream.class, InputStream.class));
|
||||
@Deprecated(since = "3.4.0", forRemoval = true)
|
||||
void getClassesShouldMergeByClassAndSortUsingSortMethod() {
|
||||
Configurations c1 = new TestDeprecatedSortedConfigurations(
|
||||
Arrays.asList(OutputStream.class, InputStream.class));
|
||||
Configurations c2 = new TestConfigurations(Collections.singletonList(Short.class));
|
||||
Configurations c3 = new TestSortedConfigurations(Arrays.asList(String.class, Integer.class));
|
||||
Configurations c3 = new TestDeprecatedSortedConfigurations(Arrays.asList(String.class, Integer.class));
|
||||
Configurations c4 = new TestConfigurations(Arrays.asList(Long.class, Byte.class));
|
||||
Class<?>[] classes = Configurations.getClasses(c1, c2, c3, c4);
|
||||
assertThat(classes).containsExactly(Short.class, Long.class, Byte.class, InputStream.class, Integer.class,
|
||||
OutputStream.class, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShouldSortClasses() {
|
||||
TestConfigurations configurations = new TestConfigurations(Sorter.instance, OutputStream.class,
|
||||
InputStream.class);
|
||||
assertThat(configurations.getClasses()).containsExactly(InputStream.class, OutputStream.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassesShouldMergeByClassAndSort() {
|
||||
Configurations c1 = new TestSortedConfigurations(OutputStream.class, InputStream.class);
|
||||
Configurations c2 = new TestConfigurations(Short.class);
|
||||
Configurations c3 = new TestSortedConfigurations(String.class, Integer.class);
|
||||
Configurations c4 = new TestConfigurations(Long.class, Byte.class);
|
||||
Class<?>[] classes = Configurations.getClasses(c1, c2, c3, c4);
|
||||
assertThat(classes).containsExactly(Short.class, Long.class, Byte.class, InputStream.class, Integer.class,
|
||||
OutputStream.class, String.class);
|
||||
}
|
||||
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
static class TestConfigurations extends Configurations {
|
||||
|
||||
protected TestConfigurations(Collection<Class<?>> classes) {
|
||||
TestConfigurations(Class<?>... classes) {
|
||||
this(Arrays.asList(classes));
|
||||
}
|
||||
|
||||
TestConfigurations(UnaryOperator<Collection<Class<?>>> sorter, Class<?>... classes) {
|
||||
super(sorter, Arrays.asList(classes));
|
||||
}
|
||||
|
||||
TestConfigurations(Collection<Class<?>> classes) {
|
||||
super(classes);
|
||||
}
|
||||
|
||||
|
|
@ -82,15 +112,12 @@ class ConfigurationsTests {
|
|||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
static class TestSortedConfigurations extends Configurations {
|
||||
|
||||
protected TestSortedConfigurations(Collection<Class<?>> classes) {
|
||||
super(classes);
|
||||
protected TestSortedConfigurations(Class<?>... classes) {
|
||||
this(Arrays.asList(classes));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<?>> sort(Collection<Class<?>> classes) {
|
||||
ArrayList<Class<?>> sorted = new ArrayList<>(classes);
|
||||
sorted.sort(Comparator.comparing(ClassUtils::getShortName));
|
||||
return sorted;
|
||||
protected TestSortedConfigurations(Collection<Class<?>> classes) {
|
||||
super(Sorter.instance, classes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -100,4 +127,38 @@ class ConfigurationsTests {
|
|||
|
||||
}
|
||||
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
@SuppressWarnings("removal")
|
||||
static class TestDeprecatedSortedConfigurations extends Configurations {
|
||||
|
||||
protected TestDeprecatedSortedConfigurations(Collection<Class<?>> classes) {
|
||||
super(classes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<?>> sort(Collection<Class<?>> classes) {
|
||||
return Sorter.instance.apply(classes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Configurations merge(Set<Class<?>> mergedClasses) {
|
||||
return new TestDeprecatedSortedConfigurations(mergedClasses);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Sorter implements UnaryOperator<Collection<Class<?>>> {
|
||||
|
||||
static final Sorter instance = new Sorter();
|
||||
|
||||
@Override
|
||||
public Collection<Class<?>> apply(Collection<Class<?>> classes) {
|
||||
ArrayList<Class<?>> sorted = new ArrayList<>(classes);
|
||||
sorted.sort(Comparator.comparing(ClassUtils::getShortName));
|
||||
return sorted;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue