Rename AutoConfigurationLoader to ImportCandidates

Move the class to a more suitable package, and load the files from
META-INF/spring/<fqn>.imports

See gh-29872
This commit is contained in:
Moritz Halbritter 2022-02-21 09:44:30 +01:00
parent 14c9147621
commit d7b229d3c7
48 changed files with 84 additions and 68 deletions

View File

@ -66,7 +66,7 @@ public class AutoConfigurationMetadata extends DefaultTask {
.withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("spring.factories");
getInputs()
.file((Callable<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration"))
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("org.springframework.boot.autoconfigure.AutoConfiguration");
@ -137,17 +137,17 @@ public class AutoConfigurationMetadata extends DefaultTask {
/**
* Reads auto-configurations from
* META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration.
* META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
* @return auto-configurations
*/
private List<String> readAutoConfigurationsFile() throws IOException {
File file = new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration");
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports");
if (!file.exists()) {
return Collections.emptyList();
}
// Nearly identical copy of
// org.springframework.boot.autoconfigure.AutoConfigurationLoader.readAutoConfigurations
// org.springframework.boot.context.annotation.ImportCandidates.load
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
List<String> autoConfigurations = new ArrayList<>();
String line;

View File

@ -106,7 +106,7 @@ public class TestSliceMetadata extends DefaultTask {
Properties springFactories = readSpringFactories(
new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories"));
readTestSlicesDirectory(springFactories,
new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring-boot/"));
new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring/"));
for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) {
addTestSlices(testSlices, classesDir, metadataReaderFactory, springFactories);
}
@ -123,14 +123,17 @@ public class TestSliceMetadata extends DefaultTask {
* @param directory directory to scan
*/
private void readTestSlicesDirectory(Properties springFactories, File directory) {
File[] files = directory.listFiles();
File[] files = directory.listFiles((dir, name) -> name.endsWith(".imports"));
if (files == null) {
return;
}
for (File file : files) {
try {
List<String> lines = removeComments(Files.readAllLines(file.toPath()));
springFactories.setProperty(file.getName(), StringUtils.collectionToCommaDelimitedString(lines));
String fileNameWithoutExtension = file.getName().substring(0,
file.getName().length() - ".imports".length());
springFactories.setProperty(fileNameWithoutExtension,
StringUtils.collectionToCommaDelimitedString(lines));
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to read file " + file, ex);

View File

@ -24,6 +24,7 @@ import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.SpringFactoriesLoader;
@ -34,9 +35,8 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
* {@link Configuration @Configuration} with the exception that
* {@literal Configuration#proxyBeanMethods() proxyBeanMethods} is always {@code false}.
* <p>
* They are located using the {@link AutoConfigurationLoader} and the
* {@link SpringFactoriesLoader} mechanism (keyed against
* {@link EnableAutoConfiguration}).
* They are located using {@link ImportCandidates} and the {@link SpringFactoriesLoader}
* mechanism (keyed against {@link EnableAutoConfiguration}).
* <p>
* Generally auto-configuration classes are marked as {@link Conditional @Conditional}
* (most often using {@link ConditionalOnClass @ConditionalOnClass} and

View File

@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.classreading.MetadataReader;
@ -63,11 +64,9 @@ public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoad
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
List<String> autoConfigurations = new ArrayList<>();
autoConfigurations.addAll(
List<String> autoConfigurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader));
autoConfigurations
.addAll(new AutoConfigurationLoader().loadNames(AutoConfiguration.class, this.beanClassLoader));
ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader).forEach(autoConfigurations::add);
this.autoConfigurations = autoConfigurations;
}
return this.autoConfigurations;

View File

@ -40,6 +40,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
@ -168,7 +169,7 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link AutoConfigurationLoader} with
* this method will load candidates using {@link ImportCandidates} with
* {@link #getSpringFactoriesLoaderFactoryClass()}. For backward compatible reasons it
* will also consider {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
@ -178,12 +179,11 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>();
configurations.addAll(
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
configurations.addAll(new AutoConfigurationLoader().loadNames(AutoConfiguration.class, getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration. If you "
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

View File

@ -26,6 +26,7 @@ import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Conditional;
@ -60,7 +61,7 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
* and classes can be searched.
* <p>
* Auto-configuration classes are regular Spring {@link Configuration @Configuration}
* beans. They are located using the {@link AutoConfigurationLoader} and the
* beans. They are located using {@link ImportCandidates} and the
* {@link SpringFactoriesLoader} mechanism (keyed against this class). Generally
* auto-configuration beans are {@link Conditional @Conditional} beans (most often using
* {@link ConditionalOnClass @ConditionalOnClass} and

View File

@ -59,8 +59,8 @@ public @interface ImportAutoConfiguration {
/**
* The auto-configuration classes that should be imported. When empty, the classes are
* specified using a file in {@code META-INF/spring-boot} where the file name is the
* fully-qualified name of the annotated class.
* specified using a file in {@code META-INF/spring} where the file name is the
* fully-qualified name of the annotated class, suffixed with '.imports'.
* @return the classes to import
*/
@AliasFor("value")

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -28,6 +28,7 @@ import java.util.Map;
import java.util.Set;
import org.springframework.boot.context.annotation.DeterminableImports;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
@ -95,9 +96,9 @@ class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelec
}
protected Collection<String> loadFactoryNames(Class<?> source) {
List<String> factoryNames = new ArrayList<>();
factoryNames.addAll(SpringFactoriesLoader.loadFactoryNames(source, getBeanClassLoader()));
factoryNames.addAll(new AutoConfigurationLoader().loadNames(source, getBeanClassLoader()));
List<String> factoryNames = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(source, getBeanClassLoader()));
ImportCandidates.load(source, getBeanClassLoader()).forEach(factoryNames::add);
return factoryNames;
}

View File

@ -32,6 +32,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
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.core.io.DefaultResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
@ -212,7 +213,10 @@ class AutoConfigurationImportSelectorTests {
}
private List<String> getAutoConfigurationClassNames() {
return new AutoConfigurationLoader().loadNames(AutoConfiguration.class, getClass().getClassLoader());
List<String> autoConfigurationClassNames = new ArrayList<>();
ImportCandidates.load(AutoConfiguration.class, getClass().getClassLoader())
.forEach(autoConfigurationClassNames::add);
return autoConfigurationClassNames;
}
private class TestAutoConfigurationImportSelector extends AutoConfigurationImportSelector {

View File

@ -18,14 +18,14 @@ Additional `@Conditional` annotations are used to constrain when the auto-config
Usually, auto-configuration classes use `@ConditionalOnClass` and `@ConditionalOnMissingBean` annotations.
This ensures that auto-configuration applies only when relevant classes are found and when you have not declared your own `@Configuration`.
You can browse the source code of {spring-boot-autoconfigure-module-code}[`spring-boot-autoconfigure`] to see the `@Configuration` classes that Spring provides (see the {spring-boot-code}/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration[`META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration`] file).
You can browse the source code of {spring-boot-autoconfigure-module-code}[`spring-boot-autoconfigure`] to see the `@Configuration` classes that Spring provides (see the {spring-boot-code}/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports[`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`] file).
[[features.developing-auto-configuration.locating-auto-configuration-candidates]]
=== Locating Auto-configuration Candidates
Spring Boot checks for the presence of a `META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration` file within your published jar.
Spring Boot checks for the presence of a `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` file within your published jar.
The file should list your configuration classes, as shown in the following example:
[indent=0]

View File

@ -769,9 +769,9 @@ include::code:MyJdbcTests[]
NOTE: Make sure to not use the regular `@Import` annotation to import auto-configurations as they are handled in a specific way by Spring Boot.
Alternatively, additional auto-configurations can be added for any use of a slice annotation by registering them in a file stored in `META-INF/spring-boot` as shown in the following example:
Alternatively, additional auto-configurations can be added for any use of a slice annotation by registering them in a file stored in `META-INF/spring` as shown in the following example:
.META-INF/spring-boot/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest
.META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports
[indent=0]
----
com.example.IntegrationAutoConfiguration

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure;
package org.springframework.boot.context.annotation;
import java.io.BufferedReader;
import java.io.IOException;
@ -22,57 +22,76 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
/**
* Loads the names of annotated classes, usually @{@link AutoConfiguration}.
* Contains import candidates, usually auto-configurations.
*
* The names of the classes are stored in files named META-INF/spring-boot/{full qualified
* name of the annotation}. Every line contains the full qualified class name of the
* annotated class. Comments are supported using the # character.
* The {@link #load(Class, ClassLoader)} method can be used to discover the import
* candidates.
*
* @author Moritz Halbritter
* @see AutoConfiguration
* @see SpringFactoriesLoader
* @since 2.7.0
*/
class AutoConfigurationLoader {
public final class ImportCandidates implements Iterable<String> {
private static final String LOCATION = "META-INF/spring-boot/";
private static final String LOCATION = "META-INF/spring/%s.imports";
private static final String COMMENT_START = "#";
private final List<String> candidates;
private ImportCandidates(List<String> candidates) {
Assert.notNull(candidates, "'candidates' must not be null");
this.candidates = Collections.unmodifiableList(candidates);
}
@NotNull
@Override
public Iterator<String> iterator() {
return this.candidates.iterator();
}
/**
* Loads the names of annotated classes.
* 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.import} on the classpath.
* Every line contains the full qualified name of the candidate class. Comments are
* supported using the # character.
* @param annotation annotation to load
* @param classLoader class loader to use for loading
* @return list of names of annotated classes
*/
List<String> loadNames(Class<?> annotation, ClassLoader classLoader) {
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = LOCATION + annotation.getName();
String location = String.format(LOCATION, annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> autoConfigurations = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
autoConfigurations.addAll(readAutoConfigurations(url));
}
return autoConfigurations;
return new ImportCandidates(autoConfigurations);
}
private ClassLoader decideClassloader(ClassLoader classLoader) {
private static ClassLoader decideClassloader(ClassLoader classLoader) {
if (classLoader == null) {
return AutoConfigurationLoader.class.getClassLoader();
return ImportCandidates.class.getClassLoader();
}
return classLoader;
}
private Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
private static Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
try {
return classLoader.getResources(location);
}
@ -82,7 +101,7 @@ class AutoConfigurationLoader {
}
}
private List<String> readAutoConfigurations(URL url) {
private static List<String> readAutoConfigurations(URL url) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
List<String> autoConfigurations = new ArrayList<>();
@ -102,7 +121,7 @@ class AutoConfigurationLoader {
}
}
private String stripComment(String line) {
private static String stripComment(String line) {
int commentStart = line.indexOf(COMMENT_START);
if (commentStart == -1) {
return line;

View File

@ -14,39 +14,28 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure;
package org.springframework.boot.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class AutoConfigurationLoaderTests {
private AutoConfigurationLoader sut;
@BeforeEach
void setUp() {
this.sut = new AutoConfigurationLoader();
}
class ImportCandidatesTest {
@Test
void loadNames() {
List<String> classNames = this.sut.loadNames(TestAutoConfiguration.class, null);
assertThat(classNames).containsExactly("class1", "class2", "class3");
void loadReadsFromClasspathFile() {
ImportCandidates candidates = ImportCandidates.load(TestAnnotation.class, null);
assertThat(candidates).containsExactly("class1", "class2", "class3");
}
@AutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAutoConfiguration {
public @interface TestAnnotation {
}