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 6ad9c3d2ee8c..c0d2f07b0e51 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 @@ -70,15 +70,19 @@ */ 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())))); getRuleDescriptions().set(getRules().map(this::asDescriptions)); } @@ -186,4 +190,7 @@ final FileTree getInputClasses() { @Input // Use descriptions as input since rules aren't serializable abstract ListProperty getRuleDescriptions(); + @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 31cbb842a233..77d27e5fa869 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 @@ -108,6 +108,15 @@ static ArchRule allBeanMethodsShouldReturnNonPrivateType() { .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("(**)").should().beFreeOfCycles(); } 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 20a0c177da6f..1063fb3e42be 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 @@ -26,6 +26,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.function.UnaryOperator; import org.gradle.api.tasks.SourceSet; import org.gradle.testkit.runner.BuildResult; @@ -39,6 +40,7 @@ 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.FileSystemUtils; import org.springframework.util.StringUtils; @@ -180,7 +182,7 @@ void whenClassCallsObjectsRequireNonNullWithMessageShouldFailAndWriteReport(Task 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}") @@ -195,7 +197,7 @@ void whenClassCallsObjectsRequireNonNullWithSupplierShouldFailAndWriteReport(Tas 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}") @@ -295,6 +297,25 @@ void whenEnumSourceValueIsSameAsTypeOfMethodsFirstParameterShouldFailAndWriteRep "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( @@ -310,7 +331,7 @@ private void prepareTask(Task task, String... sourceDirectories) throws IOExcept 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) { @@ -326,7 +347,7 @@ private void build(GradleBuild gradleBuild, Task task) throws IOException { 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) { @@ -371,7 +392,7 @@ private static final class GradleBuild { private final Set dependencies = new LinkedHashSet<>(); - private final Map prohibitObjectsRequireNonNull = new LinkedHashMap<>(); + private final Map taskConfigurations = new LinkedHashMap<>(); private GradleBuild(Path projectDir) { this.projectDir = projectDir; @@ -381,12 +402,28 @@ Path getProjectDir() { 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; } + 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)); + } + GradleBuild withDependencies(String... dependencies) { + this.dependencies.clear(); this.dependencies.addAll(Arrays.asList(dependencies)); return this; } @@ -415,15 +452,22 @@ private GradleRunner prepareRunner(String... arguments) throws IOException { 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"); + }); Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8); return GradleRunner.create() .withProjectDir(this.projectDir.toFile()) @@ -431,6 +475,17 @@ private GradleRunner prepareRunner(String... arguments) throws IOException { .withPluginClasspath(); } + 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 000000000000..e36dc4acce4a --- /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 000000000000..5c2d63d6c8d6 --- /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/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java index f3b51a078ae9..ceddfa9e5766 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditAutoConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link AuditEvent}s. @@ -50,18 +51,28 @@ public AuditListener auditListener(AuditEventRepository auditEventRepository) { return new AuditListener(auditEventRepository); } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.springframework.security.authentication.event.AbstractAuthenticationEvent") - @ConditionalOnMissingBean(AbstractAuthenticationAuditListener.class) - public AuthenticationAuditListener authenticationAuditListener() { - return new AuthenticationAuditListener(); + static class AuthenticationAuditConfiguration { + + @Bean + @ConditionalOnMissingBean(AbstractAuthenticationAuditListener.class) + AuthenticationAuditListener authenticationAuditListener() { + return new AuthenticationAuditListener(); + } + } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.springframework.security.access.event.AbstractAuthorizationEvent") - @ConditionalOnMissingBean(AbstractAuthorizationAuditListener.class) - public AuthorizationAuditListener authorizationAuditListener() { - return new AuthorizationAuditListener(); + static class AuthorizationAuditConfiguration { + + @Bean + @ConditionalOnMissingBean(AbstractAuthorizationAuditListener.class) + AuthorizationAuditListener authorizationAuditListener() { + return new AuthorizationAuditListener(); + } + } } 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 e439dedc1932..f71688749635 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 @@ -36,12 +36,12 @@ * @since 3.0.0 */ @AutoConfiguration(after = JacksonAutoConfiguration.class) +@ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) public class JacksonEndpointAutoConfiguration { @Bean @ConditionalOnProperty(name = "management.endpoints.jackson.isolated-object-mapper", matchIfMissing = true) - @ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) - public EndpointObjectMapper endpointObjectMapper() { + EndpointObjectMapper endpointObjectMapper() { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/SecurityRequestMatchersManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/SecurityRequestMatchersManagementContextConfiguration.java index 1eb231f5c8bd..63afbab8f228 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/SecurityRequestMatchersManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/SecurityRequestMatchersManagementContextConfiguration.java @@ -53,7 +53,6 @@ public static class MvcRequestMatcherConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnClass(DispatcherServlet.class) public RequestMatcherProvider requestMatcherProvider(DispatcherServletPath servletPath) { return new AntPathRequestMatcherProvider(servletPath::getRelativePath); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java index 803979cb14a2..5dff54bcf77a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java @@ -81,22 +81,37 @@ ServletManagementWebServerFactoryCustomizer servletManagementWebServerFactoryCus return new ServletManagementWebServerFactoryCustomizer(beanFactory); } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "io.undertow.Undertow") - UndertowAccessLogCustomizer undertowManagementAccessLogCustomizer() { - return new UndertowAccessLogCustomizer(); + static class UndertowConfiguration { + + @Bean + UndertowAccessLogCustomizer undertowManagementAccessLogCustomizer() { + return new UndertowAccessLogCustomizer(); + } + } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.apache.catalina.valves.AccessLogValve") - TomcatAccessLogCustomizer tomcatManagementAccessLogCustomizer() { - return new TomcatAccessLogCustomizer(); + static class TomcatConfiguration { + + @Bean + TomcatAccessLogCustomizer tomcatManagementAccessLogCustomizer() { + return new TomcatAccessLogCustomizer(); + } + } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.eclipse.jetty.server.Server") - JettyAccessLogCustomizer jettyManagementAccessLogCustomizer() { - return new JettyAccessLogCustomizer(); + static class JettyConfiguration { + + @Bean + JettyAccessLogCustomizer jettyManagementAccessLogCustomizer() { + return new JettyAccessLogCustomizer(); + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index 7a74ffb43834..a656c684e3bb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -144,24 +144,6 @@ PropertiesFlywayConnectionDetails flywayConnectionDetails() { 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, @@ -355,6 +337,40 @@ public FlywayMigrationInitializer flywayInitializer(Flyway flyway, 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/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java index ecf097d85e92..359910e3d995 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java @@ -57,13 +57,18 @@ @EnableConfigurationProperties(HateoasProperties.class) public class HypermediaAutoConfiguration { - @Bean - @ConditionalOnMissingBean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper") @ConditionalOnProperty(prefix = "spring.hateoas", name = "use-hal-as-default-json-media-type", matchIfMissing = true) - HalConfiguration applicationJsonHalConfiguration() { - return new HalConfiguration().withMediaType(MediaType.APPLICATION_JSON); + static class JacksonJsonHalConfiguration { + + @Bean + @ConditionalOnMissingBean + HalConfiguration applicationJsonHalConfiguration() { + return new HalConfiguration().withMediaType(MediaType.APPLICATION_JSON); + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfiguration.java index f38c84f24f10..e36d0a47b908 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarReactiveAutoConfiguration.java @@ -87,16 +87,6 @@ ReactivePulsarClient reactivePulsarClient(PulsarClient pulsarClient) { return AdaptedReactivePulsarClientFactory.create(pulsarClient); } - @Bean - @ConditionalOnMissingBean(ProducerCacheProvider.class) - @ConditionalOnClass(CaffeineShadedProducerCacheProvider.class) - @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "true", matchIfMissing = true) - CaffeineShadedProducerCacheProvider reactivePulsarProducerCacheProvider() { - PulsarProperties.Producer.Cache properties = this.properties.getProducer().getCache(); - return new CaffeineShadedProducerCacheProvider(properties.getExpireAfterAccess(), Duration.ofMinutes(10), - properties.getMaximumSize(), properties.getInitialCapacity()); - } - @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "true", matchIfMissing = true) @@ -205,6 +195,21 @@ ReactivePulsarTemplate pulsarReactiveTemplate(ReactivePulsarSenderFactory return new ReactivePulsarTemplate<>(reactivePulsarSenderFactory, schemaResolver, topicResolver); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(CaffeineShadedProducerCacheProvider.class) + @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "true", matchIfMissing = true) + static class PulsarCacheConfiguration { + + @Bean + @ConditionalOnMissingBean(ProducerCacheProvider.class) + CaffeineShadedProducerCacheProvider reactivePulsarProducerCacheProvider(PulsarProperties pulsarProperties) { + PulsarProperties.Producer.Cache properties = pulsarProperties.getProducer().getCache(); + return new CaffeineShadedProducerCacheProvider(properties.getExpireAfterAccess(), Duration.ofMinutes(10), + properties.getMaximumSize(), properties.getInitialCapacity()); + } + + } + @Configuration(proxyBeanMethods = false) @EnableReactivePulsar @ConditionalOnMissingBean( diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfiguration.java index c7b2516edbac..17fb3d630e8f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.Ordered; @@ -69,13 +70,6 @@ public ReactiveWebServerFactoryCustomizer reactiveWebServerFactoryCustomizer(Ser return new ReactiveWebServerFactoryCustomizer(serverProperties, sslBundles.getIfAvailable()); } - @Bean - @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") - public TomcatReactiveWebServerFactoryCustomizer tomcatReactiveWebServerFactoryCustomizer( - ServerProperties serverProperties) { - return new TomcatReactiveWebServerFactoryCustomizer(serverProperties); - } - @Bean @ConditionalOnMissingBean @ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework") @@ -83,6 +77,18 @@ public ForwardedHeaderTransformer forwardedHeaderTransformer() { return new ForwardedHeaderTransformer(); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") + static class TomcatConfiguration { + + @Bean + TomcatReactiveWebServerFactoryCustomizer tomcatReactiveWebServerFactoryCustomizer( + ServerProperties serverProperties) { + return new TomcatReactiveWebServerFactoryCustomizer(serverProperties); + } + + } + /** * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via * {@link ImportBeanDefinitionRegistrar} for early registration. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.java index fb876d977974..51acc9235f9d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.java @@ -81,11 +81,16 @@ public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(Serve cookieSameSiteSuppliers.orderedStream().toList(), sslBundles.getIfAvailable()); } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") - public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( - ServerProperties serverProperties) { - return new TomcatServletWebServerFactoryCustomizer(serverProperties); + static class TomcatConfiguration { + + @Bean + TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( + ServerProperties serverProperties) { + return new TomcatServletWebServerFactoryCustomizer(serverProperties); + } + } @Configuration(proxyBeanMethods = false) @@ -93,12 +98,6 @@ public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCust @ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class) static class ForwardedHeaderFilterConfiguration { - @Bean - @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") - ForwardedHeaderFilterCustomizer tomcatForwardedHeaderFilterCustomizer(ServerProperties serverProperties) { - return (filter) -> filter.setRelativeRedirects(serverProperties.getTomcat().isUseRelativeRedirects()); - } - @Bean FilterRegistrationBean forwardedHeaderFilter( ObjectProvider customizerProvider) { @@ -110,6 +109,17 @@ FilterRegistrationBean forwardedHeaderFilter( return registration; } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") + static class TomcatConfiguration { + + @Bean + ForwardedHeaderFilterCustomizer tomcatForwardedHeaderFilterCustomizer(ServerProperties serverProperties) { + return (filter) -> filter.setRelativeRedirects(serverProperties.getTomcat().isUseRelativeRedirects()); + } + + } + } interface ForwardedHeaderFilterCustomizer {