diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java index c2870ca12dc..7211718d49f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java @@ -72,15 +72,19 @@ import org.gradle.api.tasks.VerificationException; */ public abstract class ArchitectureCheck extends DefaultTask { + private static final String CONDITIONAL_ON_CLASS_ANNOTATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnClass"; + private FileCollection classes; public ArchitectureCheck() { getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); + getConditionalOnClassAnnotation().convention(CONDITIONAL_ON_CLASS_ANNOTATION); getRules().addAll(getProhibitObjectsRequireNonNull().convention(true) .map(whenTrue(ArchitectureRules::noClassesShouldCallObjectsRequireNonNull))); getRules().addAll(ArchitectureRules.standard()); - getRules().addAll(whenMainSources( - () -> Collections.singletonList(ArchitectureRules.allBeanMethodsShouldReturnNonPrivateType()))); + getRules().addAll(whenMainSources(() -> List + .of(ArchitectureRules.allBeanMethodsShouldReturnNonPrivateType(), ArchitectureRules + .allBeanMethodsShouldNotHaveConditionalOnClassAnnotation(getConditionalOnClassAnnotation().get())))); getRules().addAll(and(getNullMarkedEnabled(), isMainSourceSet()).map(whenTrue(() -> Collections.singletonList( ArchitectureRules.packagesShouldBeAnnotatedWithNullMarked(getNullMarkedIgnoredPackages().get()))))); getRuleDescriptions().set(getRules().map(this::asDescriptions)); @@ -205,4 +209,7 @@ public abstract class ArchitectureCheck extends DefaultTask { @Internal abstract SetProperty getNullMarkedIgnoredPackages(); + @Internal + abstract Property getConditionalOnClassAnnotation(); + } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java index 6ed88affbb1..6c836ac3873 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java @@ -130,6 +130,15 @@ final class ArchitectureRules { .allowEmptyShould(true); } + static ArchRule allBeanMethodsShouldNotHaveConditionalOnClassAnnotation(String annotationName) { + return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").should() + .notBeAnnotatedWith(annotationName) + .because("@ConditionalOnClass on @Bean methods is ineffective - it doesn't prevent " + + "the method signature from being loaded. Such condition need to be placed" + + " on a @Configuration class, allowing the condition to back off before the type is loaded.") + .allowEmptyShould(true); + } + private static ArchRule allPackagesShouldBeFreeOfTangles() { return SlicesRuleDefinition.slices() .matching("(**)") diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java index 96c4bfa9d2e..25000681d0b 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.boot.build.architecture.annotations.TestConditionalOnClass; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.FileSystemUtils; @@ -183,7 +184,7 @@ class ArchitectureCheckTests { void whenClassCallsObjectsRequireNonNullWithMessageAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( Task task) throws IOException { prepareTask(task, "objects/requireNonNullWithString"); - build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task); + build(this.gradleBuild.withProhibitObjectsRequireNonNull(false), task); } @ParameterizedTest(name = "{0}") @@ -198,7 +199,7 @@ class ArchitectureCheckTests { void whenClassCallsObjectsRequireNonNullWithSupplierAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport( Task task) throws IOException { prepareTask(task, "objects/requireNonNullWithSupplier"); - build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task); + build(this.gradleBuild.withProhibitObjectsRequireNonNull(false), task); } @ParameterizedTest(name = "{0}") @@ -320,6 +321,25 @@ class ArchitectureCheckTests { "should not have a value that is the same as the type of the method's first parameter"); } + @Test + void whenConditionalOnClassUsedOnBeanMethodsWithMainSourcesShouldFailAndWriteReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "conditionalonclass", "annotations"); + GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT) + .withConditionalOnClassAnnotation(TestConditionalOnClass.class.getName()); + buildAndFail(gradleBuild, Task.CHECK_ARCHITECTURE_MAIN, + "because @ConditionalOnClass on @Bean methods is ineffective - it doesn't prevent" + + " the method signature from being loaded. Such condition need to be placed" + + " on a @Configuration class, allowing the condition to back off before the type is loaded"); + } + + @Test + void whenConditionalOnClassUsedOnBeanMethodsWithTestSourcesShouldSucceedAndWriteEmptyReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_TEST, "conditionalonclass", "annotations"); + GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT) + .withConditionalOnClassAnnotation(TestConditionalOnClass.class.getName()); + build(gradleBuild, Task.CHECK_ARCHITECTURE_TEST); + } + private void prepareTask(Task task, String... sourceDirectories) throws IOException { for (String sourceDirectory : sourceDirectories) { FileSystemUtils.copyRecursively( @@ -335,7 +355,7 @@ class ArchitectureCheckTests { private void build(GradleBuild gradleBuild, Task task) throws IOException { try { BuildResult buildResult = gradleBuild.build(task.toString()); - assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).contains(":" + task); + assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).as(buildResult.getOutput()).contains(":" + task); assertThat(task.getFailureReport(gradleBuild.getProjectDir())).isEmpty(); } catch (UnexpectedBuildFailure ex) { @@ -351,7 +371,7 @@ class ArchitectureCheckTests { private void buildAndFail(GradleBuild gradleBuild, Task task, String... messages) throws IOException { try { BuildResult buildResult = gradleBuild.buildAndFail(task.toString()); - assertThat(buildResult.taskPaths(TaskOutcome.FAILED)).contains(":" + task); + assertThat(buildResult.taskPaths(TaskOutcome.FAILED)).as(buildResult.getOutput()).contains(":" + task); assertThat(task.getFailureReport(gradleBuild.getProjectDir())).contains(messages); } catch (UnexpectedBuildSuccess ex) { @@ -396,10 +416,10 @@ class ArchitectureCheckTests { private final Set dependencies = new LinkedHashSet<>(); - private final Map prohibitObjectsRequireNonNull = new LinkedHashMap<>(); - private NullMarkedExtension nullMarkedExtension; + private final Map taskConfigurations = new LinkedHashMap<>(); + private GradleBuild(Path projectDir) { this.projectDir = projectDir; } @@ -408,8 +428,18 @@ class ArchitectureCheckTests { return this.projectDir; } - GradleBuild withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) { - this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull); + GradleBuild withProhibitObjectsRequireNonNull(Boolean prohibitObjectsRequireNonNull) { + for (Task task : Task.values()) { + configureTask(task, (configuration) -> configuration + .withProhibitObjectsRequireNonNull(prohibitObjectsRequireNonNull)); + } + return this; + } + + GradleBuild withConditionalOnClassAnnotation(String annotationName) { + for (Task task : Task.values()) { + configureTask(task, (configuration) -> configuration.withConditionalOnClassAnnotation(annotationName)); + } return this; } @@ -423,6 +453,11 @@ class ArchitectureCheckTests { return this; } + private void configureTask(Task task, UnaryOperator configurer) { + this.taskConfigurations.computeIfAbsent(task, (key) -> new TaskConfiguration(null, null)); + this.taskConfigurations.compute(task, (key, value) -> configurer.apply(value)); + } + private void configureNullMarkedExtension(UnaryOperator configurer) { NullMarkedExtension nullMarkedExtension = this.nullMarkedExtension; if (nullMarkedExtension == null) { @@ -432,6 +467,7 @@ class ArchitectureCheckTests { } GradleBuild withDependencies(String... dependencies) { + this.dependencies.clear(); this.dependencies.addAll(Arrays.asList(dependencies)); return this; } @@ -460,15 +496,22 @@ class ArchitectureCheckTests { if (!this.dependencies.isEmpty()) { buildFile.append("dependencies {\n"); for (String dependency : this.dependencies) { - buildFile.append(" implementation '%s'\n".formatted(dependency)); + buildFile.append("\n implementation ").append(StringUtils.quote(dependency)); } buildFile.append("}\n"); } - this.prohibitObjectsRequireNonNull.forEach((task, prohibitObjectsRequireNonNull) -> buildFile.append(task) - .append(" {\n") - .append(" prohibitObjectsRequireNonNull = ") - .append(prohibitObjectsRequireNonNull) - .append("\n}\n\n")); + this.taskConfigurations.forEach((task, configuration) -> { + buildFile.append(task).append(" {"); + if (configuration.conditionalOnClassAnnotation() != null) { + buildFile.append("\n conditionalOnClassAnnotation = ") + .append(StringUtils.quote(configuration.conditionalOnClassAnnotation())); + } + if (configuration.prohibitObjectsRequireNonNull() != null) { + buildFile.append("\n prohibitObjectsRequireNonNull = ") + .append(configuration.prohibitObjectsRequireNonNull()); + } + buildFile.append("\n}\n"); + }); NullMarkedExtension nullMarkedExtension = this.nullMarkedExtension; if (nullMarkedExtension != null) { buildFile.append("architectureCheck {"); @@ -505,6 +548,17 @@ class ArchitectureCheckTests { } + private record TaskConfiguration(Boolean prohibitObjectsRequireNonNull, String conditionalOnClassAnnotation) { + + private TaskConfiguration withConditionalOnClassAnnotation(String annotationName) { + return new TaskConfiguration(this.prohibitObjectsRequireNonNull, annotationName); + } + + private TaskConfiguration withProhibitObjectsRequireNonNull(Boolean prohibitObjectsRequireNonNull) { + return new TaskConfiguration(prohibitObjectsRequireNonNull, this.conditionalOnClassAnnotation); + } + } + } } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConditionalOnClass.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConditionalOnClass.java new file mode 100644 index 00000000000..e36dc4acce4 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConditionalOnClass.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-present 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.architecture.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code @ConditionalOnClass} analogue for architecture checks. + * + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestConditionalOnClass { + + Class[] value() default {}; + + String[] name() default {}; + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonclass/OnBeanMethod.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonclass/OnBeanMethod.java new file mode 100644 index 00000000000..5c2d63d6c8d --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonclass/OnBeanMethod.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-present 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.architecture.conditionalonclass; + +import org.springframework.boot.build.architecture.annotations.TestConditionalOnClass; +import org.springframework.context.annotation.Bean; + +class OnBeanMethod { + + @Bean + @TestConditionalOnClass(String.class) + String helloWorld() { + return "Hello World"; + } + +} diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc index 6ff48d410e4..3c80cee41bb 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc @@ -8,7 +8,7 @@ Spring Boot offers several focused, feature-specific `-test` modules: |Module | Purpose |`spring-boot-cache-test` -|Testing applications that use Spring Framework's cache abstration. +|Testing applications that use Spring Framework's cache abstraction. |`spring-boot-data-cassandra-test` |Testing applications that use Spring Data Cassandra. Provides the `@DataCassandraTest` test slice. diff --git a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java index fc94a94f5e6..5047886c583 100644 --- a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java +++ b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java @@ -51,6 +51,7 @@ import org.springframework.boot.autoconfigure.jmx.JmxProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.util.ObjectUtils; /** @@ -98,18 +99,6 @@ public final class JmxEndpointAutoConfiguration { return new DefaultEndpointObjectNameFactory(this.properties, this.jmxProperties, mBeanServer, contextId); } - @Bean - @ConditionalOnSingleCandidate(MBeanServer.class) - @ConditionalOnClass(JsonMapper.class) - JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, EndpointObjectNameFactory endpointObjectNameFactory, - ObjectProvider jsonMapper, JmxEndpointsSupplier jmxEndpointsSupplier) { - JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper(jsonMapper.getIfAvailable()); - return new JmxEndpointExporter(mBeanServer, endpointObjectNameFactory, responseMapper, - jmxEndpointsSupplier.getEndpoints()); - } - - // FIXME - @Bean IncludeExcludeEndpointFilter jmxIncludeExcludePropertyEndpointFilter() { JmxEndpointProperties.Exposure exposure = this.properties.getExposure(); @@ -127,4 +116,21 @@ public final class JmxEndpointAutoConfiguration { return OperationFilter.byAccess(endpointAccessResolver); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(JsonMapper.class) + static class JmxJacksonEndpointConfiguration { + + @Bean + @ConditionalOnSingleCandidate(MBeanServer.class) + JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, + EndpointObjectNameFactory endpointObjectNameFactory, ObjectProvider jsonMapper, + JmxEndpointsSupplier jmxEndpointsSupplier) { + JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper( + jsonMapper.getIfAvailable()); + return new JmxEndpointExporter(mBeanServer, endpointObjectNameFactory, responseMapper, + jmxEndpointsSupplier.getEndpoints()); + } + + } + } diff --git a/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration.java b/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration.java index 6cc1ef8114b..c1ed53d6d00 100644 --- a/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration.java +++ b/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration.java @@ -144,24 +144,6 @@ public final class FlywayAutoConfiguration { return new PropertiesFlywayConnectionDetails(this.properties); } - @Bean - @ConditionalOnClass(name = "org.flywaydb.database.sqlserver.SQLServerConfigurationExtension") - SqlServerFlywayConfigurationCustomizer sqlServerFlywayConfigurationCustomizer() { - return new SqlServerFlywayConfigurationCustomizer(this.properties); - } - - @Bean - @ConditionalOnClass(name = "org.flywaydb.database.oracle.OracleConfigurationExtension") - OracleFlywayConfigurationCustomizer oracleFlywayConfigurationCustomizer() { - return new OracleFlywayConfigurationCustomizer(this.properties); - } - - @Bean - @ConditionalOnClass(name = "org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension") - PostgresqlFlywayConfigurationCustomizer postgresqlFlywayConfigurationCustomizer() { - return new PostgresqlFlywayConfigurationCustomizer(this.properties); - } - @Bean Flyway flyway(FlywayConnectionDetails connectionDetails, ResourceLoader resourceLoader, ObjectProvider dataSource, @FlywayDataSource ObjectProvider flywayDataSource, @@ -360,6 +342,40 @@ public final class FlywayAutoConfiguration { return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable()); } + @ConditionalOnClass(name = "org.flywaydb.database.sqlserver.SQLServerConfigurationExtension") + @Configuration(proxyBeanMethods = false) + static class SqlServerConfiguration { + + @Bean + SqlServerFlywayConfigurationCustomizer sqlServerFlywayConfigurationCustomizer(FlywayProperties properties) { + return new SqlServerFlywayConfigurationCustomizer(properties); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.flywaydb.database.oracle.OracleConfigurationExtension") + static class OracleConfiguration { + + @Bean + OracleFlywayConfigurationCustomizer oracleFlywayConfigurationCustomizer(FlywayProperties properties) { + return new OracleFlywayConfigurationCustomizer(properties); + } + + } + + @ConditionalOnClass(name = "org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension") + @Configuration(proxyBeanMethods = false) + static class PostgresqlConfiguration { + + @Bean + PostgresqlFlywayConfigurationCustomizer postgresqlFlywayConfigurationCustomizer( + FlywayProperties properties) { + return new PostgresqlFlywayConfigurationCustomizer(properties); + } + + } + } private static class LocationResolver { diff --git a/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/AvailabilityProbesAutoConfigurationTests.java b/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/AvailabilityProbesAutoConfigurationTests.java index f32bdbd0d36..86fc35beafd 100644 --- a/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/AvailabilityProbesAutoConfigurationTests.java +++ b/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/AvailabilityProbesAutoConfigurationTests.java @@ -54,7 +54,7 @@ class AvailabilityProbesAutoConfigurationTests { } @Test - void probesWhenNoActautorDependencyDoesNotAddBeans() { + void probesWhenNoActuatorDependencyDoesNotAddBeans() { this.contextRunner.withClassLoader(new FilteredClassLoader(Endpoint.class.getName())) .run(this::doesNotHaveProbeBeans); } diff --git a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/jvm/JvmMetricsAutoConfiguration.java b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/jvm/JvmMetricsAutoConfiguration.java index 9f78a983999..2b3f5c3224b 100644 --- a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/jvm/JvmMetricsAutoConfiguration.java +++ b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/jvm/JvmMetricsAutoConfiguration.java @@ -39,6 +39,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.util.ClassUtils; @@ -98,14 +99,19 @@ public final class JvmMetricsAutoConfiguration { return new JvmCompilationMetrics(); } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = VIRTUAL_THREAD_METRICS_CLASS) - @ConditionalOnMissingBean(type = VIRTUAL_THREAD_METRICS_CLASS) - @ImportRuntimeHints(VirtualThreadMetricsRuntimeHintsRegistrar.class) - MeterBinder virtualThreadMetrics() throws ClassNotFoundException { - Class virtualThreadMetricsClass = ClassUtils.forName(VIRTUAL_THREAD_METRICS_CLASS, - getClass().getClassLoader()); - return (MeterBinder) BeanUtils.instantiateClass(virtualThreadMetricsClass); + static class VirtualThreadMetricsConfiguration { + + @Bean + @ConditionalOnMissingBean(type = VIRTUAL_THREAD_METRICS_CLASS) + @ImportRuntimeHints(VirtualThreadMetricsRuntimeHintsRegistrar.class) + MeterBinder virtualThreadMetrics() throws ClassNotFoundException { + Class virtualThreadMetricsClass = ClassUtils.forName(VIRTUAL_THREAD_METRICS_CLASS, + getClass().getClassLoader()); + return (MeterBinder) BeanUtils.instantiateClass(virtualThreadMetricsClass); + } + } static final class VirtualThreadMetricsRuntimeHintsRegistrar implements RuntimeHintsRegistrar { diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/SecurityRequestMatchersManagementContextConfiguration.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/SecurityRequestMatchersManagementContextConfiguration.java index 394216d9f72..58317b798d3 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/SecurityRequestMatchersManagementContextConfiguration.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/SecurityRequestMatchersManagementContextConfiguration.java @@ -47,7 +47,6 @@ public class SecurityRequestMatchersManagementContextConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnClass(DispatcherServlet.class) public RequestMatcherProvider requestMatcherProvider(DispatcherServletPath servletPath) { return new PathPatternRequestMatcherProvider(servletPath::getRelativePath); } diff --git a/module/spring-boot-zipkin/src/main/java/org/springframework/boot/zipkin/autoconfigure/ZipkinAutoConfiguration.java b/module/spring-boot-zipkin/src/main/java/org/springframework/boot/zipkin/autoconfigure/ZipkinAutoConfiguration.java index f9f0d7b3675..6cb3b43a208 100644 --- a/module/spring-boot-zipkin/src/main/java/org/springframework/boot/zipkin/autoconfigure/ZipkinAutoConfiguration.java +++ b/module/spring-boot-zipkin/src/main/java/org/springframework/boot/zipkin/autoconfigure/ZipkinAutoConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; /** * {@link EnableAutoConfiguration Auto-configuration} for Zipkin. @@ -61,21 +62,26 @@ public final class ZipkinAutoConfiguration { }; } - @Bean - @ConditionalOnMissingBean(BytesMessageSender.class) + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HttpClient.class) - ZipkinHttpClientSender httpClientSender(ZipkinProperties properties, Encoding encoding, - ObjectProvider customizers, - ObjectProvider connectionDetailsProvider, - ObjectProvider endpointSupplierFactoryProvider) { - ZipkinConnectionDetails connectionDetails = connectionDetailsProvider - .getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties)); - HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider - .getIfAvailable(HttpEndpointSuppliers::constantFactory); - Builder httpClientBuilder = HttpClient.newBuilder().connectTimeout(properties.getConnectTimeout()); - customizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder)); - return new ZipkinHttpClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(), - httpClientBuilder.build(), properties.getReadTimeout()); + static class ZipkinHttpClientConfiguration { + + @Bean + @ConditionalOnMissingBean(BytesMessageSender.class) + ZipkinHttpClientSender httpClientSender(ZipkinProperties properties, Encoding encoding, + ObjectProvider customizers, + ObjectProvider connectionDetailsProvider, + ObjectProvider endpointSupplierFactoryProvider) { + ZipkinConnectionDetails connectionDetails = connectionDetailsProvider + .getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties)); + HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider + .getIfAvailable(HttpEndpointSuppliers::constantFactory); + Builder httpClientBuilder = HttpClient.newBuilder().connectTimeout(properties.getConnectTimeout()); + customizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder)); + return new ZipkinHttpClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(), + httpClientBuilder.build(), properties.getReadTimeout()); + } + } }