From 644aeabf7b3e53a299d74e55d1ece1f3c27acaa6 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 2 Jun 2025 12:12:58 -0700 Subject: [PATCH 1/5] Polish --- .../ObservationRegistryConfigurerIntegrationTests.java | 1 - .../boot/actuate/web/exchanges/servlet/HttpExchangesFilter.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationRegistryConfigurerIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationRegistryConfigurerIntegrationTests.java index 8ffcd64f4b2..b1925da35d5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationRegistryConfigurerIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationRegistryConfigurerIntegrationTests.java @@ -47,7 +47,6 @@ class ObservationRegistryConfigurerIntegrationTests { CalledCustomizers calledCustomizers = context.getBean(CalledCustomizers.class); Customizer1 customizer1 = context.getBean(Customizer1.class); Customizer2 customizer2 = context.getBean(Customizer2.class); - assertThat(calledCustomizers.getCustomizers()).containsExactly(customizer1, customizer2); }); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/exchanges/servlet/HttpExchangesFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/exchanges/servlet/HttpExchangesFilter.java index d7eee923222..030659739e0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/exchanges/servlet/HttpExchangesFilter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/exchanges/servlet/HttpExchangesFilter.java @@ -56,7 +56,7 @@ public class HttpExchangesFilter extends OncePerRequestFilter implements Ordered private final Set includes; /** - * Create a new instance. + * Create a new {@link HttpExchangesFilter} instance. * @param repository the repository used to record events * @param includes the include options */ From 5464812f80081cf85a6734608f13406cbddcafcc Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 2 Jun 2025 17:10:33 +0100 Subject: [PATCH 2/5] Improve checking of auto-configuration --- .../autoconfigure/AutoConfigurationClass.java | 135 +++++++++++ .../AutoConfigurationImportsTask.java | 66 ++++++ .../AutoConfigurationPlugin.java | 122 +++------- .../CheckAutoConfigurationClasses.java | 212 ++++++++++++++++++ .../CheckAutoConfigurationImports.java | 126 +++++++++++ 5 files changed, 575 insertions(+), 86 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationClass.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationImportsTask.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationClasses.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationClass.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationClass.java new file mode 100644 index 00000000000..2d3ffff8e8f --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationClass.java @@ -0,0 +1,135 @@ +/* + * Copyright 2025 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.build.autoconfigure; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; + +/** + * An {@code @AutoConfiguration} class. + * + * @param name name of the auto-configuration class + * @param before values of the {@code before} attribute + * @param beforeName values of the {@code beforeName} attribute + * @param after values of the {@code after} attribute + * @param afterName values of the {@code afterName} attribute + * @author Andy Wilkinson + */ +public record AutoConfigurationClass(String name, List before, List beforeName, List after, + List afterName) { + + private AutoConfigurationClass(String name, Map> attributes) { + this(name, attributes.getOrDefault("before", Collections.emptyList()), + attributes.getOrDefault("beforeName", Collections.emptyList()), + attributes.getOrDefault("after", Collections.emptyList()), + attributes.getOrDefault("afterName", Collections.emptyList())); + } + + static AutoConfigurationClass of(File classFile) { + try (FileInputStream input = new FileInputStream(classFile)) { + ClassReader classReader = new ClassReader(input); + AutoConfigurationClassVisitor visitor = new AutoConfigurationClassVisitor(); + classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); + return visitor.autoConfigurationClass; + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static final class AutoConfigurationClassVisitor extends ClassVisitor { + + private AutoConfigurationClass autoConfigurationClass; + + private String name; + + private AutoConfigurationClassVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + this.name = Type.getObjectType(name).getClassName(); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + String annotationClassName = Type.getType(descriptor).getClassName(); + if ("org.springframework.boot.autoconfigure.AutoConfiguration".equals(annotationClassName)) { + return new AutoConfigurationAnnotationVisitor(); + } + return null; + } + + private final class AutoConfigurationAnnotationVisitor extends AnnotationVisitor { + + private Map> attributes = new HashMap<>(); + + private static final Set INTERESTING_ATTRIBUTES = Set.of("before", "beforeName", "after", + "afterName"); + + private AutoConfigurationAnnotationVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } + + @Override + public void visitEnd() { + AutoConfigurationClassVisitor.this.autoConfigurationClass = new AutoConfigurationClass( + AutoConfigurationClassVisitor.this.name, this.attributes); + } + + @Override + public AnnotationVisitor visitArray(String attributeName) { + if (INTERESTING_ATTRIBUTES.contains(attributeName)) { + return new AnnotationVisitor(SpringAsmInfo.ASM_VERSION) { + + @Override + public void visit(String name, Object value) { + if (value instanceof Type type) { + value = type.getClassName(); + } + AutoConfigurationAnnotationVisitor.this.attributes + .computeIfAbsent(attributeName, (n) -> new ArrayList<>()) + .add(Objects.toString(value)); + } + + }; + } + return null; + } + + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationImportsTask.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationImportsTask.java new file mode 100644 index 00000000000..71b16f00bbd --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationImportsTask.java @@ -0,0 +1,66 @@ +/* + * Copyright 2025 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.build.autoconfigure; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.util.List; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileTree; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SkipWhenEmpty; + +/** + * A {@link Task} that uses a project's auto-configuration imports. + * + * @author Andy Wilkinson + */ +public abstract class AutoConfigurationImportsTask extends DefaultTask { + + static final String IMPORTS_FILE = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"; + + private FileCollection sourceFiles = getProject().getObjects().fileCollection(); + + @InputFiles + @SkipWhenEmpty + @PathSensitive(PathSensitivity.RELATIVE) + public FileTree getSource() { + return this.sourceFiles.getAsFileTree().matching((filter) -> filter.include(IMPORTS_FILE)); + } + + public void setSource(Object source) { + this.sourceFiles = getProject().getObjects().fileCollection().from(source); + } + + protected List loadImports() { + File importsFile = getSource().getSingleFile(); + try { + return Files.readAllLines(importsFile.toPath()); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java index 86a552466b8..76f0af53f11 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java @@ -16,31 +16,20 @@ package org.springframework.boot.build.autoconfigure; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collections; -import java.util.List; +import java.util.Map; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; -import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.build.DeployedPlugin; -import org.springframework.boot.build.architecture.ArchitectureCheck; import org.springframework.boot.build.architecture.ArchitecturePlugin; +import org.springframework.boot.build.optional.OptionalDependenciesPlugin; /** * {@link Plugin} for projects that define auto-configuration. When applied, the plugin @@ -70,14 +59,16 @@ public class AutoConfigurationPlugin implements Plugin { */ public static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata"; - private static final String AUTO_CONFIGURATION_IMPORTS_PATH = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"; - @Override public void apply(Project project) { project.getPlugins().apply(DeployedPlugin.class); project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { Configuration annotationProcessors = project.getConfigurations() .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); + SourceSet main = project.getExtensions() + .getByType(JavaPluginExtension.class) + .getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); annotationProcessors.getDependencies() .add(project.getDependencies() .project(Collections.singletonMap("path", @@ -87,10 +78,6 @@ public class AutoConfigurationPlugin implements Plugin { .project(Collections.singletonMap("path", ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor"))); project.getTasks().register("autoConfigurationMetadata", AutoConfigurationMetadata.class, (task) -> { - SourceSet main = project.getExtensions() - .getByType(JavaPluginExtension.class) - .getSourceSets() - .getByName(SourceSet.MAIN_SOURCE_SET_NAME); task.setSourceSet(main); task.dependsOn(main.getClassesTaskName()); task.getOutputFile() @@ -99,74 +86,37 @@ public class AutoConfigurationPlugin implements Plugin { .add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, task.getOutputFile(), (artifact) -> artifact.builtBy(task)); }); + project.getTasks() + .register("checkAutoConfigurationImports", CheckAutoConfigurationImports.class, (task) -> { + task.setSource(main.getResources()); + task.setClasspath(main.getOutput().getClassesDirs()); + task.setDescription("Checks the %s file of the main source set." + .formatted(AutoConfigurationImportsTask.IMPORTS_FILE)); + }); + Configuration requiredClasspath = project.getConfigurations() + .create("autoConfigurationRequiredClasspath") + .extendsFrom(project.getConfigurations().getByName(main.getImplementationConfigurationName()), + project.getConfigurations().getByName(main.getRuntimeOnlyConfigurationName())); + requiredClasspath.getDependencies() + .add(project.getDependencies() + .project(Map.of("path", ":spring-boot-project:spring-boot-autoconfigure"))); + TaskProvider checkAutoConfigurationClasses = project.getTasks() + .register("checkAutoConfigurationClasses", CheckAutoConfigurationClasses.class, (task) -> { + task.setSource(main.getResources()); + task.setClasspath(main.getOutput().getClassesDirs()); + task.setRequiredDependencies(requiredClasspath); + task.setDescription("Checks the auto-configuration classes of the main source set."); + }); project.getPlugins() - .withType(ArchitecturePlugin.class, (plugin) -> configureArchitecturePluginTasks(project)); - }); - } - - private void configureArchitecturePluginTasks(Project project) { - project.getTasks().configureEach((task) -> { - if ("checkArchitectureMain".equals(task.getName()) && task instanceof ArchitectureCheck architectureCheck) { - configureCheckArchitectureMain(project, architectureCheck); - } + .withType(OptionalDependenciesPlugin.class, + (plugin) -> checkAutoConfigurationClasses.configure((check) -> { + Configuration optionalClasspath = project.getConfigurations() + .create("autoConfigurationOptionalClassPath") + .extendsFrom(project.getConfigurations() + .getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME)); + check.setOptionalDependencies(optionalClasspath); + })); }); } - private void configureCheckArchitectureMain(Project project, ArchitectureCheck architectureCheck) { - SourceSet main = project.getExtensions() - .getByType(JavaPluginExtension.class) - .getSourceSets() - .getByName(SourceSet.MAIN_SOURCE_SET_NAME); - File resourcesDirectory = main.getOutput().getResourcesDir(); - architectureCheck.dependsOn(main.getProcessResourcesTaskName()); - architectureCheck.getInputs() - .files(resourcesDirectory) - .optional() - .withPathSensitivity(PathSensitivity.RELATIVE); - architectureCheck.getRules() - .add(allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports( - autoConfigurationImports(project, resourcesDirectory))); - } - - private ArchRule allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports( - Provider imports) { - return ArchRuleDefinition.classes() - .that() - .areAnnotatedWith("org.springframework.boot.autoconfigure.AutoConfiguration") - .should(beListedInAutoConfigurationImports(imports)) - .allowEmptyShould(true); - } - - private ArchCondition beListedInAutoConfigurationImports(Provider imports) { - return new ArchCondition<>("be listed in " + AUTO_CONFIGURATION_IMPORTS_PATH) { - - @Override - public void check(JavaClass item, ConditionEvents events) { - AutoConfigurationImports autoConfigurationImports = imports.get(); - if (!autoConfigurationImports.imports.contains(item.getName())) { - events.add(SimpleConditionEvent.violated(item, - item.getName() + " was not listed in " + autoConfigurationImports.importsFile)); - } - } - - }; - } - - private Provider autoConfigurationImports(Project project, File resourcesDirectory) { - Path importsFile = new File(resourcesDirectory, AUTO_CONFIGURATION_IMPORTS_PATH).toPath(); - return project.provider(() -> { - try { - return new AutoConfigurationImports(project.getProjectDir().toPath().relativize(importsFile), - Files.readAllLines(importsFile)); - } - catch (IOException ex) { - throw new RuntimeException("Failed to read AutoConfiguration.imports", ex); - } - }); - } - - private record AutoConfigurationImports(Path importsFile, List imports) { - - } - } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationClasses.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationClasses.java new file mode 100644 index 00000000000..d56e536ec2e --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationClasses.java @@ -0,0 +1,212 @@ +/* + * Copyright 2012-2025 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.build.autoconfigure; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; + +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.provider.SetProperty; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.VerificationException; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +/** + * Task to check a project's {@code @AutoConfiguration} classes. + * + * @author Andy Wilkinson + */ +public abstract class CheckAutoConfigurationClasses extends AutoConfigurationImportsTask { + + private FileCollection classpath = getProject().getObjects().fileCollection(); + + private FileCollection optionalDependencies = getProject().getObjects().fileCollection(); + + private FileCollection requiredDependencies = getProject().getObjects().fileCollection(); + + private SetProperty optionalDependencyClassNames = getProject().getObjects().setProperty(String.class); + + private SetProperty requiredDependencyClassNames = getProject().getObjects().setProperty(String.class); + + public CheckAutoConfigurationClasses() { + getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); + setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + this.optionalDependencyClassNames.set(getProject().provider(() -> classNamesOf(this.optionalDependencies))); + this.requiredDependencyClassNames.set(getProject().provider(() -> classNamesOf(this.requiredDependencies))); + } + + private static List classNamesOf(FileCollection classpath) { + return classpath.getFiles().stream().flatMap((file) -> { + try (JarFile jarFile = new JarFile(file)) { + return Collections.list(jarFile.entries()) + .stream() + .filter((entry) -> !entry.isDirectory()) + .map(JarEntry::getName) + .filter((entryName) -> entryName.endsWith(".class")) + .map((entryName) -> entryName.substring(0, entryName.length() - ".class".length())) + .map((entryName) -> entryName.replace("/", ".")); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }).toList(); + } + + @Classpath + public FileCollection getClasspath() { + return this.classpath; + } + + public void setClasspath(Object classpath) { + this.classpath = getProject().getObjects().fileCollection().from(classpath); + } + + @Classpath + public FileCollection getOptionalDependencies() { + return this.optionalDependencies; + } + + public void setOptionalDependencies(Object classpath) { + this.optionalDependencies = getProject().getObjects().fileCollection().from(classpath); + } + + @Classpath + public FileCollection getRequiredDependencies() { + return this.requiredDependencies; + } + + public void setRequiredDependencies(Object classpath) { + this.requiredDependencies = getProject().getObjects().fileCollection().from(classpath); + } + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @TaskAction + void execute() { + Map> problems = new TreeMap<>(); + Set optionalOnlyClassNames = new HashSet<>(this.optionalDependencyClassNames.get()); + Set requiredClassNames = this.requiredDependencyClassNames.get(); + optionalOnlyClassNames.removeAll(requiredClassNames); + classFiles().forEach((classFile) -> { + AutoConfigurationClass autoConfigurationClass = AutoConfigurationClass.of(classFile); + if (autoConfigurationClass != null) { + check(autoConfigurationClass, optionalOnlyClassNames, requiredClassNames, problems); + } + }); + File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); + writeReport(problems, outputFile); + if (!problems.isEmpty()) { + throw new VerificationException( + "Auto-configuration class check failed. See '%s' for details".formatted(outputFile)); + } + } + + private List classFiles() { + List classFiles = new ArrayList<>(); + for (File root : this.classpath.getFiles()) { + try (Stream files = Files.walk(root.toPath())) { + files.forEach((file) -> { + if (Files.isRegularFile(file) && file.getFileName().toString().endsWith(".class")) { + classFiles.add(file.toFile()); + } + }); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + return classFiles; + } + + private void check(AutoConfigurationClass autoConfigurationClass, Set optionalOnlyClassNames, + Set requiredClassNames, Map> problems) { + if (!autoConfigurationClass.name().endsWith("AutoConfiguration")) { + problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) + .add("Name of a class annotated with @AutoConfiguration should end with AutoConfiguration"); + } + autoConfigurationClass.before().forEach((before) -> { + if (optionalOnlyClassNames.contains(before)) { + problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) + .add("before '%s' is from an optional dependency and should be declared in beforeName" + .formatted(before)); + } + }); + autoConfigurationClass.beforeName().forEach((beforeName) -> { + if (!optionalOnlyClassNames.contains(beforeName)) { + String problem = requiredClassNames.contains(beforeName) + ? "beforeName '%s' is from a required dependency and should be declared in before" + .formatted(beforeName) + : "beforeName '%s' not found".formatted(beforeName); + problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()).add(problem); + } + }); + autoConfigurationClass.after().forEach((after) -> { + if (optionalOnlyClassNames.contains(after)) { + problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) + .add("after '%s' is from an optional dependency and should be declared in afterName" + .formatted(after)); + } + }); + autoConfigurationClass.afterName().forEach((afterName) -> { + if (!optionalOnlyClassNames.contains(afterName)) { + String problem = requiredClassNames.contains(afterName) + ? "afterName '%s' is from a required dependency and should be declared in after" + .formatted(afterName) + : "afterName '%s' not found".formatted(afterName); + problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()).add(problem); + } + }); + } + + private void writeReport(Map> problems, File outputFile) { + outputFile.getParentFile().mkdirs(); + StringBuilder report = new StringBuilder(); + if (!problems.isEmpty()) { + report.append("Found auto-configuration class problems:%n".formatted()); + problems.forEach((className, classProblems) -> { + report.append(" - %s:%n".formatted(className)); + classProblems.forEach((problem) -> report.append(" - %s%n".formatted(problem))); + }); + } + try { + Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java new file mode 100644 index 00000000000..299e6bbdda6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java @@ -0,0 +1,126 @@ +/* + * Copyright 2025 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.build.autoconfigure; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.VerificationException; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +/** + * Task to check the contents of a project's + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports} + * file. + * + * @author Andy Wilkinson + */ +public abstract class CheckAutoConfigurationImports extends AutoConfigurationImportsTask { + + private FileCollection classpath = getProject().getObjects().fileCollection(); + + public CheckAutoConfigurationImports() { + getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); + setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + } + + @Classpath + public FileCollection getClasspath() { + return this.classpath; + } + + public void setClasspath(Object classpath) { + this.classpath = getProject().getObjects().fileCollection().from(classpath); + } + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @TaskAction + void execute() { + File importsFile = getSource().getSingleFile(); + check(importsFile); + } + + private void check(File importsFile) { + List imports = loadImports(); + List problems = new ArrayList<>(); + for (String imported : imports) { + File classFile = find(imported); + if (classFile == null) { + problems.add("'%s' was not found".formatted(imported)); + } + else if (!correctlyAnnotated(classFile)) { + problems.add("'%s' is not annotated with @AutoConfiguration".formatted(imported)); + } + } + List sortedValues = new ArrayList<>(imports); + Collections.sort(sortedValues); + if (!sortedValues.equals(imports)) { + problems.add("Entries should be sorted alphabetically"); + } + File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); + writeReport(importsFile, problems, outputFile); + if (!problems.isEmpty()) { + throw new VerificationException("%s check failed. See '%s' for details" + .formatted(AutoConfigurationImportsTask.IMPORTS_FILE, outputFile)); + } + } + + private File find(String className) { + for (File root : this.classpath.getFiles()) { + String classFilePath = className.replace(".", "/") + ".class"; + File classFile = new File(root, classFilePath); + if (classFile.isFile()) { + return classFile; + } + } + return null; + } + + private boolean correctlyAnnotated(File classFile) { + return AutoConfigurationClass.of(classFile) != null; + } + + private void writeReport(File importsFile, List problems, File outputFile) { + outputFile.getParentFile().mkdirs(); + StringBuilder report = new StringBuilder(); + if (!problems.isEmpty()) { + report.append("Found problems in '%s':%n".formatted(importsFile)); + problems.forEach((problem) -> report.append(" - %s%n".formatted(problem))); + } + try { + Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + +} From d47f1d27f68555db379890fb86c60fa9c17342ef Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 2 Jun 2025 14:17:12 -0700 Subject: [PATCH 3/5] Polish AutoConfigurationPlugin --- .../AutoConfigurationPlugin.java | 156 +++++++++++------- .../DocumentAutoConfigurationClasses.java | 2 - 2 files changed, 98 insertions(+), 60 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java index 76f0af53f11..d53b5bb1ac4 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java @@ -16,19 +16,24 @@ package org.springframework.boot.build.autoconfigure; +import java.util.Arrays; import java.util.Collections; -import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.build.DeployedPlugin; -import org.springframework.boot.build.architecture.ArchitecturePlugin; import org.springframework.boot.build.optional.OptionalDependenciesPlugin; /** @@ -41,12 +46,7 @@ import org.springframework.boot.build.optional.OptionalDependenciesPlugin; *
  • Defines a task that produces metadata describing the auto-configuration. The * metadata is made available as an artifact in the {@code autoConfigurationMetadata} * configuration. - *
  • Reacts to the {@link ArchitecturePlugin} being applied and: - *
      - *
    • Adds a rule to the {@code checkArchitectureMain} task to verify that all - * {@code AutoConfiguration} classes are listed in the {@code AutoConfiguration.imports} - * file. - *
    + *
  • Add checks to ensure import files and annotations are correct
  • * * * @author Andy Wilkinson @@ -62,61 +62,101 @@ public class AutoConfigurationPlugin implements Plugin { @Override public void apply(Project project) { project.getPlugins().apply(DeployedPlugin.class); - project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { - Configuration annotationProcessors = project.getConfigurations() - .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); - SourceSet main = project.getExtensions() + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> new Configurer(project).configure()); + } + + private static class Configurer { + + private final Project project; + + private SourceSet main; + + Configurer(Project project) { + this.project = project; + this.main = project.getExtensions() .getByType(JavaPluginExtension.class) .getSourceSets() .getByName(SourceSet.MAIN_SOURCE_SET_NAME); - annotationProcessors.getDependencies() - .add(project.getDependencies() - .project(Collections.singletonMap("path", - ":spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-processor"))); - annotationProcessors.getDependencies() - .add(project.getDependencies() - .project(Collections.singletonMap("path", - ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor"))); - project.getTasks().register("autoConfigurationMetadata", AutoConfigurationMetadata.class, (task) -> { - task.setSourceSet(main); - task.dependsOn(main.getClassesTaskName()); - task.getOutputFile() - .set(project.getLayout().getBuildDirectory().file("auto-configuration-metadata.properties")); - project.getArtifacts() - .add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, task.getOutputFile(), - (artifact) -> artifact.builtBy(task)); - }); - project.getTasks() - .register("checkAutoConfigurationImports", CheckAutoConfigurationImports.class, (task) -> { - task.setSource(main.getResources()); - task.setClasspath(main.getOutput().getClassesDirs()); - task.setDescription("Checks the %s file of the main source set." - .formatted(AutoConfigurationImportsTask.IMPORTS_FILE)); - }); - Configuration requiredClasspath = project.getConfigurations() - .create("autoConfigurationRequiredClasspath") - .extendsFrom(project.getConfigurations().getByName(main.getImplementationConfigurationName()), - project.getConfigurations().getByName(main.getRuntimeOnlyConfigurationName())); + } + + void configure() { + addAnnotationProcessorsDependencies(); + TaskContainer tasks = this.project.getTasks(); + ConfigurationContainer configurations = this.project.getConfigurations(); + tasks.register("autoConfigurationMetadata", AutoConfigurationMetadata.class, + this::configureAutoConfigurationMetadata); + TaskProvider checkAutoConfigurationImports = tasks.register( + "checkAutoConfigurationImports", CheckAutoConfigurationImports.class, + this::configureCheckAutoConfigurationImports); + Configuration requiredClasspath = configurations.create("autoConfigurationRequiredClasspath") + .extendsFrom(configurations.getByName(this.main.getImplementationConfigurationName()), + configurations.getByName(this.main.getRuntimeOnlyConfigurationName())); requiredClasspath.getDependencies() - .add(project.getDependencies() - .project(Map.of("path", ":spring-boot-project:spring-boot-autoconfigure"))); - TaskProvider checkAutoConfigurationClasses = project.getTasks() - .register("checkAutoConfigurationClasses", CheckAutoConfigurationClasses.class, (task) -> { - task.setSource(main.getResources()); - task.setClasspath(main.getOutput().getClassesDirs()); - task.setRequiredDependencies(requiredClasspath); - task.setDescription("Checks the auto-configuration classes of the main source set."); - }); - project.getPlugins() + .add(projectDependency(":spring-boot-project:spring-boot-autoconfigure")); + TaskProvider checkAutoConfigurationClasses = tasks.register( + "checkAutoConfigurationClasses", CheckAutoConfigurationClasses.class, + (task) -> configureCheckAutoConfigurationClasses(requiredClasspath, task)); + this.project.getPlugins() .withType(OptionalDependenciesPlugin.class, - (plugin) -> checkAutoConfigurationClasses.configure((check) -> { - Configuration optionalClasspath = project.getConfigurations() - .create("autoConfigurationOptionalClassPath") - .extendsFrom(project.getConfigurations() - .getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME)); - check.setOptionalDependencies(optionalClasspath); - })); - }); + (plugin) -> configureCheckAutoConfigurationClassesForOptionalDependencies(configurations, + checkAutoConfigurationClasses)); + this.project.getTasks() + .getByName(JavaBasePlugin.CHECK_TASK_NAME) + .dependsOn(checkAutoConfigurationImports, checkAutoConfigurationClasses); + } + + private void addAnnotationProcessorsDependencies() { + this.project.getConfigurations() + .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME) + .getDependencies() + .addAll(projectDependencies( + ":spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-processor", + ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor")); + } + + private void configureAutoConfigurationMetadata(AutoConfigurationMetadata task) { + task.setSourceSet(this.main); + task.dependsOn(this.main.getClassesTaskName()); + task.getOutputFile() + .set(this.project.getLayout().getBuildDirectory().file("auto-configuration-metadata.properties")); + this.project.getArtifacts() + .add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, task.getOutputFile(), + (artifact) -> artifact.builtBy(task)); + } + + private void configureCheckAutoConfigurationImports(CheckAutoConfigurationImports task) { + task.setSource(this.main.getResources()); + task.setClasspath(this.main.getOutput().getClassesDirs()); + task.setDescription( + "Checks the %s file of the main source set.".formatted(AutoConfigurationImportsTask.IMPORTS_FILE)); + } + + private void configureCheckAutoConfigurationClasses(Configuration requiredClasspath, + CheckAutoConfigurationClasses task) { + task.setSource(this.main.getResources()); + task.setClasspath(this.main.getOutput().getClassesDirs()); + task.setRequiredDependencies(requiredClasspath); + task.setDescription("Checks the auto-configuration classes of the main source set."); + } + + private void configureCheckAutoConfigurationClassesForOptionalDependencies( + ConfigurationContainer configurations, + TaskProvider checkAutoConfigurationClasses) { + checkAutoConfigurationClasses.configure((check) -> { + Configuration optionalClasspath = configurations.create("autoConfigurationOptionalClassPath") + .extendsFrom(configurations.getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME)); + check.setOptionalDependencies(optionalClasspath); + }); + } + + private Set projectDependencies(String... paths) { + return Arrays.stream(paths).map((path) -> projectDependency(path)).collect(Collectors.toSet()); + } + + private Dependency projectDependency(String path) { + return this.project.getDependencies().project(Collections.singletonMap("path", path)); + } + } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java index 4d34cba44d9..f76682282e1 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java @@ -80,14 +80,12 @@ public abstract class DocumentAutoConfigurationClasses extends DefaultTask { writer.println("[cols=\"4,1\"]"); writer.println("|==="); writer.println("| Configuration Class | Links"); - for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses.classes) { writer.println(); writer.printf("| {code-spring-boot}/spring-boot-project/%s/src/main/java/%s.java[`%s`]%n", autoConfigurationClasses.module, autoConfigurationClass.path, autoConfigurationClass.name); writer.printf("| xref:api:java/%s.html[javadoc]%n", autoConfigurationClass.path); } - writer.println("|==="); } } From c688ae345cd19d6834237052928eb030411632ce Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 2 Jun 2025 17:10:54 -0700 Subject: [PATCH 4/5] Improve error report to include expected sorted output --- .../CheckAutoConfigurationImports.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java index 299e6bbdda6..06ce6a78ae7 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/CheckAutoConfigurationImports.java @@ -20,10 +20,10 @@ import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; -import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; @@ -82,7 +82,11 @@ public abstract class CheckAutoConfigurationImports extends AutoConfigurationImp List sortedValues = new ArrayList<>(imports); Collections.sort(sortedValues); if (!sortedValues.equals(imports)) { - problems.add("Entries should be sorted alphabetically"); + File sortedOutputFile = getOutputDirectory().file("sorted-" + importsFile.getName()).get().getAsFile(); + writeString(sortedOutputFile, + sortedValues.stream().collect(Collectors.joining(System.lineSeparator())) + System.lineSeparator()); + problems.add("Entries should be sorted alphabetically (expect content written to " + + sortedOutputFile.getAbsolutePath() + ")"); } File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); writeReport(importsFile, problems, outputFile); @@ -114,9 +118,12 @@ public abstract class CheckAutoConfigurationImports extends AutoConfigurationImp report.append("Found problems in '%s':%n".formatted(importsFile)); problems.forEach((problem) -> report.append(" - %s%n".formatted(problem))); } + writeString(outputFile, report.toString()); + } + + private void writeString(File file, String content) { try { - Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); + Files.writeString(file.toPath(), content); } catch (IOException ex) { throw new UncheckedIOException(ex); From 76caa3cb2974cefd864f2a4c890c9dbfb43075d2 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 2 Jun 2025 17:12:51 -0700 Subject: [PATCH 5/5] Fix problems found by improved auto-configuration checks --- .../JacksonEndpointAutoConfiguration.java | 6 +-- .../HibernateMetricsAutoConfiguration.java | 6 +-- ...ot.autoconfigure.AutoConfiguration.imports | 20 ++++----- .../reactor/ReactorAutoConfiguration.java | 4 +- ...ot.autoconfigure.AutoConfiguration.imports | 42 +++++++++---------- 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java index 9a409fa55d6..ad5bcd396de 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java @@ -21,13 +21,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; /** @@ -36,8 +35,7 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; * @author Phillip Webb * @since 3.0.0 */ -@Configuration(proxyBeanMethods = false) -@AutoConfigureAfter(JacksonAutoConfiguration.class) +@AutoConfiguration(after = JacksonAutoConfiguration.class) public class JacksonEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfiguration.java index 9de4febb50a..661e29ed091 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfiguration.java @@ -28,12 +28,11 @@ import org.hibernate.stat.HibernateMetrics; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; /** @@ -44,8 +43,7 @@ import org.springframework.util.StringUtils; * @author Stephane Nicoll * @since 2.1.0 */ -@Configuration(proxyBeanMethods = false) -@AutoConfigureAfter({ MetricsAutoConfiguration.class, HibernateJpaAutoConfiguration.class, +@AutoConfiguration(after = { MetricsAutoConfiguration.class, HibernateJpaAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class }) @ConditionalOnClass({ EntityManagerFactory.class, SessionFactory.class, HibernateMetrics.class, MeterRegistry.class }) @ConditionalOnBean({ EntityManagerFactory.class, MeterRegistry.class }) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index d3a6fc7ead5..12904887ad1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -7,14 +7,18 @@ org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfigurat org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.cassandra.CassandraReactiveHealthContributorAutoConfiguration -org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpointAutoConfiguration -org.springframework.boot.actuate.autoconfigure.context.properties.ConfigurationPropertiesReportEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.context.ShutdownEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.context.properties.ConfigurationPropertiesReportEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.couchbase.CouchbaseHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.couchbase.CouchbaseReactiveHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.data.elasticsearch.ElasticsearchReactiveHealthContributorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.data.mongo.MongoHealthContributorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.data.mongo.MongoReactiveHealthContributorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.data.redis.RedisHealthContributorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.data.redis.RedisReactiveHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchRestHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.endpoint.jackson.JacksonEndpointAutoConfiguration @@ -70,8 +74,6 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetri org.springframework.boot.actuate.autoconfigure.metrics.export.stackdriver.StackdriverMetricsExportAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdMetricsExportAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration -org.springframework.boot.actuate.autoconfigure.observation.batch.BatchObservationAutoConfiguration -org.springframework.boot.actuate.autoconfigure.observation.graphql.GraphQlObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.integration.IntegrationMetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.jersey.JerseyServerMetricsAutoConfiguration @@ -81,21 +83,19 @@ org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetri org.springframework.boot.actuate.autoconfigure.metrics.redis.LettuceMetricsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.startup.StartupTimeMetricsListenerAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.task.TaskExecutorMetricsAutoConfiguration -org.springframework.boot.actuate.autoconfigure.observation.web.client.HttpClientObservationsAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration -org.springframework.boot.actuate.autoconfigure.observation.web.reactive.WebFluxObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat.TomcatMetricsAutoConfiguration -org.springframework.boot.actuate.autoconfigure.data.mongo.MongoHealthContributorAutoConfiguration -org.springframework.boot.actuate.autoconfigure.data.mongo.MongoReactiveHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration +org.springframework.boot.actuate.autoconfigure.observation.batch.BatchObservationAutoConfiguration +org.springframework.boot.actuate.autoconfigure.observation.graphql.GraphQlObservationAutoConfiguration +org.springframework.boot.actuate.autoconfigure.observation.web.client.HttpClientObservationsAutoConfiguration +org.springframework.boot.actuate.autoconfigure.observation.web.reactive.WebFluxObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.observation.web.servlet.WebMvcObservationAutoConfiguration org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration org.springframework.boot.actuate.autoconfigure.quartz.QuartzEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.r2dbc.ConnectionFactoryHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.r2dbc.R2dbcObservationAutoConfiguration -org.springframework.boot.actuate.autoconfigure.data.redis.RedisHealthContributorAutoConfiguration -org.springframework.boot.actuate.autoconfigure.data.redis.RedisReactiveHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.sbom.SbomEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksObservabilityAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java index 9323e6eca46..9ab522efdf4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/reactor/ReactorAutoConfiguration.java @@ -18,10 +18,10 @@ package org.springframework.boot.autoconfigure.reactor; import reactor.core.publisher.Hooks; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; /** * {@link EnableAutoConfiguration Auto-configuration} for Reactor. @@ -29,7 +29,7 @@ import org.springframework.context.annotation.Configuration; * @author Brian Clozel * @since 3.2.0 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass(Hooks.class) @EnableConfigurationProperties(ReactorProperties.class) public class ReactorAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 38fe003d37f..20af895d575 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,6 +1,7 @@ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration -org.springframework.boot.autoconfigure.aop.AopAutoConfiguration org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration +org.springframework.boot.autoconfigure.aop.AopAutoConfiguration +org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration @@ -45,10 +46,10 @@ org.springframework.boot.autoconfigure.elasticsearch.ReactiveElasticsearchClient org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration -org.springframework.boot.autoconfigure.graphql.data.GraphQlReactiveQueryByExampleAutoConfiguration -org.springframework.boot.autoconfigure.graphql.data.GraphQlReactiveQuerydslAutoConfiguration org.springframework.boot.autoconfigure.graphql.data.GraphQlQueryByExampleAutoConfiguration org.springframework.boot.autoconfigure.graphql.data.GraphQlQuerydslAutoConfiguration +org.springframework.boot.autoconfigure.graphql.data.GraphQlReactiveQueryByExampleAutoConfiguration +org.springframework.boot.autoconfigure.graphql.data.GraphQlReactiveQuerydslAutoConfiguration org.springframework.boot.autoconfigure.graphql.reactive.GraphQlWebFluxAutoConfiguration org.springframework.boot.autoconfigure.graphql.rsocket.GraphQlRSocketAutoConfiguration org.springframework.boot.autoconfigure.graphql.rsocket.RSocketGraphQlClientAutoConfiguration @@ -68,23 +69,22 @@ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcClientAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration -org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration -org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration +org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration -org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration -org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration +org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration @@ -104,21 +104,21 @@ org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration -org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration -org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration -org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerJwtAutoConfiguration org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration +org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration +org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration org.springframework.boot.autoconfigure.session.SessionAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerJwtAutoConfiguration org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration @@ -140,13 +140,13 @@ org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfig org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration -org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration -org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration +org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration -org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration -org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration -org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration +org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration +org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration +org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration +org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration