Merge branch '2.7.x'

pull/29896/head
Moritz Halbritter 3 years ago
commit 303979fb65

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,14 +16,19 @@
package org.springframework.boot.build.autoconfigure; package org.springframework.boot.build.autoconfigure;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -48,6 +53,8 @@ import org.springframework.util.StringUtils;
*/ */
public class AutoConfigurationMetadata extends DefaultTask { public class AutoConfigurationMetadata extends DefaultTask {
private static final String COMMENT_START = "#";
private SourceSet sourceSet; private SourceSet sourceSet;
private File outputFile; private File outputFile;
@ -57,6 +64,12 @@ public class AutoConfigurationMetadata extends DefaultTask {
.file((Callable<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(), .file((Callable<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring.factories")) "META-INF/spring.factories"))
.withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("spring.factories"); .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"))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("org.springframework.boot.autoconfigure.AutoConfiguration");
dependsOn((Callable<String>) () -> this.sourceSet.getProcessResourcesTaskName()); dependsOn((Callable<String>) () -> this.sourceSet.getProcessResourcesTaskName());
getProject().getConfigurations() getProject().getConfigurations()
.maybeCreate(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME); .maybeCreate(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME);
@ -86,11 +99,9 @@ public class AutoConfigurationMetadata extends DefaultTask {
private Properties readAutoConfiguration() throws IOException { private Properties readAutoConfiguration() throws IOException {
Properties autoConfiguration = CollectionFactory.createSortedProperties(true); Properties autoConfiguration = CollectionFactory.createSortedProperties(true);
Properties springFactories = readSpringFactories( Set<String> classNames = new LinkedHashSet<>();
new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories")); classNames.addAll(readSpringFactories());
String enableAutoConfiguration = springFactories classNames.addAll(readAutoConfigurationsFile());
.getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration");
Set<String> classNames = StringUtils.commaDelimitedListToSet(enableAutoConfiguration);
Set<String> publicClassNames = new LinkedHashSet<>(); Set<String> publicClassNames = new LinkedHashSet<>();
for (String className : classNames) { for (String className : classNames) {
File classFile = findClassFile(className); File classFile = findClassFile(className);
@ -109,6 +120,57 @@ public class AutoConfigurationMetadata extends DefaultTask {
return autoConfiguration; return autoConfiguration;
} }
/**
* Reads auto-configurations from META-INF/spring.factories.
* @return auto-configurations
*/
private Set<String> readSpringFactories() throws IOException {
File file = new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories");
if (!file.exists()) {
return Collections.emptySet();
}
Properties springFactories = readSpringFactories(file);
String enableAutoConfiguration = springFactories
.getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration");
return StringUtils.commaDelimitedListToSet(enableAutoConfiguration);
}
/**
* Reads auto-configurations from
* META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration.
* @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");
if (!file.exists()) {
return Collections.emptyList();
}
// Nearly identical copy of
// org.springframework.boot.autoconfigure.AutoConfigurationLoader.readAutoConfigurations
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
List<String> autoConfigurations = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
line = stripComment(line);
line = line.trim();
if (line.isEmpty()) {
continue;
}
autoConfigurations.add(line);
}
return autoConfigurations;
}
}
private String stripComment(String line) {
int commentStart = line.indexOf(COMMENT_START);
if (commentStart == -1) {
return line;
}
return line.substring(0, commentStart);
}
private File findClassFile(String className) { private File findClassFile(String className) {
String classFileName = className.replace(".", "/") + ".class"; String classFileName = className.replace(".", "/") + ".class";
for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) { for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) {

@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.SpringFactoriesLoader;
/** /**
* Indicates that a class provides configuration that can be automatically applied by * Indicates that a class provides configuration that can be automatically applied by
@ -33,6 +34,10 @@ import org.springframework.context.annotation.Configuration;
* {@link Configuration @Configuration} with the exception that * {@link Configuration @Configuration} with the exception that
* {@literal Configuration#proxyBeanMethods() proxyBeanMethods} is always {@code false}. * {@literal Configuration#proxyBeanMethods() proxyBeanMethods} is always {@code false}.
* <p> * <p>
* They are located using the {@link AutoConfigurationLoader} and the
* {@link SpringFactoriesLoader} mechanism (keyed against
* {@link EnableAutoConfiguration}).
* <p>
* Generally auto-configuration classes are marked as {@link Conditional @Conditional} * Generally auto-configuration classes are marked as {@link Conditional @Conditional}
* (most often using {@link ConditionalOnClass @ConditionalOnClass} and * (most often using {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations). * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure; package org.springframework.boot.autoconfigure;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
@ -54,13 +55,20 @@ public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoad
} }
private boolean isAutoConfiguration(MetadataReader metadataReader) { private boolean isAutoConfiguration(MetadataReader metadataReader) {
return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName()); boolean annotatedWithAutoConfiguration = metadataReader.getAnnotationMetadata()
.isAnnotated(AutoConfiguration.class.getName());
return annotatedWithAutoConfiguration
|| getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
} }
protected List<String> getAutoConfigurations() { protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) { if (this.autoConfigurations == null) {
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, List<String> autoConfigurations = new ArrayList<>();
this.beanClassLoader); autoConfigurations.addAll(
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader));
autoConfigurations
.addAll(new AutoConfigurationLoader().loadNames(AutoConfiguration.class, this.beanClassLoader));
this.autoConfigurations = autoConfigurations;
} }
return this.autoConfigurations; return this.autoConfigurations;
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -67,6 +67,7 @@ import org.springframework.util.StringUtils;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Madhura Bhave * @author Madhura Bhave
* @author Moritz Halbritter
* @since 1.3.0 * @since 1.3.0
* @see EnableAutoConfiguration * @see EnableAutoConfiguration
*/ */
@ -167,7 +168,9 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
/** /**
* Return the auto-configuration class names that should be considered. By default * Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with * this method will load candidates using {@link AutoConfigurationLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}. For backward compatible reasons it
* will also consider {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}. * {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata * @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
@ -175,9 +178,12 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
* @return a list of candidate configurations * @return a list of candidate configurations
*/ */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), List<String> configurations = new ArrayList<>();
getBeanClassLoader()); configurations.addAll(
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
configurations.addAll(new AutoConfigurationLoader().loadNames(AutoConfiguration.class, getBeanClassLoader()));
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 "
+ "are using a custom packaging, make sure that file is correct."); + "are using a custom packaging, make sure that file is correct.");
return configurations; return configurations;
} }

@ -0,0 +1,113 @@
/*
* 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.
* 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.ArrayList;
import java.util.Enumeration;
import java.util.List;
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}.
*
* 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.
*
* @author Moritz Halbritter
* @see AutoConfiguration
* @see SpringFactoriesLoader
*/
class AutoConfigurationLoader {
private static final String LOCATION = "META-INF/spring-boot/";
private static final String COMMENT_START = "#";
/**
* Loads the names of annotated classes.
* @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) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = 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;
}
private ClassLoader decideClassloader(ClassLoader classLoader) {
if (classLoader == null) {
return AutoConfigurationLoader.class.getClassLoader();
}
return classLoader;
}
private Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
try {
return classLoader.getResources(location);
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to load autoconfigurations from location [" + location + "]",
ex);
}
}
private List<String> readAutoConfigurations(URL url) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
List<String> autoConfigurations = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
line = stripComment(line);
line = line.trim();
if (line.isEmpty()) {
continue;
}
autoConfigurations.add(line);
}
return autoConfigurations;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load autoconfigurations from location [" + url + "]", ex);
}
}
private String stripComment(String line) {
int commentStart = line.indexOf(COMMENT_START);
if (commentStart == -1) {
return line;
}
return line.substring(0, commentStart);
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -60,9 +60,9 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
* and classes can be searched. * and classes can be searched.
* <p> * <p>
* Auto-configuration classes are regular Spring {@link Configuration @Configuration} * Auto-configuration classes are regular Spring {@link Configuration @Configuration}
* beans. They are located using the {@link SpringFactoriesLoader} mechanism (keyed * beans. They are located using the {@link AutoConfigurationLoader} and the
* against this class). Generally auto-configuration beans are * {@link SpringFactoriesLoader} mechanism (keyed against this class). Generally
* {@link Conditional @Conditional} beans (most often using * auto-configuration beans are {@link Conditional @Conditional} beans (most often using
* {@link ConditionalOnClass @ConditionalOnClass} and * {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations). * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
* *

@ -44,6 +44,7 @@ import org.springframework.util.ObjectUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Moritz Halbritter
*/ */
class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelector implements DeterminableImports { class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelector implements DeterminableImports {
@ -94,7 +95,10 @@ class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelec
} }
protected Collection<String> loadFactoryNames(Class<?> source) { protected Collection<String> loadFactoryNames(Class<?> source) {
return SpringFactoriesLoader.loadFactoryNames(source, getBeanClassLoader()); List<String> factoryNames = new ArrayList<>();
factoryNames.addAll(SpringFactoriesLoader.loadFactoryNames(source, getBeanClassLoader()));
factoryNames.addAll(new AutoConfigurationLoader().loadNames(source, getBeanClassLoader()));
return factoryNames;
} }
@Override @Override

@ -0,0 +1,53 @@
/*
* 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.
* 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.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();
}
@Test
void loadNames() {
List<String> classNames = this.sut.loadNames(TestAutoConfiguration.class, null);
assertThat(classNames).containsExactly("class1", "class2", "class3");
}
@AutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAutoConfiguration {
}
}

@ -0,0 +1,6 @@
# A comment spanning a complete line
class1
class2 # with comment at the end
# Comment with some whitespace in front
class3

@ -24,16 +24,18 @@ You can browse the source code of {spring-boot-autoconfigure-module-code}[`sprin
[[features.developing-auto-configuration.locating-auto-configuration-candidates]] [[features.developing-auto-configuration.locating-auto-configuration-candidates]]
=== Locating Auto-configuration Candidates === Locating Auto-configuration Candidates
Spring Boot checks for the presence of a `META-INF/spring.factories` file within your published jar.
The file should list your configuration classes under the `EnableAutoConfiguration` key, as shown in the following example: Spring Boot checks for the presence of a `META-INF/spring-boot/org.springframework.boot.autoconfigure.AutoConfiguration` file within your published jar.
The file should list your configuration classes, as shown in the following example:
[indent=0] [indent=0]
---- ----
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
---- ----
TIP: You can use comments via `#` in this file.
NOTE: Auto-configurations must be loaded that way _only_. NOTE: Auto-configurations must be loaded that way _only_.
Make sure that they are defined in a specific package space and that they are never the target of component scanning. Make sure that they are defined in a specific package space and that they are never the target of component scanning.
Furthermore, auto-configuration classes should not enable component scanning to find additional components. Furthermore, auto-configuration classes should not enable component scanning to find additional components.

@ -716,13 +716,18 @@ 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. 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 `META-INF/spring.factories` 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-boot` as shown in the following example:
.META-INF/spring-boot/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest
[indent=0] [indent=0]
---- ----
org.springframework.boot.test.autoconfigure.jdbc.JdbcTest=com.example.IntegrationAutoConfiguration com.example.IntegrationAutoConfiguration
---- ----
In this example, the `com.example.IntegrationAutoConfiguration` is enabled on every test annotated with `@JdbcTest`.
TIP: You can use comments via `#` in this file.
TIP: A slice or `@AutoConfigure...` annotation can be customized this way as long as it is meta-annotated with `@ImportAutoConfiguration`. TIP: A slice or `@AutoConfigure...` annotation can be customized this way as long as it is meta-annotated with `@ImportAutoConfiguration`.

Loading…
Cancel
Save