From b9c2c289b38c3a99ae2c3de9270943452bb74a99 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 8 Jun 2023 10:06:07 +0100 Subject: [PATCH 0001/1656] Start working on Spring Boot 3.2 This commit also disables the creation of forward merge issues when merging into main. Forward merge issues will be re-enabled once 3.1.1 has been released. --- README.adoc | 2 +- ci/README.adoc | 2 +- ci/parameters.yml | 2 +- ci/pipeline.yml | 4 +- eclipse/spring-boot-project.setup | 4 +- git/hooks/prepare-forward-merge | 8 +- gradle.properties | 2 +- .../spring-boot-dependencies/build.gradle | 2 +- .../jar-layered-custom/jar/src/layers.xml | 2 +- .../war-layered-custom/war/src/layers.xml | 2 +- .../src/main/xsd/layers-3.2.xsd | 100 ++++++++++++++++++ .../dependencies-layer-no-filter.xml | 2 +- .../src/test/resources/layers.xml | 2 +- .../resources/resource-layer-no-filter.xml | 2 +- 14 files changed, 119 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/xsd/layers-3.2.xsd diff --git a/README.adoc b/README.adoc index b0e635135a51..b603722e4e25 100755 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -= Spring Boot image:https://ci.spring.io/api/v1/teams/spring-boot/pipelines/spring-boot-3.1.x/jobs/build/badge["Build Status", link="https://ci.spring.io/teams/spring-boot/pipelines/spring-boot-3.1.x?groups=Build"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"] += Spring Boot image:https://ci.spring.io/api/v1/teams/spring-boot/pipelines/spring-boot-3.2.x/jobs/build/badge["Build Status", link="https://ci.spring.io/teams/spring-boot/pipelines/spring-boot-3.2.x?groups=Build"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"] :docs: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference :github: https://github.com/spring-projects/spring-boot diff --git a/ci/README.adoc b/ci/README.adoc index 4601e84e0b13..5ef82f3ab88d 100644 --- a/ci/README.adoc +++ b/ci/README.adoc @@ -11,7 +11,7 @@ The pipeline can be deployed using the following command: [source] ---- -$ fly -t spring-boot set-pipeline -p spring-boot-3.1.x -c ci/pipeline.yml -l ci/parameters.yml +$ fly -t spring-boot set-pipeline -p spring-boot-3.2.x -c ci/pipeline.yml -l ci/parameters.yml ---- NOTE: This assumes that you have credhub integration configured with the appropriate diff --git a/ci/parameters.yml b/ci/parameters.yml index 5bc1412be5e6..5821375bd742 100644 --- a/ci/parameters.yml +++ b/ci/parameters.yml @@ -4,7 +4,7 @@ homebrew-tap-repo: "https://github.com/spring-io/homebrew-tap.git" docker-hub-organization: "springci" artifactory-server: "https://repo.spring.io" branch: "main" -milestone: "3.1.x" +milestone: "3.2.x" build-name: "spring-boot" concourse-url: "https://ci.spring.io" task-timeout: 2h00m diff --git a/ci/pipeline.yml b/ci/pipeline.yml index de7c64bc4fb9..15109f6d2e39 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -591,7 +591,7 @@ jobs: <<: *sdkman-task-params RELEASE_TYPE: RELEASE BRANCH: ((branch)) - LATEST_GA: true + LATEST_GA: false - name: update-homebrew-tap serial: true plan: @@ -607,7 +607,7 @@ jobs: image: ci-image file: git-repo/ci/tasks/update-homebrew-tap.yml params: - LATEST_GA: true + LATEST_GA: false - put: homebrew-tap-repo params: repository: updated-homebrew-tap-repo diff --git a/eclipse/spring-boot-project.setup b/eclipse/spring-boot-project.setup index c370697aad4a..f1fa2ed8f26e 100644 --- a/eclipse/spring-boot-project.setup +++ b/eclipse/spring-boot-project.setup @@ -11,8 +11,8 @@ xmlns:setup.workingsets="http://www.eclipse.org/oomph/setup/workingsets/1.0" xmlns:workingsets="http://www.eclipse.org/oomph/workingsets/1.0" xsi:schemaLocation="http://www.eclipse.org/oomph/setup/jdt/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/JDT.ecore http://www.eclipse.org/buildship/oomph/1.0 https://raw.githubusercontent.com/eclipse/buildship/master/org.eclipse.buildship.oomph/model/GradleImport-1.0.ecore http://www.eclipse.org/oomph/predicates/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/Predicates.ecore http://www.eclipse.org/oomph/setup/workingsets/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/SetupWorkingSets.ecore http://www.eclipse.org/oomph/workingsets/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/WorkingSets.ecore" - name="spring.boot.3.1.x" - label="Spring Boot 3.1.x"> + name="spring.boot.3.2.x" + label="Spring Boot 3.2.x"> + https://www.springframework.org/schema/layers/layers-3.2.xsd"> **/application*.* diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/src/layers.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/src/layers.xml index 418078fe423e..41e9157bb728 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/src/layers.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/war-layered-custom/war/src/layers.xml @@ -1,7 +1,7 @@ + https://www.springframework.org/schema/layers/layers-3.2.xsd"> **/application*.* diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/xsd/layers-3.2.xsd b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/xsd/layers-3.2.xsd new file mode 100644 index 000000000000..20219b9bd8b1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/xsd/layers-3.2.xsd @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/dependencies-layer-no-filter.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/dependencies-layer-no-filter.xml index 1398e8320206..b6e9af44d621 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/dependencies-layer-no-filter.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/dependencies-layer-no-filter.xml @@ -1,7 +1,7 @@ + https://www.springframework.org/schema/boot/layers/layers-3.2.xsd"> diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/layers.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/layers.xml index b7427f83a79b..81f10e26311c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/layers.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/layers.xml @@ -1,7 +1,7 @@ + https://www.springframework.org/schema/boot/layers/layers-3.2.xsd"> META-INF/resources/** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/resource-layer-no-filter.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/resource-layer-no-filter.xml index 0614492c4982..ebfb721fb7c2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/resource-layer-no-filter.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/resources/resource-layer-no-filter.xml @@ -1,7 +1,7 @@ + https://www.springframework.org/schema/boot/layers/layers-3.2.xsd"> From 214f06083b6d79d4540564064d0dcefdad9d2a73 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 13 Jun 2023 08:14:22 +0200 Subject: [PATCH 0002/1656] Auto-configure OtlpHttpSpanExporter only if property is set - Remove the default value of 'management.otlp.tracing.endpoint' Closes gh-35596 --- .../tracing/otlp/OtlpAutoConfiguration.java | 3 +++ .../autoconfigure/tracing/otlp/OtlpProperties.java | 2 +- .../tracing/otlp/OtlpAutoConfigurationTests.java | 11 +++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java index c9493f0d0d99..ca2012c20d64 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -45,6 +46,7 @@ * define an {@link OtlpGrpcSpanExporter} and this auto-configuration will back off. * * @author Jonatan Ivanov + * @author Moritz Halbritter * @since 3.1.0 */ @AutoConfiguration @@ -56,6 +58,7 @@ public class OtlpAutoConfiguration { @Bean @ConditionalOnMissingBean(value = OtlpHttpSpanExporter.class, type = "io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter") + @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "endpoint") OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties) { OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder() .setEndpoint(properties.getEndpoint()) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java index efb1a32f7553..371de8491146 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java @@ -34,7 +34,7 @@ public class OtlpProperties { /** * URL to the OTel collector's HTTP API. */ - private String endpoint = "http://localhost:4318/v1/traces"; + private String endpoint; /** * Call timeout for the OTel Collector to process an exported batch of data. This diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java index 5d75c06b5112..f3b3e5b3a5a9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java @@ -33,16 +33,23 @@ * Tests for {@link OtlpAutoConfiguration}. * * @author Jonatan Ivanov + * @author Moritz Halbritter */ class OtlpAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(OtlpAutoConfiguration.class)); + @Test + void shouldNotSupplyBeansIfPropertyIsNotSet() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(OtlpHttpSpanExporter.class)); + } + @Test void shouldSupplyBeans() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) - .hasSingleBean(SpanExporter.class)); + this.contextRunner.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") + .run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) + .hasSingleBean(SpanExporter.class)); } @Test From 5b06224af53098a9aa546e51ba6408b2c3ec034a Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 13 Jun 2023 09:28:58 +0200 Subject: [PATCH 0003/1656] Add property for common key/values on observations - Deprecates 'management.metrics.tags.*' Closes gh-33241 --- .../metrics/MetricsProperties.java | 1 + .../ObservationAutoConfiguration.java | 7 +++ .../observation/ObservationProperties.java | 16 +++++ .../PropertiesObservationFilter.java | 51 +++++++++++++++ .../ObservationAutoConfigurationTests.java | 15 +++++ .../PropertiesObservationFilterTests.java | 62 +++++++++++++++++++ .../src/docs/asciidoc/actuator/metrics.adoc | 13 +--- .../docs/asciidoc/actuator/observability.adoc | 20 +++++- 8 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java index f06176bb95dc..e4ad80555230 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java @@ -79,6 +79,7 @@ public Map getEnable() { return this.enable; } + @DeprecatedConfigurationProperty(replacement = "management.observations.key-values") public Map getTags() { return this.tags; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java index 216bc4262408..440fc9bf9656 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java @@ -43,6 +43,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; /** * {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API. @@ -75,6 +76,12 @@ ObservationRegistry observationRegistry() { return ObservationRegistry.create(); } + @Bean + @Order(0) + PropertiesObservationFilter propertiesObservationFilter(ObservationProperties properties) { + return new PropertiesObservationFilter(properties); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MeterRegistry.class) @ConditionalOnMissingClass("io.micrometer.tracing.Tracer") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java index e4668d7f4b59..92f8e8b0fe2d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.autoconfigure.observation; +import java.util.LinkedHashMap; +import java.util.Map; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -30,10 +33,23 @@ public class ObservationProperties { private final Http http = new Http(); + /** + * Common key-values that are applied to every observation. + */ + private Map keyValues = new LinkedHashMap<>(); + public Http getHttp() { return this.http; } + public Map getKeyValues() { + return this.keyValues; + } + + public void setKeyValues(Map keyValues) { + this.keyValues = keyValues; + } + public static class Http { private final Client client = new Client(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java new file mode 100644 index 000000000000..d1116aa4ddb5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.observation; + +import java.util.Map.Entry; + +import io.micrometer.common.KeyValues; +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationFilter; + +/** + * {@link ObservationFilter} to apply settings from {@link ObservationProperties}. + * + * @author Moritz Halbritter + */ +class PropertiesObservationFilter implements ObservationFilter { + + private final ObservationFilter delegate; + + PropertiesObservationFilter(ObservationProperties properties) { + this.delegate = createDelegate(properties); + } + + @Override + public Context map(Context context) { + return this.delegate.map(context); + } + + private static ObservationFilter createDelegate(ObservationProperties properties) { + if (properties.getKeyValues().isEmpty()) { + return (context) -> context; + } + KeyValues keyValues = KeyValues.of(properties.getKeyValues().entrySet(), Entry::getKey, Entry::getValue); + return (context) -> context.addLowCardinalityKeyValues(keyValues); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index 28e4bd0100e2..186adde6056a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java @@ -189,6 +189,21 @@ void autoConfiguresObservationFilters() { }); } + @Test + void shouldSupplyPropertiesObservationFilterBean() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesObservationFilter.class)); + } + + @Test + void shouldApplyCommonKeyValuesToObservations() { + this.contextRunner.withPropertyValues("management.observations.key-values.a=alpha").run((context) -> { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("keyvalues", observationRegistry).stop(); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThat(meterRegistry.get("keyvalues").tag("a", "alpha").timer().count()).isOne(); + }); + } + @Test void autoConfiguresGlobalObservationConventions() { this.contextRunner.withUserConfiguration(CustomGlobalObservationConvention.class).run((context) -> { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java new file mode 100644 index 000000000000..b352f9695d1a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.observation; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import io.micrometer.observation.Observation.Context; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PropertiesObservationFilter}. + * + * @author Moritz Halbritter + */ +class PropertiesObservationFilterTests { + + @Test + void shouldDoNothingIfKeyValuesAreEmpty() { + PropertiesObservationFilter filter = createFilter(); + Context mapped = mapContext(filter, "a", "alpha"); + assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha")); + } + + @Test + void shouldAddKeyValues() { + PropertiesObservationFilter filter = createFilter("b", "beta"); + Context mapped = mapContext(filter, "a", "alpha"); + assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"), + KeyValue.of("b", "beta")); + } + + private static Context mapContext(PropertiesObservationFilter filter, String... initialKeyValues) { + Context context = new Context(); + context.addLowCardinalityKeyValues(KeyValues.of(initialKeyValues)); + return filter.map(context); + } + + private static PropertiesObservationFilter createFilter(String... keyValues) { + ObservationProperties properties = new ObservationProperties(); + for (int i = 0; i < keyValues.length; i += 2) { + properties.getKeyValues().put(keyValues[i], keyValues[i + 1]); + } + return new PropertiesObservationFilter(properties); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc index 2f9ce53ec614..96d671570bc2 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc @@ -1100,19 +1100,8 @@ These use the global registry that is not Spring-managed. [[actuator.metrics.customizing.common-tags]] ==== Common Tags -Common tags are generally used for dimensional drill-down on the operating environment, such as host, instance, region, stack, and others. -Commons tags are applied to all meters and can be configured, as the following example shows: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - management: - metrics: - tags: - region: "us-east-1" - stack: "prod" ----- - -The preceding example adds `region` and `stack` tags to all meters with a value of `us-east-1` and `prod`, respectively. +You can configure common tags using the <>. NOTE: The order of common tags is important if you use Graphite. As the order of common tags cannot be guaranteed by using this approach, Graphite users are advised to define a custom `MeterFilter` instead. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc index b7db70d50219..f077a1409c23 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc @@ -9,9 +9,9 @@ To create your own observations (which will lead to metrics and traces), you can include::code:MyCustomObservation[] -NOTE: Low cardinality tags will be added to metrics and traces, while high cardinality tags will only be added to traces. +NOTE: Low cardinality key-values will be added to metrics and traces, while high cardinality key-values will only be added to traces. -Beans of type `ObservationPredicate`, `GlobalObservationConvention` and `ObservationHandler` will be automatically registered on the `ObservationRegistry`. +Beans of type `ObservationPredicate`, `GlobalObservationConvention`, `ObservationFilter` and `ObservationHandler` will be automatically registered on the `ObservationRegistry`. You can additionally register any number of `ObservationRegistryCustomizer` beans to further configure the registry. For more details please see the https://micrometer.io/docs/observation[Micrometer Observation documentation]. @@ -21,4 +21,20 @@ For JDBC, the https://github.com/jdbc-observations/datasource-micrometer[Datasou Read more about it https://jdbc-observations.github.io/datasource-micrometer/docs/current/docs/html/[in the reference documentation]. For R2DBC, the https://github.com/spring-projects-experimental/r2dbc-micrometer-spring-boot[Spring Boot Auto Configuration for R2DBC Observation] creates observations for R2DBC query invocations. +[[actuator.observability.common-key-values]] +=== Common Key-Values +Common key-values are generally used for dimensional drill-down on the operating environment, such as host, instance, region, stack, and others. +Commons key-values are applied to all observations as low cardinality key-values and can be configured, as the following example shows: + +[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +---- + management: + observations: + key-values: + region: "us-east-1" + stack: "prod" +---- + +The preceding example adds `region` and `stack` key-values to all observations with a value of `us-east-1` and `prod`, respectively. + The next sections will provide more details about logging, metrics and traces. From 491e12ab5e8a0cec272ac7e88e7b3edaee0130ba Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 13 Jun 2023 10:42:52 +0200 Subject: [PATCH 0004/1656] Add property to disable Spring Security observations Setting 'management.observations.spring-security.enabled' installs an ObservationPredicate, which prevents all observations starting with 'spring.security.' to be created. Closes gh-34802 --- .../ObservationAutoConfiguration.java | 7 ++++ ...itional-spring-configuration-metadata.json | 6 ++++ .../ObservationAutoConfigurationTests.java | 22 +++++++++++++ .../docs/asciidoc/actuator/observability.adoc | 12 +++++++ .../MyObservationPredicate.java | 32 +++++++++++++++++++ 5 files changed, 79 insertions(+) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java index 440fc9bf9656..5e88d1fcc615 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java @@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -82,6 +83,12 @@ PropertiesObservationFilter propertiesObservationFilter(ObservationProperties pr return new PropertiesObservationFilter(properties); } + @Bean + @ConditionalOnProperty(name = "management.observations.spring-security.enabled", havingValue = "false") + ObservationPredicate springSecurityObservationsDisabler() { + return (name, context) -> !name.startsWith("spring.security."); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MeterRegistry.class) @ConditionalOnMissingClass("io.micrometer.tracing.Tracer") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 9a15c30b96cf..14f14f7c924c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -2038,6 +2038,12 @@ "level": "error" } }, + { + "name": "management.observations.spring-security.enabled", + "description": "Whether to enable observations for Spring Security", + "type": "java.lang.Boolean", + "defaultValue": true + }, { "name": "management.otlp.tracing.compression", "defaultValue": "none" diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index 186adde6056a..fd7199af72aa 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java @@ -291,6 +291,28 @@ void autoConfiguresObservationHandlerWhenTracingIsActive() { }); } + @Test + void shouldNotDisableSpringSecurityObservationsByDefault() { + this.contextRunner.run((context) -> { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("spring.security.filterchains", observationRegistry).stop(); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThat(meterRegistry.get("spring.security.filterchains").timer().count()).isOne(); + }); + } + + @Test + void shouldDisableSpringSecurityObservationsIfPropertyIsSet() { + this.contextRunner.withPropertyValues("management.observations.spring-security.enabled=false") + .run((context) -> { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("spring.security.filterchains", observationRegistry).stop(); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThatThrownBy(() -> meterRegistry.get("spring.security.filterchains").timer()) + .isInstanceOf(MeterNotFoundException.class); + }); + } + @Configuration(proxyBeanMethods = false) static class ObservationPredicates { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc index f077a1409c23..10d8ec732207 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc @@ -37,4 +37,16 @@ Commons key-values are applied to all observations as low cardinality key-values The preceding example adds `region` and `stack` key-values to all observations with a value of `us-east-1` and `prod`, respectively. +[[actuator.observability.preventing-observations]] +=== Preventing Observations + +If you'd like to prevent some observations from being reported, you can register beans of type `ObservationPredicate`. +Observations are only reported if all the `ObservationPredicate` beans return `true` for that observation. + +include::code:MyObservationPredicate[] + +The preceding example will prevent all observations with a name starting with "denied.prefix.". + +TIP: If you want to prevent Spring Security from reporting observations, set the property configprop:management.observations.spring-security.enabled[] to `false`. + The next sections will provide more details about logging, metrics and traces. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java new file mode 100644 index 000000000000..17a5543dc34b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2023 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.docs.actuator.observability.preventingobservations; + +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationPredicate; + +import org.springframework.stereotype.Component; + +@Component +class MyObservationPredicate implements ObservationPredicate { + + @Override + public boolean test(String name, Context context) { + return !name.startsWith("denied.prefix."); + } + +} From 7b90fbb0b2ef73871ca776d9f999e9cdc1cb7e6d Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 13 Jun 2023 12:01:39 +0200 Subject: [PATCH 0005/1656] Add property to specify the order of ServerHttpObservationFilter The property is named 'management.observations.http.server.filter.order' Closes gh-35067 --- .../observation/ObservationProperties.java | 24 ++++++++++ .../OrderedServerHttpObservationFilter.java | 46 +++++++++++++++++++ .../WebFluxObservationAutoConfiguration.java | 10 ++-- ...FluxObservationAutoConfigurationTests.java | 10 ++++ 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java index 92f8e8b0fe2d..27c2473cc635 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java @@ -20,6 +20,7 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.Ordered; /** * {@link ConfigurationProperties @ConfigurationProperties} for configuring Micrometer @@ -96,10 +97,16 @@ public static class Server { private final ServerRequests requests = new ServerRequests(); + private final Filter filter = new Filter(); + public ServerRequests getRequests() { return this.requests; } + public Filter getFilter() { + return this.filter; + } + public static class ServerRequests { /** @@ -118,6 +125,23 @@ public void setName(String name) { } + public static class Filter { + + /** + * Order of the filter that creates the observations. + */ + private int order = Ordered.HIGHEST_PRECEDENCE + 1; + + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java new file mode 100644 index 000000000000..9d3146f1dbee --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.observation.web.reactive; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.boot.web.reactive.filter.OrderedWebFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; +import org.springframework.web.filter.reactive.ServerHttpObservationFilter; + +/** + * {@link ServerHttpObservationFilter} that also implements {@link Ordered}. + * + * @author Moritz Halbritter + */ +class OrderedServerHttpObservationFilter extends ServerHttpObservationFilter implements OrderedWebFilter { + + private final int order; + + OrderedServerHttpObservationFilter(ObservationRegistry observationRegistry, + ServerRequestObservationConvention observationConvention, int order) { + super(observationRegistry, observationConvention); + this.order = order; + } + + @Override + public int getOrder() { + return this.order; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java index a998e6b5d24b..83c67a60736e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java @@ -42,7 +42,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention; import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; @@ -55,6 +54,7 @@ * @author Brian Clozel * @author Jon Schneider * @author Dmytro Nosan + * @author Moritz Halbritter * @since 3.0.0 */ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, @@ -77,9 +77,8 @@ public WebFluxObservationAutoConfiguration(MetricsProperties metricsProperties, } @Bean - @ConditionalOnMissingBean - @Order(Ordered.HIGHEST_PRECEDENCE + 1) - public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry, + @ConditionalOnMissingBean(ServerHttpObservationFilter.class) + public OrderedServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry, ObjectProvider customConvention, ObjectProvider tagConfigurer, ObjectProvider contributorsProvider) { @@ -90,7 +89,8 @@ public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry List tagsContributors = contributorsProvider.orderedStream().toList(); ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name, tagsProvider, tagsContributors); - return new ServerHttpObservationFilter(registry, convention); + int order = this.observationProperties.getHttp().getServer().getFilter().getOrder(); + return new OrderedServerHttpObservationFilter(registry, convention, order); } private static ServerRequestObservationConvention createConvention( diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java index 88609e23f686..d8197198eb47 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java @@ -57,6 +57,7 @@ * @author Brian Clozel * @author Dmytro Nosan * @author Madhura Bhave + * @author Moritz Halbritter */ @ExtendWith(OutputCaptureExtension.class) @SuppressWarnings("removal") @@ -169,6 +170,15 @@ void shouldNotDenyNorLogIfMaxUrisIsNotReached(CapturedOutput output) { }); } + @Test + void shouldUsePropertyForServerHttpObservationFilterOrder() { + this.contextRunner.withPropertyValues("management.observations.http.server.filter.order=1000") + .run((context) -> { + OrderedServerHttpObservationFilter bean = context.getBean(OrderedServerHttpObservationFilter.class); + assertThat(bean.getOrder()).isEqualTo(1000); + }); + } + private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context) throws Exception { return getInitializedMeterRegistry(context, "/test0", "/test1", "/test2"); From f562ced79a0e854fe055266bfef02a02f1440045 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 9 Jun 2023 09:52:37 +0100 Subject: [PATCH 0006/1656] Add CI on JDK 21 --- ci/images/ci-image-jdk21/Dockerfile | 11 ++++ ci/images/get-jdk-url.sh | 3 + ci/pipeline.yml | 89 ++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 ci/images/ci-image-jdk21/Dockerfile diff --git a/ci/images/ci-image-jdk21/Dockerfile b/ci/images/ci-image-jdk21/Dockerfile new file mode 100644 index 000000000000..6636b2c19d24 --- /dev/null +++ b/ci/images/ci-image-jdk21/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:jammy-20230425 + +ADD setup.sh /setup.sh +ADD get-jdk-url.sh /get-jdk-url.sh +ADD get-docker-url.sh /get-docker-url.sh +ADD get-docker-compose-url.sh /get-docker-compose-url.sh +RUN ./setup.sh java17 java21 + +ENV JAVA_HOME /opt/openjdk +ENV PATH $JAVA_HOME/bin:$PATH +ADD docker-lib.sh /docker-lib.sh diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index ec2505e5b13d..6dc041e1d64e 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -8,6 +8,9 @@ case "$1" in java20) echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz" ;; + java21) + echo "https://download.java.net/java/early_access/jdk21/25/GPL/openjdk-21-ea+25_linux-x64_bin.tar.gz" + ;; *) echo $"Unknown java version" exit 1 diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 15109f6d2e39..14c4b2d87e11 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -175,6 +175,12 @@ resources: source: <<: *ci-registry-image-resource-source repository: ((docker-hub-organization))/spring-boot-ci-jdk20 +- name: ci-image-jdk21 + type: registry-image + icon: docker + source: + <<: *ci-registry-image-resource-source + repository: ((docker-hub-organization))/spring-boot-ci-jdk21 - name: paketo-builder-base-image type: registry-image icon: docker @@ -207,6 +213,14 @@ resources: access_token: ((github-ci-status-token)) branch: ((branch)) context: jdk20-build +- name: repo-status-jdk21-build + type: github-status-resource + icon: eye-check-outline + source: + repository: ((github-repo-name)) + access_token: ((github-ci-status-token)) + branch: ((branch)) + context: jdk21-build - name: slack-alert type: slack-notification icon: slack @@ -249,6 +263,13 @@ jobs: image: ci-image-jdk20 vars: ci-image-name: ci-image-jdk20 + - task: build-ci-image-jdk21 + privileged: true + file: git-repo/ci/tasks/build-ci-image.yml + output_mapping: + image: ci-image-jdk21 + vars: + ci-image-name: ci-image-jdk21 - in_parallel: - put: ci-image params: @@ -256,6 +277,9 @@ jobs: - put: ci-image-jdk20 params: image: ci-image-jdk20/image.tar + - put: ci-image-jdk21 + params: + image: ci-image-jdk21/image.tar - name: detect-jdk-updates plan: - get: git-repo @@ -366,6 +390,38 @@ jobs: - put: slack-alert params: <<: *slack-success-params +- name: jdk21-build + serial: true + public: true + plan: + - get: ci-image-jdk21 + - get: git-repo + trigger: true + - put: repo-status-jdk21-build + params: { state: "pending", commit: "git-repo" } + - do: + - task: build-project + image: ci-image-jdk21 + privileged: true + timeout: ((task-timeout)) + file: git-repo/ci/tasks/build-project.yml + params: + BRANCH: ((branch)) + TOOLCHAIN_JAVA_VERSION: 21 + <<: *gradle-enterprise-task-params + <<: *docker-hub-task-params + on_failure: + do: + - put: repo-status-jdk21-build + params: { state: "failure", commit: "git-repo" } + - put: slack-alert + params: + <<: *slack-fail-params + - put: repo-status-jdk21-build + params: { state: "success", commit: "git-repo" } + - put: slack-alert + params: + <<: *slack-success-params - name: windows-build serial: true plan: @@ -662,13 +718,42 @@ jobs: - put: slack-alert params: <<: *slack-success-params +- name: jdk21-run-system-tests + serial: true + public: true + plan: + - get: ci-image-jdk21 + - get: git-repo + - get: paketo-builder-base-image + trigger: true + - get: daily + trigger: true + - do: + - task: run-system-tests + image: ci-image-jdk21 + privileged: true + timeout: ((task-timeout)) + file: git-repo/ci/tasks/run-system-tests.yml + params: + BRANCH: ((branch)) + TOOLCHAIN_JAVA_VERSION: 21 + <<: *gradle-enterprise-task-params + <<: *docker-hub-task-params + on_failure: + do: + - put: slack-alert + params: + <<: *slack-fail-params + - put: slack-alert + params: + <<: *slack-success-params groups: - name: "builds" - jobs: ["build", "jdk20-build", "windows-build"] + jobs: ["build", "jdk20-build", "jdk21-build", "windows-build"] - name: "releases" jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release", "publish-gradle-plugin", "publish-to-sdkman", "update-homebrew-tap"] - name: "system-tests" - jobs: ["run-system-tests", "jdk20-run-system-tests"] + jobs: ["run-system-tests", "jdk20-run-system-tests", "jdk21-run-system-tests"] - name: "ci-images" jobs: ["build-ci-images", "detect-docker-updates", "detect-jdk-updates", "detect-ubuntu-image-updates"] From c73315b4a31da90ab60cacb0da46f82155aab7a7 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 13 Jun 2023 13:59:38 +0200 Subject: [PATCH 0007/1656] Add property to prevent observations starting with a prefix For example, setting management.observations.enable.denied.prefix=false will prevent all observations starting with 'denied.prefix' Closes gh-34802 --- .../ObservationAutoConfiguration.java | 11 +- .../observation/ObservationProperties.java | 15 +++ .../PropertiesObservationFilter.java | 51 --------- .../PropertiesObservationFilterPredicate.java | 83 +++++++++++++++ ...itional-spring-configuration-metadata.json | 6 -- .../ObservationAutoConfigurationTests.java | 18 ++-- ...ertiesObservationFilterPredicateTests.java | 100 ++++++++++++++++++ .../PropertiesObservationFilterTests.java | 62 ----------- .../docs/asciidoc/actuator/observability.adoc | 24 ++++- .../MyObservationPredicate.java | 2 +- 10 files changed, 230 insertions(+), 142 deletions(-) delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java index 5e88d1fcc615..a164afa63e84 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java @@ -40,7 +40,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -79,14 +78,8 @@ ObservationRegistry observationRegistry() { @Bean @Order(0) - PropertiesObservationFilter propertiesObservationFilter(ObservationProperties properties) { - return new PropertiesObservationFilter(properties); - } - - @Bean - @ConditionalOnProperty(name = "management.observations.spring-security.enabled", havingValue = "false") - ObservationPredicate springSecurityObservationsDisabler() { - return (name, context) -> !name.startsWith("spring.security."); + PropertiesObservationFilterPredicate propertiesObservationFilter(ObservationProperties properties) { + return new PropertiesObservationFilterPredicate(properties); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java index 27c2473cc635..a60bdb700762 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java @@ -27,6 +27,7 @@ * observations. * * @author Brian Clozel + * @author Moritz Halbritter * @since 3.0.0 */ @ConfigurationProperties("management.observations") @@ -39,6 +40,20 @@ public class ObservationProperties { */ private Map keyValues = new LinkedHashMap<>(); + /** + * Whether observations starting with the specified name should be enabled. The + * longest match wins, the key 'all' can also be used to configure all observations. + */ + private Map enable = new LinkedHashMap<>(); + + public Map getEnable() { + return this.enable; + } + + public void setEnable(Map enable) { + this.enable = enable; + } + public Http getHttp() { return this.http; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java deleted file mode 100644 index d1116aa4ddb5..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2012-2023 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.actuate.autoconfigure.observation; - -import java.util.Map.Entry; - -import io.micrometer.common.KeyValues; -import io.micrometer.observation.Observation.Context; -import io.micrometer.observation.ObservationFilter; - -/** - * {@link ObservationFilter} to apply settings from {@link ObservationProperties}. - * - * @author Moritz Halbritter - */ -class PropertiesObservationFilter implements ObservationFilter { - - private final ObservationFilter delegate; - - PropertiesObservationFilter(ObservationProperties properties) { - this.delegate = createDelegate(properties); - } - - @Override - public Context map(Context context) { - return this.delegate.map(context); - } - - private static ObservationFilter createDelegate(ObservationProperties properties) { - if (properties.getKeyValues().isEmpty()) { - return (context) -> context; - } - KeyValues keyValues = KeyValues.of(properties.getKeyValues().entrySet(), Entry::getKey, Entry::getValue); - return (context) -> context.addLowCardinalityKeyValues(keyValues); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java new file mode 100644 index 000000000000..1154668798af --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.observation; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Supplier; + +import io.micrometer.common.KeyValues; +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationFilter; +import io.micrometer.observation.ObservationPredicate; + +import org.springframework.util.StringUtils; + +/** + * {@link ObservationFilter} to apply settings from {@link ObservationProperties}. + * + * @author Moritz Halbritter + */ +class PropertiesObservationFilterPredicate implements ObservationFilter, ObservationPredicate { + + private final ObservationFilter commonKeyValuesFilter; + + private final ObservationProperties properties; + + PropertiesObservationFilterPredicate(ObservationProperties properties) { + this.properties = properties; + this.commonKeyValuesFilter = createCommonKeyValuesFilter(properties); + } + + @Override + public Context map(Context context) { + return this.commonKeyValuesFilter.map(context); + } + + @Override + public boolean test(String name, Context context) { + return lookupWithFallbackToAll(this.properties.getEnable(), name, true); + } + + private static T lookupWithFallbackToAll(Map values, String name, T defaultValue) { + if (values.isEmpty()) { + return defaultValue; + } + return doLookup(values, name, () -> values.getOrDefault("all", defaultValue)); + } + + private static T doLookup(Map values, String name, Supplier defaultValue) { + while (StringUtils.hasLength(name)) { + T result = values.get(name); + if (result != null) { + return result; + } + int lastDot = name.lastIndexOf('.'); + name = (lastDot != -1) ? name.substring(0, lastDot) : ""; + } + return defaultValue.get(); + } + + private static ObservationFilter createCommonKeyValuesFilter(ObservationProperties properties) { + if (properties.getKeyValues().isEmpty()) { + return (context) -> context; + } + KeyValues keyValues = KeyValues.of(properties.getKeyValues().entrySet(), Entry::getKey, Entry::getValue); + return (context) -> context.addLowCardinalityKeyValues(keyValues); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 14f14f7c924c..9a15c30b96cf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -2038,12 +2038,6 @@ "level": "error" } }, - { - "name": "management.observations.spring-security.enabled", - "description": "Whether to enable observations for Spring Security", - "type": "java.lang.Boolean", - "defaultValue": true - }, { "name": "management.otlp.tracing.compression", "defaultValue": "none" diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index fd7199af72aa..eb994e6896d1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java @@ -191,7 +191,8 @@ void autoConfiguresObservationFilters() { @Test void shouldSupplyPropertiesObservationFilterBean() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesObservationFilter.class)); + this.contextRunner + .run((context) -> assertThat(context).hasSingleBean(PropertiesObservationFilterPredicate.class)); } @Test @@ -303,14 +304,13 @@ void shouldNotDisableSpringSecurityObservationsByDefault() { @Test void shouldDisableSpringSecurityObservationsIfPropertyIsSet() { - this.contextRunner.withPropertyValues("management.observations.spring-security.enabled=false") - .run((context) -> { - ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); - Observation.start("spring.security.filterchains", observationRegistry).stop(); - MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); - assertThatThrownBy(() -> meterRegistry.get("spring.security.filterchains").timer()) - .isInstanceOf(MeterNotFoundException.class); - }); + this.contextRunner.withPropertyValues("management.observations.enable.spring.security=false").run((context) -> { + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("spring.security.filterchains", observationRegistry).stop(); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + assertThatThrownBy(() -> meterRegistry.get("spring.security.filterchains").timer()) + .isInstanceOf(MeterNotFoundException.class); + }); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java new file mode 100644 index 000000000000..4afd4f601e67 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.observation; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import io.micrometer.observation.Observation.Context; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PropertiesObservationFilterPredicate}. + * + * @author Moritz Halbritter + */ +class PropertiesObservationFilterPredicateTests { + + @Test + void shouldDoNothingIfKeyValuesAreEmpty() { + PropertiesObservationFilterPredicate filter = createFilter(); + Context mapped = mapContext(filter, "a", "alpha"); + assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha")); + } + + @Test + void shouldAddKeyValues() { + PropertiesObservationFilterPredicate filter = createFilter("b", "beta"); + Context mapped = mapContext(filter, "a", "alpha"); + assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"), + KeyValue.of("b", "beta")); + } + + @Test + void shouldFilter() { + PropertiesObservationFilterPredicate predicate = createPredicate("spring.security"); + Context context = new Context(); + assertThat(predicate.test("spring.security.filterchains", context)).isFalse(); + assertThat(predicate.test("spring.security", context)).isFalse(); + assertThat(predicate.test("spring.data", context)).isTrue(); + assertThat(predicate.test("spring", context)).isTrue(); + } + + @Test + void filterShouldFallbackToAll() { + PropertiesObservationFilterPredicate predicate = createPredicate("all"); + Context context = new Context(); + assertThat(predicate.test("spring.security.filterchains", context)).isFalse(); + assertThat(predicate.test("spring.security", context)).isFalse(); + assertThat(predicate.test("spring.data", context)).isFalse(); + assertThat(predicate.test("spring", context)).isFalse(); + } + + @Test + void shouldNotFilterIfDisabledNamesIsEmpty() { + PropertiesObservationFilterPredicate predicate = createPredicate(); + Context context = new Context(); + assertThat(predicate.test("spring.security.filterchains", context)).isTrue(); + assertThat(predicate.test("spring.security", context)).isTrue(); + assertThat(predicate.test("spring.data", context)).isTrue(); + assertThat(predicate.test("spring", context)).isTrue(); + } + + private static Context mapContext(PropertiesObservationFilterPredicate filter, String... initialKeyValues) { + Context context = new Context(); + context.addLowCardinalityKeyValues(KeyValues.of(initialKeyValues)); + return filter.map(context); + } + + private static PropertiesObservationFilterPredicate createFilter(String... keyValues) { + ObservationProperties properties = new ObservationProperties(); + for (int i = 0; i < keyValues.length; i += 2) { + properties.getKeyValues().put(keyValues[i], keyValues[i + 1]); + } + return new PropertiesObservationFilterPredicate(properties); + } + + private static PropertiesObservationFilterPredicate createPredicate(String... disabledNames) { + ObservationProperties properties = new ObservationProperties(); + for (String name : disabledNames) { + properties.getEnable().put(name, false); + } + return new PropertiesObservationFilterPredicate(properties); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java deleted file mode 100644 index b352f9695d1a..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2012-2023 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.actuate.autoconfigure.observation; - -import io.micrometer.common.KeyValue; -import io.micrometer.common.KeyValues; -import io.micrometer.observation.Observation.Context; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link PropertiesObservationFilter}. - * - * @author Moritz Halbritter - */ -class PropertiesObservationFilterTests { - - @Test - void shouldDoNothingIfKeyValuesAreEmpty() { - PropertiesObservationFilter filter = createFilter(); - Context mapped = mapContext(filter, "a", "alpha"); - assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha")); - } - - @Test - void shouldAddKeyValues() { - PropertiesObservationFilter filter = createFilter("b", "beta"); - Context mapped = mapContext(filter, "a", "alpha"); - assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"), - KeyValue.of("b", "beta")); - } - - private static Context mapContext(PropertiesObservationFilter filter, String... initialKeyValues) { - Context context = new Context(); - context.addLowCardinalityKeyValues(KeyValues.of(initialKeyValues)); - return filter.map(context); - } - - private static PropertiesObservationFilter createFilter(String... keyValues) { - ObservationProperties properties = new ObservationProperties(); - for (int i = 0; i < keyValues.length; i += 2) { - properties.getKeyValues().put(keyValues[i], keyValues[i + 1]); - } - return new PropertiesObservationFilter(properties); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc index 10d8ec732207..e8c900d161e9 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc @@ -40,13 +40,29 @@ The preceding example adds `region` and `stack` key-values to all observations w [[actuator.observability.preventing-observations]] === Preventing Observations -If you'd like to prevent some observations from being reported, you can register beans of type `ObservationPredicate`. +If you'd like to prevent some observations from being reported, you can use the configprop:management.observations.enable[] properties: + +[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +---- + management: + observations: + enable: + denied: + prefix: false + another: + denied: + prefix: false +---- + +The preceding example will prevent all observations with a name starting with `denied.prefix` or `another.denied.prefix`. + +TIP: If you want to prevent Spring Security from reporting observations, set the property configprop:management.observations.enable.spring.security[] to `false`. + +If you need greater control over the prevention of observations, you can register beans of type `ObservationPredicate`. Observations are only reported if all the `ObservationPredicate` beans return `true` for that observation. include::code:MyObservationPredicate[] -The preceding example will prevent all observations with a name starting with "denied.prefix.". - -TIP: If you want to prevent Spring Security from reporting observations, set the property configprop:management.observations.spring-security.enabled[] to `false`. +The preceding example will prevent all observations whose name contains "denied". The next sections will provide more details about logging, metrics and traces. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java index 17a5543dc34b..f065c8e2ca83 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java @@ -26,7 +26,7 @@ class MyObservationPredicate implements ObservationPredicate { @Override public boolean test(String name, Context context) { - return !name.startsWith("denied.prefix."); + return !name.contains("denied"); } } From fb64f6744e9c0026e5892d19a3006e9c14b75546 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 6 Jun 2023 09:38:50 +0200 Subject: [PATCH 0008/1656] Add 21 to JavaVersion See gh-35892 --- .../java/org/springframework/boot/system/JavaVersion.java | 8 +++++++- .../org/springframework/boot/system/JavaVersionTests.java | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/JavaVersion.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/JavaVersion.java index feda02e0099b..3ebcfbf10fc8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/JavaVersion.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/JavaVersion.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.SortedSet; import java.util.concurrent.Future; import org.springframework.util.ClassUtils; @@ -53,7 +54,12 @@ public enum JavaVersion { /** * Java 20. */ - TWENTY("20", Class.class, "accessFlags"); + TWENTY("20", Class.class, "accessFlags"), + + /** + * Java 21. + */ + TWENTY_ONE("21", SortedSet.class, "getFirst"); private final String name; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/JavaVersionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/JavaVersionTests.java index 0b0787b9feab..d87c1230f700 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/JavaVersionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/JavaVersionTests.java @@ -103,4 +103,10 @@ void currentJavaVersionTwenty() { assertThat(JavaVersion.getJavaVersion()).isEqualTo(JavaVersion.TWENTY); } + @Test + @EnabledOnJre(JRE.JAVA_21) + void currentJavaVersionTwentyOne() { + assertThat(JavaVersion.getJavaVersion()).isEqualTo(JavaVersion.TWENTY_ONE); + } + } From 6e604ad65c6ab0e0da90eae3ae9994c78b031768 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 6 Jun 2023 09:39:16 +0200 Subject: [PATCH 0009/1656] Implement @ConditionalOnVirtualThreads Closes gh-35892 --- .../DocumentConfigurationProperties.java | 1 + .../ConditionalOnVirtualThreads.java | 42 +++++++++++ ...itional-spring-configuration-metadata.json | 6 ++ .../ConditionalOnVirtualThreadsTests.java | 70 +++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreads.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreadsTests.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java index d7c8710e2cb4..8019eb3a27bd 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java @@ -106,6 +106,7 @@ private void corePrefixes(Config config) { config.accept("spring.reactor"); config.accept("spring.ssl"); config.accept("spring.task"); + config.accept("spring.threads"); config.accept("spring.mandatory-file-encoding"); config.accept("info"); config.accept("spring.output.ansi.enabled"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreads.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreads.java new file mode 100644 index 000000000000..cbc80beaa653 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreads.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.system.JavaVersion; +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional @Conditional} that only matches when virtual threads are available + * and enabled. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ConditionalOnJava(JavaVersion.TWENTY_ONE) +@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true") +public @interface ConditionalOnVirtualThreads { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index d4c480df6479..dae3a6e6cdf0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -2825,6 +2825,12 @@ "name": "spring.sql.init.mode", "defaultValue": "embedded" }, + { + "name": "spring.threads.virtual.enabled", + "type": "java.lang.Boolean", + "description": "Whether to use virtual threads.", + "defaultValue": false + }, { "name": "spring.thymeleaf.prefix", "defaultValue": "classpath:/templates/" diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreadsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreadsTests.java new file mode 100644 index 000000000000..dd05fa5b94ac --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnVirtualThreadsTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnVirtualThreads @ConditionalOnVirtualThreads}. + * + * @author Moritz Halbritter + */ +class ConditionalOnVirtualThreadsTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(BasicConfiguration.class); + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void isDisabledOnJdkBelow21EvenIfPropertyIsSet() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("someBean")); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void isDisabledOnJdk21IfPropertyIsNotSet() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean("someBean")); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void isEnabledOnJdk21IfPropertyIsSet() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> assertThat(context).hasBean("someBean")); + } + + @Configuration(proxyBeanMethods = false) + static class BasicConfiguration { + + @Bean + @ConditionalOnVirtualThreads + String someBean() { + return "someBean"; + } + + } + +} From f81787e65d9e3fd57c8b5da092645d59b95db279 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 14 Jun 2023 16:35:33 +0200 Subject: [PATCH 0010/1656] Enable virtual threads on Tomcat Closes gh-35704 --- ...veManagementChildContextConfiguration.java | 4 +- ...etManagementChildContextConfiguration.java | 6 +- ...verFactoryCustomizerAutoConfiguration.java | 7 ++ ...tualThreadsWebServerFactoryCustomizer.java | 47 ++++++++++++++ .../TomcatWebServerFactoryCustomizer.java | 4 +- ...hreadsWebServerFactoryCustomizerTests.java | 64 +++++++++++++++++++ 6 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java index 657f9dbda20d..faa2522016ea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer; +import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryCustomizer; @@ -76,7 +77,8 @@ static class ReactiveManagementWebServerFactoryCustomizer ReactiveManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { super(beanFactory, ReactiveWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, - TomcatReactiveWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, + TomcatReactiveWebServerFactoryCustomizer.class, + TomcatVirtualThreadsWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class, NettyWebServerFactoryCustomizer.class); } 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 44de3225efc5..bc0b6c42c3b6 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 @@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; +import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer; @@ -122,8 +123,9 @@ static class ServletManagementWebServerFactoryCustomizer ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { super(beanFactory, ServletWebServerFactoryCustomizer.class, TomcatServletWebServerFactoryCustomizer.class, - TomcatWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, - UndertowServletWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class); + TomcatWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class, + JettyWebServerFactoryCustomizer.class, UndertowServletWebServerFactoryCustomizer.class, + UndertowWebServerFactoryCustomizer.class); } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java index eca465353db6..300594855e37 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment; +import org.springframework.boot.autoconfigure.condition.ConditionalOnVirtualThreads; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -62,6 +63,12 @@ public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environ return new TomcatWebServerFactoryCustomizer(environment, serverProperties); } + @Bean + @ConditionalOnVirtualThreads + TomcatVirtualThreadsWebServerFactoryCustomizer tomcatVirtualThreadsProtocolHandlerCustomizer() { + return new TomcatVirtualThreadsWebServerFactoryCustomizer(); + } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java new file mode 100644 index 000000000000..9af929ffaa82 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import org.apache.coyote.ProtocolHandler; +import org.apache.tomcat.util.threads.VirtualThreadExecutor; + +import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.core.Ordered; + +/** + * Activates {@link VirtualThreadExecutor} on {@link ProtocolHandler Tomcat's protocol + * handler}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public class TomcatVirtualThreadsWebServerFactoryCustomizer + implements WebServerFactoryCustomizer, Ordered { + + @Override + public void customize(ConfigurableTomcatWebServerFactory factory) { + factory.addProtocolHandlerCustomizers( + (protocolHandler) -> protocolHandler.setExecutor(new VirtualThreadExecutor("tomcat-handler-"))); + } + + @Override + public int getOrder() { + return TomcatWebServerFactoryCustomizer.order + 1; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java index e47b7cccfe59..050edc743384 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java @@ -67,6 +67,8 @@ public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer, Ordered { + static final int order = 0; + private final Environment environment; private final ServerProperties serverProperties; @@ -78,7 +80,7 @@ public TomcatWebServerFactoryCustomizer(Environment environment, ServerPropertie @Override public int getOrder() { - return 0; + return order; } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java new file mode 100644 index 000000000000..5fcf72d1f937 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import java.util.function.Consumer; + +import org.apache.tomcat.util.threads.VirtualThreadExecutor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TomcatVirtualThreadsWebServerFactoryCustomizer}. + * + * @author Moritz Halbritter + */ +class TomcatVirtualThreadsWebServerFactoryCustomizerTests { + + private final TomcatVirtualThreadsWebServerFactoryCustomizer customizer = new TomcatVirtualThreadsWebServerFactoryCustomizer(); + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldSetVirtualThreadExecutor() { + withWebServer((webServer) -> assertThat(webServer.getTomcat().getConnector().getProtocolHandler().getExecutor()) + .isInstanceOf(VirtualThreadExecutor.class)); + } + + private TomcatWebServer getWebServer() { + TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0); + this.customizer.customize(factory); + return (TomcatWebServer) factory.getWebServer(); + } + + private void withWebServer(Consumer callback) { + TomcatWebServer webServer = getWebServer(); + webServer.start(); + try { + callback.accept(webServer); + } + finally { + webServer.stop(); + } + } + +} From 3e4a9f5204ee2fe989583b17b77d8dab8558282a Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 6 Jun 2023 09:39:33 +0200 Subject: [PATCH 0011/1656] Add property to limit maximum connections for Jetty Closes gh-35899 --- .../boot/autoconfigure/web/ServerProperties.java | 14 ++++++++++++++ .../embedded/JettyWebServerFactoryCustomizer.java | 1 + .../jetty/ConfigurableJettyWebServerFactory.java | 8 ++++++++ .../jetty/JettyReactiveWebServerFactory.java | 12 ++++++++++++ .../jetty/JettyServletWebServerFactory.java | 12 ++++++++++++ .../jetty/JettyReactiveWebServerFactoryTests.java | 13 +++++++++++++ .../jetty/JettyServletWebServerFactoryTests.java | 13 +++++++++++++ 7 files changed, 73 insertions(+) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index a4e5bcf2d1a8..681bd7d8384a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -1123,6 +1123,12 @@ public static class Jetty { */ private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8); + /** + * Maximum number of connections that the server accepts and processes at any + * given time. + */ + private int maxConnections = -1; + public Accesslog getAccesslog() { return this.accesslog; } @@ -1155,6 +1161,14 @@ public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) { this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize; } + public int getMaxConnections() { + return this.maxConnections; + } + + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + /** * Jetty access log properties. */ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java index 2248f1a72039..2c9d015cae91 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java @@ -81,6 +81,7 @@ public void customize(ConfigurableJettyWebServerFactory factory) { ServerProperties.Jetty.Threads threadProperties = properties.getThreads(); factory.setThreadPool(determineThreadPool(properties.getThreads())); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(properties::getMaxConnections).to(factory::setMaxConnections); map.from(threadProperties::getAcceptors).to(factory::setAcceptors); map.from(threadProperties::getSelectors).to(factory::setSelectors); map.from(this.serverProperties::getMaxHttpRequestHeaderSize) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java index 5f8a927b403a..65cae98947d0 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java @@ -25,6 +25,7 @@ * {@link ConfigurableWebServerFactory} for Jetty-specific features. * * @author Brian Clozel + * @author Moritz Halbritter * @since 2.0.0 * @see JettyServletWebServerFactory * @see JettyReactiveWebServerFactory @@ -63,4 +64,11 @@ public interface ConfigurableJettyWebServerFactory extends ConfigurableWebServer */ void addServerCustomizers(JettyServerCustomizer... customizers); + /** + * Sets the maximum number of concurrent connections. + * @param maxConnections the maximum number of concurrent connections + * @since 3.2.0 + */ + void setMaxConnections(int maxConnections); + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java index 3e8049f4d739..3102dd4aa3d4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -55,6 +56,7 @@ * {@link ReactiveWebServerFactory} that can be used to create {@link JettyWebServer}s. * * @author Brian Clozel + * @author Moritz Halbritter * @since 2.0.0 */ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory @@ -80,6 +82,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact private ThreadPool threadPool; + private int maxConnections = -1; + /** * Create a new {@link JettyServletWebServerFactory} instance. */ @@ -118,6 +122,11 @@ public void addServerCustomizers(JettyServerCustomizer... customizers) { this.jettyServerCustomizers.addAll(Arrays.asList(customizers)); } + @Override + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + /** * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server} * before it is started. Calling this method will replace any existing customizers. @@ -180,6 +189,9 @@ protected Server createJettyServer(JettyHttpHandlerAdapter servlet) { contextHandler.addServlet(servletHolder, "/"); server.setHandler(addHandlerWrappers(contextHandler)); JettyReactiveWebServerFactory.logger.info("Server initialized with port: " + port); + if (this.maxConnections > -1) { + server.addBean(new ConnectionLimit(this.maxConnections, server)); + } if (Ssl.isEnabled(getSsl())) { customizeSsl(server, address); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java index e060a372f07c..4d93f13367f5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; @@ -101,6 +102,7 @@ * @author Eddú Meléndez * @author Venil Noronha * @author Henri Kerola + * @author Moritz Halbritter * @since 2.0.0 * @see #setPort(int) * @see #setConfigurations(Collection) @@ -129,6 +131,8 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor private ThreadPool threadPool; + private int maxConnections = -1; + /** * Create a new {@link JettyServletWebServerFactory} instance. */ @@ -163,6 +167,9 @@ public WebServer getWebServer(ServletContextInitializer... initializers) { configureWebAppContext(context, initializers); server.setHandler(addHandlerWrappers(context)); this.logger.info("Server initialized with port: " + port); + if (this.maxConnections > -1) { + server.addBean(new ConnectionLimit(this.maxConnections, server)); + } if (Ssl.isEnabled(getSsl())) { customizeSsl(server, address); } @@ -458,6 +465,11 @@ public void setSelectors(int selectors) { this.selectors = selectors; } + @Override + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + /** * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server} * before it is started. Calling this method will replace any existing customizers. diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java index 09ecc2c3cef7..cf42ba29d7a4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java @@ -22,6 +22,7 @@ import java.util.Arrays; import org.awaitility.Awaitility; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -47,6 +48,7 @@ * * @author Brian Clozel * @author Madhura Bhave + * @author Moritz Halbritter */ @Servlet5ClassPathOverrides class JettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests { @@ -148,4 +150,15 @@ void whenServerIsShuttingDownGracefullyThenNewConnectionsCannotBeMade() { this.webServer.stop(); } + @Test + void shouldApplyMaxConnections() { + JettyReactiveWebServerFactory factory = getFactory(); + factory.setMaxConnections(1); + this.webServer = factory.getWebServer(new EchoHandler()); + Server server = ((JettyWebServer) this.webServer).getServer(); + ConnectionLimit connectionLimit = server.getBean(ConnectionLimit.class); + assertThat(connectionLimit).isNotNull(); + assertThat(connectionLimit.getMaxConnections()).isOne(); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java index 63fe24022315..f037202ca4e2 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java @@ -40,6 +40,7 @@ import org.apache.hc.core5.http.HttpResponse; import org.apache.jasper.servlet.JspServlet; import org.awaitility.Awaitility; +import org.eclipse.jetty.server.ConnectionLimit; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; @@ -86,6 +87,7 @@ * @author Dave Syer * @author Andy Wilkinson * @author Henri Kerola + * @author Moritz Halbritter */ @Servlet5ClassPathOverrides class JettyServletWebServerFactoryTests extends AbstractServletWebServerFactoryTests { @@ -518,6 +520,17 @@ public void configure(WebAppContext context) throws Exception { assertThat(context.getErrorHandler()).isInstanceOf(CustomErrorHandler.class); } + @Test + void shouldApplyMaxConnections() { + JettyServletWebServerFactory factory = getFactory(); + factory.setMaxConnections(1); + this.webServer = factory.getWebServer(); + Server server = ((JettyWebServer) this.webServer).getServer(); + ConnectionLimit connectionLimit = server.getBean(ConnectionLimit.class); + assertThat(connectionLimit).isNotNull(); + assertThat(connectionLimit.getMaxConnections()).isOne(); + } + private WebAppContext findWebAppContext(JettyWebServer webServer) { return findWebAppContext(webServer.getServer().getHandler()); } From 23979e6ccfefef35fafa95e09d43af275e510e3a Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 15 Jun 2023 10:13:19 +0200 Subject: [PATCH 0012/1656] Enable LoaderIntegrationTests on Java 21 --- .../boot/loader/LoaderIntegrationTests.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java index c01b5f40c343..7b6ce6e7d3d4 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/src/intTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java @@ -44,6 +44,7 @@ * Integration tests loader that supports fat jars. * * @author Phillip Webb + * @author Moritz Halbritter */ @DisabledIfDockerUnavailable @DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", @@ -85,6 +86,7 @@ static Stream javaRuntimes() { javaRuntimes.add(JavaRuntime.openJdk(JavaVersion.SEVENTEEN)); javaRuntimes.add(JavaRuntime.openJdk(JavaVersion.TWENTY)); javaRuntimes.add(JavaRuntime.oracleJdk17()); + javaRuntimes.add(JavaRuntime.openJdkEarlyAccess(JavaVersion.TWENTY_ONE)); return javaRuntimes.stream().filter(JavaRuntime::isCompatible); } @@ -115,6 +117,13 @@ public String toString() { return this.name; } + static JavaRuntime openJdkEarlyAccess(JavaVersion version) { + String imageVersion = version.toString(); + DockerImageName image = DockerImageName.parse("openjdk:%s-ea-jdk".formatted(imageVersion)); + return new JavaRuntime("OpenJDK Early Access " + imageVersion, version, + () -> new GenericContainer<>(image)); + } + static JavaRuntime openJdk(JavaVersion version) { String imageVersion = version.toString(); DockerImageName image = DockerImageName.parse("bellsoft/liberica-openjdk-debian:" + imageVersion); From 140c37ceba3db80397c100dd40fac489d0774149 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 6 Jun 2023 09:39:33 +0200 Subject: [PATCH 0013/1656] Enable virtual threads on Jetty Closes gh-35703 --- ...veManagementChildContextConfiguration.java | 4 +- ...etManagementChildContextConfiguration.java | 5 +- ...verFactoryCustomizerAutoConfiguration.java | 7 +++ .../web/embedded/JettyThreadPool.java | 60 +++++++++++++++++++ ...tualThreadsWebServerFactoryCustomizer.java | 56 +++++++++++++++++ .../JettyWebServerFactoryCustomizer.java | 32 ++-------- ...hreadsWebServerFactoryCustomizerTests.java | 54 +++++++++++++++++ 7 files changed, 187 insertions(+), 31 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizerTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java index faa2522016ea..c0c618347eb5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.embedded.JettyVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; @@ -79,7 +80,8 @@ static class ReactiveManagementWebServerFactoryCustomizer super(beanFactory, ReactiveWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, TomcatReactiveWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, - UndertowWebServerFactoryCustomizer.class, NettyWebServerFactoryCustomizer.class); + JettyVirtualThreadsWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class, + NettyWebServerFactoryCustomizer.class); } } 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 bc0b6c42c3b6..71beda39b6ba 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 @@ -39,6 +39,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.embedded.JettyVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; @@ -124,8 +125,8 @@ static class ServletManagementWebServerFactoryCustomizer ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { super(beanFactory, ServletWebServerFactoryCustomizer.class, TomcatServletWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class, - JettyWebServerFactoryCustomizer.class, UndertowServletWebServerFactoryCustomizer.class, - UndertowWebServerFactoryCustomizer.class); + JettyWebServerFactoryCustomizer.class, JettyVirtualThreadsWebServerFactoryCustomizer.class, + UndertowServletWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class); } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java index 300594855e37..846806696738 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java @@ -84,6 +84,13 @@ public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environme return new JettyWebServerFactoryCustomizer(environment, serverProperties); } + @Bean + @ConditionalOnVirtualThreads + JettyVirtualThreadsWebServerFactoryCustomizer jettyVirtualThreadsWebServerFactoryCustomizer( + ServerProperties serverProperties) { + return new JettyVirtualThreadsWebServerFactoryCustomizer(serverProperties); + } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java new file mode 100644 index 000000000000..56d01c91a232 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; + +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ThreadPool; + +import org.springframework.boot.autoconfigure.web.ServerProperties; + +/** + * Creates a {@link ThreadPool} for Jetty, applying the + * {@link ServerProperties.Jetty.Threads} properties. + * + * @author Moritz Halbritter + */ +final class JettyThreadPool { + + private JettyThreadPool() { + } + + static QueuedThreadPool create(ServerProperties.Jetty.Threads properties) { + BlockingQueue queue = determineBlockingQueue(properties.getMaxQueueCapacity()); + int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200; + int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8; + int threadIdleTimeout = (properties.getIdleTimeout() != null) ? (int) properties.getIdleTimeout().toMillis() + : 60000; + return new QueuedThreadPool(maxThreadCount, minThreadCount, threadIdleTimeout, queue); + } + + private static BlockingQueue determineBlockingQueue(Integer maxQueueCapacity) { + if (maxQueueCapacity == null) { + return null; + } + if (maxQueueCapacity == 0) { + return new SynchronousQueue<>(); + } + else { + return new BlockingArrayQueue<>(maxQueueCapacity); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizer.java new file mode 100644 index 000000000000..05720b3c82dc --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizer.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import org.eclipse.jetty.util.VirtualThreads; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +/** + * Activates virtual threads on the {@link ConfigurableJettyWebServerFactory}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public class JettyVirtualThreadsWebServerFactoryCustomizer + implements WebServerFactoryCustomizer, Ordered { + + private final ServerProperties serverProperties; + + public JettyVirtualThreadsWebServerFactoryCustomizer(ServerProperties serverProperties) { + this.serverProperties = serverProperties; + } + + @Override + public void customize(ConfigurableJettyWebServerFactory factory) { + Assert.state(VirtualThreads.areSupported(), "Virtual threads are not supported"); + QueuedThreadPool threadPool = JettyThreadPool.create(this.serverProperties.getJetty().getThreads()); + threadPool.setVirtualThreadsExecutor(VirtualThreads.getDefaultVirtualThreadsExecutor()); + factory.setThreadPool(threadPool); + } + + @Override + public int getOrder() { + return JettyWebServerFactoryCustomizer.ORDER + 1; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java index 2c9d015cae91..e53118c5d9af 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java @@ -18,8 +18,6 @@ import java.time.Duration; import java.util.Arrays; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.SynchronousQueue; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; @@ -31,9 +29,6 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.util.BlockingArrayQueue; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ThreadPool; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.cloud.CloudPlatform; @@ -60,6 +55,8 @@ public class JettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer, Ordered { + static final int ORDER = 0; + private final Environment environment; private final ServerProperties serverProperties; @@ -71,7 +68,7 @@ public JettyWebServerFactoryCustomizer(Environment environment, ServerProperties @Override public int getOrder() { - return 0; + return ORDER; } @Override @@ -79,7 +76,7 @@ public void customize(ConfigurableJettyWebServerFactory factory) { ServerProperties.Jetty properties = this.serverProperties.getJetty(); factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders()); ServerProperties.Jetty.Threads threadProperties = properties.getThreads(); - factory.setThreadPool(determineThreadPool(properties.getThreads())); + factory.setThreadPool(JettyThreadPool.create(properties.getThreads())); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(properties::getMaxConnections).to(factory::setMaxConnections); map.from(threadProperties::getAcceptors).to(factory::setAcceptors); @@ -151,27 +148,6 @@ else if (handler instanceof HandlerCollection collection) { }); } - private ThreadPool determineThreadPool(ServerProperties.Jetty.Threads properties) { - BlockingQueue queue = determineBlockingQueue(properties.getMaxQueueCapacity()); - int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200; - int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8; - int threadIdleTimeout = (properties.getIdleTimeout() != null) ? (int) properties.getIdleTimeout().toMillis() - : 60000; - return new QueuedThreadPool(maxThreadCount, minThreadCount, threadIdleTimeout, queue); - } - - private BlockingQueue determineBlockingQueue(Integer maxQueueCapacity) { - if (maxQueueCapacity == null) { - return null; - } - if (maxQueueCapacity == 0) { - return new SynchronousQueue<>(); - } - else { - return new BlockingArrayQueue<>(maxQueueCapacity); - } - } - private void customizeAccessLog(ConfigurableJettyWebServerFactory factory, ServerProperties.Jetty.Accesslog properties) { factory.addServerCustomizers((server) -> { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizerTests.java new file mode 100644 index 000000000000..6f7fb09dd7f3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyVirtualThreadsWebServerFactoryCustomizerTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.embedded; + +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.assertArg; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JettyVirtualThreadsWebServerFactoryCustomizer}. + * + * @author Moritz Halbritter + */ +class JettyVirtualThreadsWebServerFactoryCustomizerTests { + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldConfigureVirtualThreads() { + ServerProperties properties = new ServerProperties(); + JettyVirtualThreadsWebServerFactoryCustomizer customizer = new JettyVirtualThreadsWebServerFactoryCustomizer( + properties); + ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class); + customizer.customize(factory); + then(factory).should().setThreadPool(assertArg((threadPool) -> { + assertThat(threadPool).isInstanceOf(QueuedThreadPool.class); + QueuedThreadPool queuedThreadPool = (QueuedThreadPool) threadPool; + assertThat(queuedThreadPool.getVirtualThreadsExecutor()).isNotNull(); + })); + } + +} From efcc65bc5bb2870868cb43e540252cd87fa42a4a Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 15 Jun 2023 11:45:05 +0200 Subject: [PATCH 0014/1656] Apply filter order to ServerHttpObservationFilter Closes gh-35067 --- .../servlet/WebMvcObservationAutoConfiguration.java | 4 ++-- .../WebMvcObservationAutoConfigurationTests.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java index 5a70728ac125..802b8a3e969f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java @@ -44,7 +44,6 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.server.observation.DefaultServerRequestObservationConvention; import org.springframework.http.server.observation.ServerRequestObservationConvention; @@ -58,6 +57,7 @@ * @author Brian Clozel * @author Jon Schneider * @author Dmytro Nosan + * @author Moritz Halbritter * @since 3.0.0 */ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, @@ -90,7 +90,7 @@ public FilterRegistrationBean webMvcObservationFilt customTagsProvider.getIfAvailable(), contributorsProvider.orderedStream().toList()); ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention); FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); - registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); + registration.setOrder(this.observationProperties.getHttp().getServer().getFilter().getOrder()); registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC); return registration; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java index a8fe9bf9c9f1..7328d40b2b53 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java @@ -64,6 +64,7 @@ * @author Tadaya Tsuyukubo * @author Madhura Bhave * @author Chanhyeong LEE + * @author Moritz Halbritter */ @ExtendWith(OutputCaptureExtension.class) @SuppressWarnings("removal") @@ -117,6 +118,15 @@ void filterRegistrationHasExpectedDispatcherTypesAndOrder() { }); } + @Test + void filterRegistrationOrderCanBeOverridden() { + this.contextRunner.withPropertyValues("management.observations.http.server.filter.order=1000") + .run((context) -> { + FilterRegistrationBean registration = context.getBean(FilterRegistrationBean.class); + assertThat(registration.getOrder()).isEqualTo(1000); + }); + } + @Test void filterRegistrationBacksOffWithAnotherServerHttpObservationFilterRegistration() { this.contextRunner.withUserConfiguration(TestServerHttpObservationFilterRegistrationConfiguration.class) From 6e86f5c4444fdce9c4446ae41f4c883ba6d29ea4 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 15 Jun 2023 15:01:13 +0200 Subject: [PATCH 0015/1656] Register uncategorized ObservationHandlers after categorized ones Closes gh-34399 --- .../ObservationHandlerGrouping.java | 8 +- .../ObservationAutoConfigurationTests.java | 29 ++-- .../ObservationHandlerGroupingTests.java | 126 ++++++++++++++++++ 3 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGroupingTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java index 163186947e8a..8312bc986c3f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGrouping.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.observation; +import java.util.ArrayList; import java.util.List; import io.micrometer.observation.ObservationHandler; @@ -30,6 +31,7 @@ * Groups {@link ObservationHandler ObservationHandlers} by type. * * @author Andy Wilkinson + * @author Moritz Halbritter */ @SuppressWarnings("rawtypes") class ObservationHandlerGrouping { @@ -46,13 +48,14 @@ class ObservationHandlerGrouping { void apply(List> handlers, ObservationConfig config) { MultiValueMap, ObservationHandler> groupings = new LinkedMultiValueMap<>(); + List> handlersWithoutCategory = new ArrayList<>(); for (ObservationHandler handler : handlers) { Class category = findCategory(handler); if (category != null) { groupings.add(category, handler); } else { - config.observationHandler(handler); + handlersWithoutCategory.add(handler); } } for (Class category : this.categories) { @@ -61,6 +64,9 @@ void apply(List> handlers, ObservationConfig config) { config.observationHandler(new FirstMatchingCompositeObservationHandler(handlerGroup)); } } + for (ObservationHandler observationHandler : handlersWithoutCategory) { + config.observationHandler(observationHandler); + } } private Class findCategory(ObservationHandler handler) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index eb994e6896d1..8060d268908d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java @@ -223,14 +223,13 @@ void autoConfiguresObservationHandlers() { Observation.start("test-observation", observationRegistry).stop(); assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class); assertThat(handlers).hasSize(2); - // Regular handlers are registered first - assertThat(handlers.get(0)).isInstanceOf(CustomObservationHandler.class); // Multiple MeterObservationHandler are wrapped in - // FirstMatchingCompositeObservationHandler, which calls only the first - // one - assertThat(handlers.get(1)).isInstanceOf(CustomMeterObservationHandler.class); - assertThat(((CustomMeterObservationHandler) handlers.get(1)).getName()) + // FirstMatchingCompositeObservationHandler, which calls only the first one + assertThat(handlers.get(0)).isInstanceOf(CustomMeterObservationHandler.class); + assertThat(((CustomMeterObservationHandler) handlers.get(0)).getName()) .isEqualTo("customMeterObservationHandler1"); + // Regular handlers are registered last + assertThat(handlers.get(1)).isInstanceOf(CustomObservationHandler.class); assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class); assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class); }); @@ -273,20 +272,18 @@ void autoConfiguresObservationHandlerWhenTracingIsActive() { List> handlers = context.getBean(CalledHandlers.class).getCalledHandlers(); Observation.start("test-observation", observationRegistry).stop(); assertThat(handlers).hasSize(3); - // Regular handlers are registered first - assertThat(handlers.get(0)).isInstanceOf(CustomObservationHandler.class); // Multiple TracingObservationHandler are wrapped in - // FirstMatchingCompositeObservationHandler, which calls only the first - // one - assertThat(handlers.get(1)).isInstanceOf(CustomTracingObservationHandler.class); - assertThat(((CustomTracingObservationHandler) handlers.get(1)).getName()) + // FirstMatchingCompositeObservationHandler, which calls only the first one + assertThat(handlers.get(0)).isInstanceOf(CustomTracingObservationHandler.class); + assertThat(((CustomTracingObservationHandler) handlers.get(0)).getName()) .isEqualTo("customTracingHandler1"); // Multiple MeterObservationHandler are wrapped in - // FirstMatchingCompositeObservationHandler, which calls only the first - // one - assertThat(handlers.get(2)).isInstanceOf(CustomMeterObservationHandler.class); - assertThat(((CustomMeterObservationHandler) handlers.get(2)).getName()) + // FirstMatchingCompositeObservationHandler, which calls only the first one + assertThat(handlers.get(1)).isInstanceOf(CustomMeterObservationHandler.class); + assertThat(((CustomMeterObservationHandler) handlers.get(1)).getName()) .isEqualTo("customMeterObservationHandler1"); + // Regular handlers are registered last + assertThat(handlers.get(2)).isInstanceOf(CustomObservationHandler.class); assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class); assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class); }); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGroupingTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGroupingTests.java new file mode 100644 index 000000000000..62ac14d092b1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationHandlerGroupingTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.observation; + +import java.lang.reflect.Method; +import java.util.List; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler; +import io.micrometer.observation.ObservationRegistry.ObservationConfig; +import org.junit.jupiter.api.Test; + +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ObservationHandlerGrouping}. + * + * @author Moritz Halbritter + */ +class ObservationHandlerGroupingTests { + + @Test + void shouldGroupCategoriesIntoFirstMatchingHandlerAndRespectsCategoryOrder() { + ObservationHandlerGrouping grouping = new ObservationHandlerGrouping( + List.of(ObservationHandlerA.class, ObservationHandlerB.class)); + ObservationConfig config = new ObservationConfig(); + ObservationHandlerA handlerA1 = new ObservationHandlerA("a1"); + ObservationHandlerA handlerA2 = new ObservationHandlerA("a2"); + ObservationHandlerB handlerB1 = new ObservationHandlerB("b1"); + ObservationHandlerB handlerB2 = new ObservationHandlerB("b2"); + grouping.apply(List.of(handlerB1, handlerB2, handlerA1, handlerA2), config); + List> handlers = getObservationHandlers(config); + assertThat(handlers).hasSize(2); + // Category A is first + assertThat(handlers.get(0)).isInstanceOf(FirstMatchingCompositeObservationHandler.class); + FirstMatchingCompositeObservationHandler firstMatching0 = (FirstMatchingCompositeObservationHandler) handlers + .get(0); + assertThat(firstMatching0.getHandlers()).containsExactly(handlerA1, handlerA2); + // Category B is second + assertThat(handlers.get(1)).isInstanceOf(FirstMatchingCompositeObservationHandler.class); + FirstMatchingCompositeObservationHandler firstMatching1 = (FirstMatchingCompositeObservationHandler) handlers + .get(1); + assertThat(firstMatching1.getHandlers()).containsExactly(handlerB1, handlerB2); + } + + @Test + void uncategorizedHandlersShouldBeOrderedAfterCategories() { + ObservationHandlerGrouping grouping = new ObservationHandlerGrouping(ObservationHandlerA.class); + ObservationConfig config = new ObservationConfig(); + ObservationHandlerA handlerA1 = new ObservationHandlerA("a1"); + ObservationHandlerA handlerA2 = new ObservationHandlerA("a2"); + ObservationHandlerB handlerB1 = new ObservationHandlerB("b1"); + grouping.apply(List.of(handlerB1, handlerA1, handlerA2), config); + List> handlers = getObservationHandlers(config); + assertThat(handlers).hasSize(2); + // Category A is first + assertThat(handlers.get(0)).isInstanceOf(FirstMatchingCompositeObservationHandler.class); + FirstMatchingCompositeObservationHandler firstMatching0 = (FirstMatchingCompositeObservationHandler) handlers + .get(0); + // Uncategorized handlers follow + assertThat(firstMatching0.getHandlers()).containsExactly(handlerA1, handlerA2); + assertThat(handlers.get(1)).isEqualTo(handlerB1); + } + + @SuppressWarnings("unchecked") + private static List> getObservationHandlers(ObservationConfig config) { + Method method = ReflectionUtils.findMethod(ObservationConfig.class, "getObservationHandlers"); + ReflectionUtils.makeAccessible(method); + return (List>) ReflectionUtils.invokeMethod(method, config); + } + + private static class NamedObservationHandler implements ObservationHandler { + + private final String name; + + NamedObservationHandler(String name) { + this.name = name; + } + + @Override + public boolean supportsContext(Context context) { + return true; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{name='" + this.name + "'}"; + } + + } + + private static class ObservationHandlerA extends NamedObservationHandler { + + ObservationHandlerA(String name) { + super(name); + } + + } + + private static class ObservationHandlerB extends NamedObservationHandler { + + ObservationHandlerB(String name) { + super(name); + } + + } + +} From d51559956f0682ac40c794d1744da2cfd73069c5 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 16 Jun 2023 09:54:27 +0200 Subject: [PATCH 0016/1656] Support overriding default OTel SpanProcessor Also makes it easier to set the MeterProvider used in the default SpanProcessor. Closes gh-35560 --- .../OpenTelemetryAutoConfiguration.java | 29 +++++--- .../autoconfigure/tracing/SpanProcessors.java | 70 +++++++++++++++++++ .../OpenTelemetryAutoConfigurationTests.java | 50 ++++++++++++- .../tracing/SpanProcessorsTests.java | 52 ++++++++++++++ 4 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 0d86bb9c2cc5..b86f1eeda46a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import io.micrometer.tracing.SpanCustomizer; import io.micrometer.tracing.exporter.SpanExportingPredicate; @@ -37,6 +38,7 @@ import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.ContextStorage; import io.opentelemetry.context.propagation.ContextPropagators; @@ -47,6 +49,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; @@ -99,13 +102,13 @@ OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagat @Bean @ConditionalOnMissingBean - SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider spanProcessors, - Sampler sampler, ObjectProvider customizers) { + SdkTracerProvider otelSdkTracerProvider(Environment environment, SpanProcessors spanProcessors, Sampler sampler, + ObjectProvider customizers) { String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); SdkTracerProviderBuilder builder = SdkTracerProvider.builder() .setSampler(sampler) .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName))); - spanProcessors.orderedStream().forEach(builder::addSpanProcessor); + spanProcessors.forEach(builder::addSpanProcessor); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } @@ -124,14 +127,20 @@ Sampler otelSampler() { } @Bean - SpanProcessor otelSpanProcessor(ObjectProvider spanExporters, + @ConditionalOnMissingBean + SpanProcessors spanProcessors(ObjectProvider spanProcessors) { + return () -> spanProcessors.orderedStream().collect(Collectors.toList()); + } + + @Bean + BatchSpanProcessor otelSpanProcessor(ObjectProvider spanExporters, ObjectProvider spanExportingPredicates, ObjectProvider spanReporters, - ObjectProvider spanFilters) { - return BatchSpanProcessor - .builder(new CompositeSpanExporter(spanExporters.orderedStream().toList(), - spanExportingPredicates.orderedStream().toList(), spanReporters.orderedStream().toList(), - spanFilters.orderedStream().toList())) - .build(); + ObjectProvider spanFilters, ObjectProvider meterProvider) { + BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(new CompositeSpanExporter( + spanExporters.orderedStream().toList(), spanExportingPredicates.orderedStream().toList(), + spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList())); + meterProvider.ifAvailable(builder::setMeterProvider); + return builder.build(); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java new file mode 100644 index 000000000000..3edc77b0de46 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.tracing; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; + +import io.opentelemetry.sdk.trace.SpanProcessor; + +/** + * A collection of {@link SpanProcessor span processors}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public interface SpanProcessors extends Iterable { + + /** + * Returns the list of {@link SpanProcessor span processors}. + * @return the list of span processors + */ + List getList(); + + @Override + default Iterator iterator() { + return getList().iterator(); + } + + @Override + default Spliterator spliterator() { + return getList().spliterator(); + } + + /** + * Constructs a {@link SpanProcessors} instance with the given list of + * {@link SpanProcessor span processors}. + * @param spanProcessors the list of span processors + * @return the constructed {@link SpanProcessors} instance + */ + static SpanProcessors of(List spanProcessors) { + return () -> spanProcessors; + } + + /** + * Constructs a {@link SpanProcessors} instance with the given {@link SpanProcessor + * span processors}. + * @param spanProcessors the span processors + * @return the constructed {@link SpanProcessors} instance + */ + static SpanProcessors of(SpanProcessor... spanProcessors) { + return of(Arrays.asList(spanProcessors)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java index 02ff5e562e9f..2bb1b24924b6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java @@ -29,6 +29,7 @@ import io.micrometer.tracing.otel.bridge.Slf4JEventListener; import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.propagation.ContextPropagators; @@ -41,6 +42,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; @@ -51,6 +53,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** @@ -82,6 +87,7 @@ void shouldSupplyBeans() { assertThat(context).hasSingleBean(OtelPropagator.class); assertThat(context).hasSingleBean(TextMapPropagator.class); assertThat(context).hasSingleBean(OtelSpanCustomizer.class); + assertThat(context).hasSingleBean(SpanProcessors.class); }); } @@ -112,6 +118,7 @@ void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { assertThat(context).doesNotHaveBean(OtelPropagator.class); assertThat(context).doesNotHaveBean(TextMapPropagator.class); assertThat(context).doesNotHaveBean(OtelSpanCustomizer.class); + assertThat(context).doesNotHaveBean(SpanProcessors.class); }); } @@ -142,14 +149,18 @@ void shouldBackOffOnCustomBeans() { assertThat(context).hasSingleBean(OtelPropagator.class); assertThat(context).hasBean("customSpanCustomizer"); assertThat(context).hasSingleBean(SpanCustomizer.class); + assertThat(context).hasBean("customSpanProcessors"); + assertThat(context).hasSingleBean(SpanProcessors.class); }); } @Test void shouldAllowMultipleSpanProcessors() { - this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + this.contextRunner.withUserConfiguration(AdditionalSpanProcessorConfiguration.class).run((context) -> { assertThat(context.getBeansOfType(SpanProcessor.class)).hasSize(2); assertThat(context).hasBean("customSpanProcessor"); + SpanProcessors spanProcessors = context.getBean(SpanProcessors.class); + assertThat(spanProcessors).hasSize(2); }); } @@ -235,9 +246,46 @@ void shouldCustomizeSdkTracerProvider() { }); } + @Test + void defaultSpanProcessorShouldUseMeterProviderIfAvailable() { + this.contextRunner.withUserConfiguration(MeterProviderConfiguration.class).run((context) -> { + MeterProvider meterProvider = context.getBean(MeterProvider.class); + assertThat(Mockito.mockingDetails(meterProvider).isMock()).isTrue(); + then(meterProvider).should().meterBuilder(anyString()); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class MeterProviderConfiguration { + + @Bean + MeterProvider meterProvider() { + MeterProvider mock = mock(MeterProvider.class); + given(mock.meterBuilder(anyString())) + .willAnswer((invocation) -> MeterProvider.noop().meterBuilder(invocation.getArgument(0, String.class))); + return mock; + } + + } + + @Configuration(proxyBeanMethods = false) + private static class AdditionalSpanProcessorConfiguration { + + @Bean + SpanProcessor customSpanProcessor() { + return mock(SpanProcessor.class); + } + + } + @Configuration(proxyBeanMethods = false) private static class CustomConfiguration { + @Bean + SpanProcessors customSpanProcessors() { + return SpanProcessors.of(mock(SpanProcessor.class)); + } + @Bean io.micrometer.tracing.Tracer customMicrometerTracer() { return mock(io.micrometer.tracing.Tracer.class); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java new file mode 100644 index 000000000000..f65a3b07dddd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.tracing; + +import java.util.List; + +import io.opentelemetry.sdk.trace.SpanProcessor; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SpanProcessors}. + * + * @author Moritz Halbritter + */ +class SpanProcessorsTests { + + @Test + void ofList() { + SpanProcessor spanProcessor1 = mock(SpanProcessor.class); + SpanProcessor spanProcessor2 = mock(SpanProcessor.class); + SpanProcessors spanProcessors = SpanProcessors.of(List.of(spanProcessor1, spanProcessor2)); + assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2); + assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2); + } + + @Test + void ofArray() { + SpanProcessor spanProcessor1 = mock(SpanProcessor.class); + SpanProcessor spanProcessor2 = mock(SpanProcessor.class); + SpanProcessors spanProcessors = SpanProcessors.of(spanProcessor1, spanProcessor2); + assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2); + assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2); + } + +} From 929283f4dc2e21e5ddbd046d939aa2a0d7a9077e Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 16 Jun 2023 10:24:45 +0200 Subject: [PATCH 0017/1656] Support overriding OTel SpanExporters See gh-35596 --- .../OpenTelemetryAutoConfiguration.java | 14 +++- .../autoconfigure/tracing/SpanExporters.java | 70 +++++++++++++++++ .../OpenTelemetryAutoConfigurationTests.java | 76 ++++++++++++++++--- .../tracing/SpanExportersTests.java | 52 +++++++++++++ 4 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index b86f1eeda46a..4220d31c0d26 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -133,16 +133,22 @@ SpanProcessors spanProcessors(ObjectProvider spanProcessors) { } @Bean - BatchSpanProcessor otelSpanProcessor(ObjectProvider spanExporters, + BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters, ObjectProvider spanExportingPredicates, ObjectProvider spanReporters, ObjectProvider spanFilters, ObjectProvider meterProvider) { - BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(new CompositeSpanExporter( - spanExporters.orderedStream().toList(), spanExportingPredicates.orderedStream().toList(), - spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList())); + BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder( + new CompositeSpanExporter(spanExporters.getList(), spanExportingPredicates.orderedStream().toList(), + spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList())); meterProvider.ifAvailable(builder::setMeterProvider); return builder.build(); } + @Bean + @ConditionalOnMissingBean + SpanExporters spanExporters(ObjectProvider spanExporters) { + return SpanExporters.of(spanExporters.orderedStream().collect(Collectors.toList())); + } + @Bean @ConditionalOnMissingBean Tracer otelTracer(OpenTelemetry openTelemetry) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java new file mode 100644 index 000000000000..b6f133e57b7a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.tracing; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; + +import io.opentelemetry.sdk.trace.export.SpanExporter; + +/** + * A collection of {@link SpanExporter span exporters}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public interface SpanExporters extends Iterable { + + /** + * Returns the list of {@link SpanExporter span exporters}. + * @return the list of span exporters + */ + List getList(); + + @Override + default Iterator iterator() { + return getList().iterator(); + } + + @Override + default Spliterator spliterator() { + return getList().spliterator(); + } + + /** + * Constructs a {@link SpanExporters} instance with the given list of + * {@link SpanExporter span exporters}. + * @param spanExporters the list of span exporters + * @return the constructed {@link SpanExporters} instance + */ + static SpanExporters of(List spanExporters) { + return () -> spanExporters; + } + + /** + * Constructs a {@link SpanExporters} instance with the given {@link SpanExporter span + * exporters}. + * @param spanExporters the span exporters + * @return the constructed {@link SpanExporters} instance + */ + static SpanExporters of(SpanExporter... spanExporters) { + return of(Arrays.asList(spanExporters)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java index 2bb1b24924b6..e75701765366 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import io.micrometer.tracing.SpanCustomizer; @@ -35,9 +36,12 @@ import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.extension.trace.propagation.B3Propagator; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanLimits; import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -88,6 +92,7 @@ void shouldSupplyBeans() { assertThat(context).hasSingleBean(TextMapPropagator.class); assertThat(context).hasSingleBean(OtelSpanCustomizer.class); assertThat(context).hasSingleBean(SpanProcessors.class); + assertThat(context).hasSingleBean(SpanExporters.class); }); } @@ -119,6 +124,7 @@ void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { assertThat(context).doesNotHaveBean(TextMapPropagator.class); assertThat(context).doesNotHaveBean(OtelSpanCustomizer.class); assertThat(context).doesNotHaveBean(SpanProcessors.class); + assertThat(context).doesNotHaveBean(SpanExporters.class); }); } @@ -151,6 +157,8 @@ void shouldBackOffOnCustomBeans() { assertThat(context).hasSingleBean(SpanCustomizer.class); assertThat(context).hasBean("customSpanProcessors"); assertThat(context).hasSingleBean(SpanProcessors.class); + assertThat(context).hasBean("customSpanExporters"); + assertThat(context).hasSingleBean(SpanExporters.class); }); } @@ -164,6 +172,17 @@ void shouldAllowMultipleSpanProcessors() { }); } + @Test + void shouldAllowMultipleSpanExporters() { + this.contextRunner.withUserConfiguration(MultipleSpanExporterConfiguration.class).run((context) -> { + assertThat(context.getBeansOfType(SpanExporter.class)).hasSize(2); + assertThat(context).hasBean("spanExporter1"); + assertThat(context).hasBean("spanExporter2"); + SpanExporters spanExporters = context.getBean(SpanExporters.class); + assertThat(spanExporters).hasSize(2); + }); + } + @Test void shouldAllowMultipleTextMapPropagators() { this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { @@ -228,15 +247,6 @@ void shouldSupplyW3CPropagationWithoutBaggageWhenDisabled() { }); } - private List getInjectors(TextMapPropagator propagator) { - assertThat(propagator).as("propagator").isNotNull(); - if (propagator instanceof CompositeTextMapPropagator compositePropagator) { - return compositePropagator.getInjectors().stream().toList(); - } - fail("Expected CompositeTextMapPropagator, found %s".formatted(propagator.getClass())); - throw new AssertionError("Unreachable"); - } - @Test void shouldCustomizeSdkTracerProvider() { this.contextRunner.withUserConfiguration(SdkTracerProviderCustomizationConfiguration.class).run((context) -> { @@ -255,6 +265,15 @@ void defaultSpanProcessorShouldUseMeterProviderIfAvailable() { }); } + private List getInjectors(TextMapPropagator propagator) { + assertThat(propagator).as("propagator").isNotNull(); + if (propagator instanceof CompositeTextMapPropagator compositePropagator) { + return compositePropagator.getInjectors().stream().toList(); + } + fail("Expected CompositeTextMapPropagator, found %s".formatted(propagator.getClass())); + throw new AssertionError("Unreachable"); + } + @Configuration(proxyBeanMethods = false) private static class MeterProviderConfiguration { @@ -278,6 +297,21 @@ SpanProcessor customSpanProcessor() { } + @Configuration(proxyBeanMethods = false) + private static class MultipleSpanExporterConfiguration { + + @Bean + SpanExporter spanExporter1() { + return new DummySpanExporter(); + } + + @Bean + SpanExporter spanExporter2() { + return new DummySpanExporter(); + } + + } + @Configuration(proxyBeanMethods = false) private static class CustomConfiguration { @@ -286,6 +320,11 @@ SpanProcessors customSpanProcessors() { return SpanProcessors.of(mock(SpanProcessor.class)); } + @Bean + SpanExporters customSpanExporters() { + return SpanExporters.of(new DummySpanExporter()); + } + @Bean io.micrometer.tracing.Tracer customMicrometerTracer() { return mock(io.micrometer.tracing.Tracer.class); @@ -381,4 +420,23 @@ SdkTracerProviderBuilderCustomizer sdkTracerProviderBuilderCustomizerTwo() { } + private static class DummySpanExporter implements SpanExporter { + + @Override + public CompletableResultCode export(Collection spans) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java new file mode 100644 index 000000000000..dc883b971863 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.tracing; + +import java.util.List; + +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SpanExporters}. + * + * @author Moritz Halbritter + */ +class SpanExportersTests { + + @Test + void ofList() { + SpanExporter spanExporter1 = mock(SpanExporter.class); + SpanExporter spanExporter2 = mock(SpanExporter.class); + SpanExporters spanExporters = SpanExporters.of(List.of(spanExporter1, spanExporter2)); + assertThat(spanExporters).containsExactly(spanExporter1, spanExporter2); + assertThat(spanExporters.getList()).containsExactly(spanExporter1, spanExporter2); + } + + @Test + void ofArray() { + SpanExporter spanExporter1 = mock(SpanExporter.class); + SpanExporter spanExporter2 = mock(SpanExporter.class); + SpanExporters spanExporters = SpanExporters.of(spanExporter1, spanExporter2); + assertThat(spanExporters).containsExactly(spanExporter1, spanExporter2); + assertThat(spanExporters.getList()).containsExactly(spanExporter1, spanExporter2); + } + +} From c25b084391b7a5aae5b4104619aa46af092b7599 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 16 Jun 2023 10:36:33 +0200 Subject: [PATCH 0018/1656] Polish --- .../autoconfigure/tracing/OpenTelemetryAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 4220d31c0d26..6a30b9cbc636 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -129,7 +129,7 @@ Sampler otelSampler() { @Bean @ConditionalOnMissingBean SpanProcessors spanProcessors(ObjectProvider spanProcessors) { - return () -> spanProcessors.orderedStream().collect(Collectors.toList()); + return SpanProcessors.of(spanProcessors.orderedStream().collect(Collectors.toList())); } @Bean From 27add2bbe3095805f4bb159f33e9aefd3292df2e Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 16 Jun 2023 14:09:00 +0200 Subject: [PATCH 0019/1656] Rework @AutoConfigureObservability and tracing auto-configurations @ConditionalOnEnabledTracing is now applied to the minimal amount of beans. The beans which are annotated with it are beans that will lead to span sending to backends. This leaves the majority of the Micrometer Tracing, Brave and OpenTelemetry infrastructure untouched in tests. Closes gh-35354 --- .../tracing/BraveAutoConfiguration.java | 1 - .../MicrometerTracingAutoConfiguration.java | 1 - .../OpenTelemetryAutoConfiguration.java | 1 - .../tracing/otlp/OtlpAutoConfiguration.java | 2 +- .../PrometheusExemplarsAutoConfiguration.java | 2 - .../WavefrontTracingAutoConfiguration.java | 4 +- .../zipkin/ZipkinAutoConfiguration.java | 2 - .../tracing/zipkin/ZipkinConfigurations.java | 3 + .../WavefrontSenderConfiguration.java | 23 +++++ ...intsAutoConfigurationIntegrationTests.java | 5 +- .../tracing/BraveAutoConfigurationTests.java | 6 -- ...crometerTracingAutoConfigurationTests.java | 11 --- .../otlp/OtlpAutoConfigurationTests.java | 10 +++ ...etheusExemplarsAutoConfigurationTests.java | 8 +- ...avefrontTracingAutoConfigurationTests.java | 16 ++-- .../zipkin/ZipkinAutoConfigurationTests.java | 6 -- ...ConfigurationsBraveConfigurationTests.java | 9 ++ ...ationsOpenTelemetryConfigurationTests.java | 9 ++ .../WavefrontSenderConfigurationTests.java | 29 ++++++ ...ObservabilityContextCustomizerFactory.java | 90 ------------------- ...vabilityContextCustomizerFactoryTests.java | 70 --------------- 21 files changed, 100 insertions(+), 208 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java index 4001f0e7fad5..393ff7dbfa1a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java @@ -76,7 +76,6 @@ @AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) @ConditionalOnClass({ Tracer.class, BraveTracer.class }) @EnableConfigurationProperties(TracingProperties.class) -@ConditionalOnEnabledTracing public class BraveAutoConfiguration { private static final BraveBaggageManager BRAVE_BAGGAGE_MANAGER = new BraveBaggageManager(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java index e91e41a5b057..149141c887f9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java @@ -39,7 +39,6 @@ */ @AutoConfiguration @ConditionalOnClass(Tracer.class) -@ConditionalOnEnabledTracing public class MicrometerTracingAutoConfiguration { /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 6a30b9cbc636..f2ccd0a5a629 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -75,7 +75,6 @@ * @since 3.0.0 */ @AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) -@ConditionalOnEnabledTracing @ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class }) @EnableConfigurationProperties(TracingProperties.class) public class OpenTelemetryAutoConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java index ca2012c20d64..e86b48ae555e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java @@ -50,7 +50,6 @@ * @since 3.1.0 */ @AutoConfiguration -@ConditionalOnEnabledTracing @ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class, OtlpHttpSpanExporter.class }) @EnableConfigurationProperties(OtlpProperties.class) public class OtlpAutoConfiguration { @@ -59,6 +58,7 @@ public class OtlpAutoConfiguration { @ConditionalOnMissingBean(value = OtlpHttpSpanExporter.class, type = "io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter") @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "endpoint") + @ConditionalOnEnabledTracing OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties) { OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder() .setEndpoint(properties.getEndpoint()) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java index 9032a0712ad5..e669127982c7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java @@ -22,7 +22,6 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -43,7 +42,6 @@ after = MicrometerTracingAutoConfiguration.class) @ConditionalOnBean(Tracer.class) @ConditionalOnClass({ Tracer.class, SpanContextSupplier.class }) -@ConditionalOnEnabledTracing public class PrometheusExemplarsAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java index 81a0dd0863c3..f3fab744bcc4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java @@ -52,7 +52,6 @@ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, WavefrontAutoConfiguration.class }) @ConditionalOnClass({ WavefrontSender.class, WavefrontSpanHandler.class }) -@ConditionalOnEnabledTracing @EnableConfigurationProperties(WavefrontProperties.class) @Import(WavefrontSenderConfiguration.class) public class WavefrontTracingAutoConfiguration { @@ -60,6 +59,7 @@ public class WavefrontTracingAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(WavefrontSender.class) + @ConditionalOnEnabledTracing WavefrontSpanHandler wavefrontSpanHandler(WavefrontProperties properties, WavefrontSender wavefrontSender, SpanMetrics spanMetrics, ApplicationTags applicationTags) { return new WavefrontSpanHandler(properties.getSender().getMaxQueueSize(), wavefrontSender, spanMetrics, @@ -96,6 +96,7 @@ static class WavefrontBrave { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledTracing WavefrontBraveSpanHandler wavefrontBraveSpanHandler(WavefrontSpanHandler wavefrontSpanHandler) { return new WavefrontBraveSpanHandler(wavefrontSpanHandler); } @@ -108,6 +109,7 @@ static class WavefrontOpenTelemetry { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledTracing WavefrontOtelSpanExporter wavefrontOtelSpanExporter(WavefrontSpanHandler wavefrontSpanHandler) { return new WavefrontOtelSpanExporter(wavefrontSpanHandler); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java index 971b9d514ec1..daff635f8631 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfiguration.java @@ -21,7 +21,6 @@ import zipkin2.codec.SpanBytesEncoder; import zipkin2.reporter.Sender; -import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration; @@ -48,7 +47,6 @@ @ConditionalOnClass(Sender.class) @Import({ SenderConfiguration.class, ReporterConfiguration.class, BraveConfiguration.class, OpenTelemetryConfiguration.class }) -@ConditionalOnEnabledTracing @EnableConfigurationProperties(ZipkinProperties.class) public class ZipkinAutoConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java index 722b502befa5..b4a1802b7bc6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java @@ -26,6 +26,7 @@ import zipkin2.reporter.urlconnection.URLConnectionSender; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -142,6 +143,7 @@ static class BraveConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(Reporter.class) + @ConditionalOnEnabledTracing ZipkinSpanHandler zipkinSpanHandler(Reporter spanReporter) { return (ZipkinSpanHandler) ZipkinSpanHandler.newBuilder(spanReporter).build(); } @@ -155,6 +157,7 @@ static class OpenTelemetryConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(Sender.class) + @ConditionalOnEnabledTracing ZipkinSpanExporter zipkinSpanExporter(BytesEncoder encoder, Sender sender) { return ZipkinSpanExporter.builder().setEncoder(encoder).setSender(sender).build(); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java index 6cb11ae31df3..1416abc72ec4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java @@ -21,13 +21,17 @@ import com.wavefront.sdk.common.WavefrontSender; import com.wavefront.sdk.common.clients.WavefrontClient.Builder; +import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontTracingAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.util.unit.DataSize; @@ -46,6 +50,7 @@ public class WavefrontSenderConfiguration { @Bean @ConditionalOnMissingBean + @Conditional(WavefrontTracingOrMetricsCondition.class) public WavefrontSender wavefrontSender(WavefrontProperties properties) { Builder builder = new Builder(properties.getEffectiveUri().toString(), properties.getApiTokenOrThrow()); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); @@ -57,4 +62,22 @@ public WavefrontSender wavefrontSender(WavefrontProperties properties) { return builder.build(); } + static final class WavefrontTracingOrMetricsCondition extends AnyNestedCondition { + + WavefrontTracingOrMetricsCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnEnabledTracing + static class TracingCondition { + + } + + @ConditionalOnEnabledMetricsExport("wavefront") + static class MetricsCondition { + + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java index f2d6c56aca3b..33aa8931dd26 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java @@ -19,6 +19,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -80,7 +82,8 @@ private ReactiveWebApplicationContextRunner reactiveWebRunner() { MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class, HazelcastAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, RedisAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class }) + RedisRepositoriesAutoConfiguration.class, BraveAutoConfiguration.class, + OpenTelemetryAutoConfiguration.class }) @SpringBootConfiguration static class WebEndpointTestApplication { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java index e51998741cef..073fd9b96871 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java @@ -151,12 +151,6 @@ void shouldSupplyB3PropagationFactoryViaProperty() { }); } - @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(BraveAutoConfiguration.class)); - } - @Test void shouldNotSupplyCorrelationScopeDecoratorIfBaggageDisabled() { this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java index 6f3c4a373d06..d9f94f46396a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java @@ -111,17 +111,6 @@ void shouldNotSupplyBeansIfPropagatorIsMissing() { }); } - @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withUserConfiguration(TracerConfiguration.class, PropagatorConfiguration.class) - .withPropertyValues("management.tracing.enabled=false") - .run((context) -> { - assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class); - assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class); - assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class); - }); - } - @Configuration(proxyBeanMethods = false) private static class TracerConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java index f3b3e5b3a5a9..0f5fbca25c80 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java @@ -40,6 +40,9 @@ class OtlpAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(OtlpAutoConfiguration.class)); + private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner + .withPropertyValues("management.tracing.enabled=false"); + @Test void shouldNotSupplyBeansIfPropertyIsNotSet() { this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(OtlpHttpSpanExporter.class)); @@ -96,6 +99,13 @@ void shouldBackOffWhenCustomGrpcExporterIsDefined() { .hasSingleBean(SpanExporter.class)); } + @Test + void shouldNotSupplyOtlpHttpSpanExporterIfTracingIsDisabled() { + this.tracingDisabledContextRunner + .withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") + .run((context) -> assertThat(context).doesNotHaveBean(OtlpHttpSpanExporter.class)); + } + @Configuration(proxyBeanMethods = false) private static class CustomHttpExporterConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java index 2d2f9d35a530..3bbec4be49f3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfigurationTests.java @@ -40,7 +40,7 @@ /** * Tests for {@link PrometheusExemplarsAutoConfiguration}. * - * * @author Jonatan Ivanov + * @author Jonatan Ivanov */ class PrometheusExemplarsAutoConfigurationTests { @@ -52,12 +52,6 @@ class PrometheusExemplarsAutoConfigurationTests { AutoConfigurations.of(PrometheusExemplarsAutoConfiguration.class, ObservationAutoConfiguration.class, BraveAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)); - @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); - } - @Test void shouldNotSupplyBeansIfPrometheusSupportIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("io.prometheus.client.exemplars")) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java index a75d692772e6..8295ed6930f6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java @@ -47,6 +47,9 @@ class WavefrontTracingAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( AutoConfigurations.of(WavefrontAutoConfiguration.class, WavefrontTracingAutoConfiguration.class)); + private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner + .withPropertyValues("management.tracing.enabled=false"); + @Test void shouldSupplyBeans() { this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { @@ -83,14 +86,11 @@ void shouldNotSupplyBeansIfMicrometerReporterWavefrontIsMissing() { @Test void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .withUserConfiguration(WavefrontSenderConfiguration.class) - .run((context) -> { - assertThat(context).doesNotHaveBean(WavefrontSpanHandler.class); - assertThat(context).doesNotHaveBean(SpanMetrics.class); - assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class); - assertThat(context).doesNotHaveBean(WavefrontOtelSpanExporter.class); - }); + this.tracingDisabledContextRunner.withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { + assertThat(context).doesNotHaveBean(WavefrontSpanHandler.class); + assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class); + assertThat(context).doesNotHaveBean(WavefrontOtelSpanExporter.class); + }); } @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java index c647bfbfe2ac..1da2e7668a87 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinAutoConfigurationTests.java @@ -59,12 +59,6 @@ void shouldBackOffOnCustomBeans() { }); } - @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(BytesEncoder.class)); - } - @Test void definesPropertiesBasedConnectionDetailsByDefault() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesZipkinConnectionDetails.class)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java index 735cd00d0c6b..9b488aebe404 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsBraveConfigurationTests.java @@ -42,6 +42,9 @@ class ZipkinConfigurationsBraveConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(BraveConfiguration.class)); + private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner + .withPropertyValues("management.tracing.enabled=false"); + @Test void shouldSupplyBeans() { this.contextRunner.withUserConfiguration(ReporterConfiguration.class) @@ -79,6 +82,12 @@ void shouldSupplyZipkinSpanHandlerWithCustomSpanHandler() { }); } + @Test + void shouldNotSupplyZipkinSpanHandlerIfTracingIsDisabled() { + this.tracingDisabledContextRunner.withUserConfiguration(ReporterConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanHandler.class)); + } + @Configuration(proxyBeanMethods = false) private static class ReporterConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java index c3ef5f99c371..5c2a9059c558 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsOpenTelemetryConfigurationTests.java @@ -43,6 +43,9 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(BaseConfiguration.class, OpenTelemetryConfiguration.class)); + private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner + .withPropertyValues("management.tracing.enabled=false"); + @Test void shouldSupplyBeans() { this.contextRunner.withUserConfiguration(SenderConfiguration.class) @@ -70,6 +73,12 @@ void shouldBackOffOnCustomBeans() { }); } + @Test + void shouldNotSupplyZipkinSpanExporterIfTracingIsDisabled() { + this.tracingDisabledContextRunner.withUserConfiguration(SenderConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class)); + } + @Configuration(proxyBeanMethods = false) private static class SenderConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java index a75203e4fedf..367adebb5c59 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfigurationTests.java @@ -42,6 +42,17 @@ class WavefrontSenderConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(WavefrontSenderConfiguration.class)); + private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner + .withPropertyValues("management.tracing.enabled=false"); + + private final ApplicationContextRunner metricsDisabledContextRunner = this.contextRunner.withPropertyValues( + "management.defaults.metrics.export.enabled=false", "management.simple.metrics.export.enabled=true"); + + // Both metrics and tracing are disabled + private final ApplicationContextRunner observabilityDisabledContextRunner = this.contextRunner.withPropertyValues( + "management.tracing.enabled=false", "management.defaults.metrics.export.enabled=false", + "management.simple.metrics.export.enabled=true"); + @Test void shouldNotFailIfWavefrontIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("com.wavefront")) @@ -83,6 +94,24 @@ void configureWavefrontSender() { }); } + @Test + void shouldNotSupplyWavefrontSenderIfObservabilityIsDisabled() { + this.observabilityDisabledContextRunner.withPropertyValues("management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).doesNotHaveBean(WavefrontSender.class)); + } + + @Test + void shouldSupplyWavefrontSenderIfOnlyTracingIsDisabled() { + this.tracingDisabledContextRunner.withPropertyValues("management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).hasSingleBean(WavefrontSender.class)); + } + + @Test + void shouldSupplyWavefrontSenderIfOnlyMetricsAreDisabled() { + this.metricsDisabledContextRunner.withPropertyValues("management.wavefront.api-token=abcde") + .run((context) -> assertThat(context).hasSingleBean(WavefrontSender.class)); + } + @Test void allowsWavefrontSenderToBeCustomized() { this.contextRunner.withUserConfiguration(CustomSenderConfiguration.class) diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java index 918777baf1e7..86a003b6b6e0 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactory.java @@ -19,39 +19,19 @@ import java.util.List; import java.util.Objects; -import io.micrometer.tracing.Tracer; - -import org.springframework.aot.AotDetector; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.ConfigurationClassPostProcessor; -import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.TestContextAnnotationUtils; -import org.springframework.util.ClassUtils; /** * {@link ContextCustomizerFactory} that globally disables metrics export and tracing in * tests. The behaviour can be controlled with {@link AutoConfigureObservability} on the * test class or via the {@value #AUTO_CONFIGURE_PROPERTY} property. - *

- * Registers {@link Tracer#NOOP} if tracing is disabled, micrometer-tracing is on the - * classpath, and the user hasn't supplied their own {@link Tracer}. * * @author Chris Bono * @author Moritz Halbritter @@ -87,7 +67,6 @@ public void customizeContext(ConfigurableApplicationContext context, } if (isTracingDisabled(context.getEnvironment())) { TestPropertyValues.of("management.tracing.enabled=false").applyTo(context); - registerNoopTracer(context); } } @@ -105,25 +84,6 @@ private boolean isTracingDisabled(Environment environment) { return !environment.getProperty(AUTO_CONFIGURE_PROPERTY, Boolean.class, false); } - private void registerNoopTracer(ConfigurableApplicationContext context) { - if (AotDetector.useGeneratedArtifacts()) { - return; - } - if (!ClassUtils.isPresent("io.micrometer.tracing.Tracer", context.getClassLoader())) { - return; - } - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - if (beanFactory instanceof BeanDefinitionRegistry registry) { - registerNoopTracer(registry); - } - } - - private void registerNoopTracer(BeanDefinitionRegistry registry) { - RootBeanDefinition definition = new RootBeanDefinition(NoopTracerRegistrar.class); - definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(NoopTracerRegistrar.class.getName(), definition); - } - @Override public boolean equals(Object o) { if (this == o) { @@ -143,54 +103,4 @@ public int hashCode() { } - /** - * {@link BeanDefinitionRegistryPostProcessor} that runs after the - * {@link ConfigurationClassPostProcessor} and adds a {@link Tracer} bean definition - * when a {@link Tracer} hasn't already been registered. - */ - static class NoopTracerRegistrar implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware { - - private BeanFactory beanFactory; - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; - } - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - if (AotDetector.useGeneratedArtifacts()) { - return; - } - if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory, - Tracer.class, false, false).length == 0) { - registry.registerBeanDefinition("noopTracer", new RootBeanDefinition(NoopTracerFactoryBean.class)); - } - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - } - - } - - static class NoopTracerFactoryBean implements FactoryBean { - - @Override - public Tracer getObject() { - return Tracer.NOOP; - } - - @Override - public Class getObjectType() { - return Tracer.class; - } - - } - } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java index d8e46424c52e..c8604bf53071 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/observability/ObservabilityContextCustomizerFactoryTests.java @@ -18,22 +18,15 @@ import java.util.Collections; -import io.micrometer.tracing.Tracer; import org.junit.jupiter.api.Test; -import org.springframework.boot.context.annotation.UserConfigurations; -import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericApplicationContext; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.ContextCustomizer; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; /** * Tests for {@link AutoConfigureObservability} and @@ -82,59 +75,6 @@ void shouldEnableBothWhenAnnotated() { assertThatTracingIsEnabled(context); } - @Test - void shouldRegisterNoopTracerIfTracingIsDisabled() { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - ConfigurableApplicationContext context = new GenericApplicationContext(); - applyCustomizerToContext(customizer, context); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - assertThat(tracer).isNotNull(); - assertThat(tracer.nextSpan().isNoop()).isTrue(); - } - - @Test - void shouldNotRegisterNoopTracerIfTracingIsEnabled() { - ContextCustomizer customizer = createContextCustomizer(WithAnnotation.class); - ConfigurableApplicationContext context = new GenericApplicationContext(); - applyCustomizerToContext(customizer, context); - context.refresh(); - assertThat(context.getBeanProvider(Tracer.class).getIfAvailable()).as("Tracer bean").isNull(); - } - - @Test - void shouldNotRegisterNoopTracerIfMicrometerTracingIsNotPresent() throws Exception { - try (FilteredClassLoader filteredClassLoader = new FilteredClassLoader("io.micrometer.tracing")) { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - new ApplicationContextRunner().withClassLoader(filteredClassLoader) - .withInitializer(applyCustomizer(customizer)) - .run((context) -> { - assertThat(context).doesNotHaveBean(Tracer.class); - assertThatMetricsAreDisabled(context); - assertThatTracingIsDisabled(context); - }); - } - } - - @Test - void shouldBackOffOnCustomTracer() { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - new ApplicationContextRunner().withConfiguration(UserConfigurations.of(CustomTracer.class)) - .withInitializer(applyCustomizer(customizer)) - .run((context) -> { - assertThat(context).hasSingleBean(Tracer.class); - assertThat(context).hasBean("customTracer"); - }); - } - - @Test - void shouldNotRunIfAotIsEnabled() { - ContextCustomizer customizer = createContextCustomizer(NoAnnotation.class); - new ApplicationContextRunner().withSystemProperties("spring.aot.enabled:true") - .withInitializer(applyCustomizer(customizer)) - .run((context) -> assertThat(context).doesNotHaveBean(Tracer.class)); - } - @Test void notEquals() { ContextCustomizer customizer1 = createContextCustomizer(OnlyMetrics.class); @@ -256,14 +196,4 @@ static class WithDisabledAnnotation { } - @Configuration(proxyBeanMethods = false) - static class CustomTracer { - - @Bean - Tracer customTracer() { - return mock(Tracer.class); - } - - } - } From 3664df61eb9b46cf14ade3e8322eea8ac369c6e7 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 16 Jun 2023 14:53:40 +0200 Subject: [PATCH 0020/1656] Polish API of SpanExporters and SpanProcessors --- .../tracing/OpenTelemetryAutoConfiguration.java | 6 +++--- .../boot/actuate/autoconfigure/tracing/SpanExporters.java | 6 +++--- .../boot/actuate/autoconfigure/tracing/SpanProcessors.java | 6 +++--- .../actuate/autoconfigure/tracing/SpanExportersTests.java | 4 ++-- .../actuate/autoconfigure/tracing/SpanProcessorsTests.java | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index f2ccd0a5a629..e24dfc24473d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -135,9 +135,9 @@ SpanProcessors spanProcessors(ObjectProvider spanProcessors) { BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters, ObjectProvider spanExportingPredicates, ObjectProvider spanReporters, ObjectProvider spanFilters, ObjectProvider meterProvider) { - BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder( - new CompositeSpanExporter(spanExporters.getList(), spanExportingPredicates.orderedStream().toList(), - spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList())); + BatchSpanProcessorBuilder builder = BatchSpanProcessor + .builder(new CompositeSpanExporter(spanExporters.list(), spanExportingPredicates.orderedStream().toList(), + spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList())); meterProvider.ifAvailable(builder::setMeterProvider); return builder.build(); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java index b6f133e57b7a..d718047a9f23 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java @@ -35,16 +35,16 @@ public interface SpanExporters extends Iterable { * Returns the list of {@link SpanExporter span exporters}. * @return the list of span exporters */ - List getList(); + List list(); @Override default Iterator iterator() { - return getList().iterator(); + return list().iterator(); } @Override default Spliterator spliterator() { - return getList().spliterator(); + return list().spliterator(); } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java index 3edc77b0de46..183e3bedac5f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java @@ -35,16 +35,16 @@ public interface SpanProcessors extends Iterable { * Returns the list of {@link SpanProcessor span processors}. * @return the list of span processors */ - List getList(); + List list(); @Override default Iterator iterator() { - return getList().iterator(); + return list().iterator(); } @Override default Spliterator spliterator() { - return getList().spliterator(); + return list().spliterator(); } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java index dc883b971863..d15f2d1aceb6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExportersTests.java @@ -37,7 +37,7 @@ void ofList() { SpanExporter spanExporter2 = mock(SpanExporter.class); SpanExporters spanExporters = SpanExporters.of(List.of(spanExporter1, spanExporter2)); assertThat(spanExporters).containsExactly(spanExporter1, spanExporter2); - assertThat(spanExporters.getList()).containsExactly(spanExporter1, spanExporter2); + assertThat(spanExporters.list()).containsExactly(spanExporter1, spanExporter2); } @Test @@ -46,7 +46,7 @@ void ofArray() { SpanExporter spanExporter2 = mock(SpanExporter.class); SpanExporters spanExporters = SpanExporters.of(spanExporter1, spanExporter2); assertThat(spanExporters).containsExactly(spanExporter1, spanExporter2); - assertThat(spanExporters.getList()).containsExactly(spanExporter1, spanExporter2); + assertThat(spanExporters.list()).containsExactly(spanExporter1, spanExporter2); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java index f65a3b07dddd..8a5fa76868de 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessorsTests.java @@ -37,7 +37,7 @@ void ofList() { SpanProcessor spanProcessor2 = mock(SpanProcessor.class); SpanProcessors spanProcessors = SpanProcessors.of(List.of(spanProcessor1, spanProcessor2)); assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2); - assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2); + assertThat(spanProcessors.list()).containsExactly(spanProcessor1, spanProcessor2); } @Test @@ -46,7 +46,7 @@ void ofArray() { SpanProcessor spanProcessor2 = mock(SpanProcessor.class); SpanProcessors spanProcessors = SpanProcessors.of(spanProcessor1, spanProcessor2); assertThat(spanProcessors).containsExactly(spanProcessor1, spanProcessor2); - assertThat(spanProcessors.getList()).containsExactly(spanProcessor1, spanProcessor2); + assertThat(spanProcessors.list()).containsExactly(spanProcessor1, spanProcessor2); } } From 854b29b8fbdb34df180b890db92c6cc584656a9e Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 16 Jun 2023 14:20:03 -0700 Subject: [PATCH 0021/1656] Polish --- .../OpenTelemetryAutoConfiguration.java | 5 ++-- .../autoconfigure/tracing/SpanExporters.java | 26 ++++++++++++------- .../autoconfigure/tracing/SpanProcessors.java | 26 ++++++++++++------- .../web/embedded/JettyThreadPool.java | 9 +++---- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 93553ead79c4..8a3a4d79f820 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -18,7 +18,6 @@ import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import io.micrometer.tracing.SpanCustomizer; import io.micrometer.tracing.exporter.SpanExportingPredicate; @@ -128,7 +127,7 @@ Sampler otelSampler() { @Bean @ConditionalOnMissingBean SpanProcessors spanProcessors(ObjectProvider spanProcessors) { - return SpanProcessors.of(spanProcessors.orderedStream().collect(Collectors.toList())); + return SpanProcessors.of(spanProcessors.orderedStream().toList()); } @Bean @@ -145,7 +144,7 @@ BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters, @Bean @ConditionalOnMissingBean SpanExporters spanExporters(ObjectProvider spanExporters) { - return SpanExporters.of(spanExporters.orderedStream().collect(Collectors.toList())); + return SpanExporters.of(spanExporters.orderedStream().toList()); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java index d718047a9f23..a44f8ce0e035 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanExporters.java @@ -17,18 +17,22 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Spliterator; import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.springframework.util.Assert; + /** * A collection of {@link SpanExporter span exporters}. * * @author Moritz Halbritter * @since 3.2.0 */ +@FunctionalInterface public interface SpanExporters extends Iterable { /** @@ -47,16 +51,6 @@ default Spliterator spliterator() { return list().spliterator(); } - /** - * Constructs a {@link SpanExporters} instance with the given list of - * {@link SpanExporter span exporters}. - * @param spanExporters the list of span exporters - * @return the constructed {@link SpanExporters} instance - */ - static SpanExporters of(List spanExporters) { - return () -> spanExporters; - } - /** * Constructs a {@link SpanExporters} instance with the given {@link SpanExporter span * exporters}. @@ -67,4 +61,16 @@ static SpanExporters of(SpanExporter... spanExporters) { return of(Arrays.asList(spanExporters)); } + /** + * Constructs a {@link SpanExporters} instance with the given list of + * {@link SpanExporter span exporters}. + * @param spanExporters the list of span exporters + * @return the constructed {@link SpanExporters} instance + */ + static SpanExporters of(Collection spanExporters) { + Assert.notNull(spanExporters, "SpanExporters must not be null"); + List copy = List.copyOf(spanExporters); + return () -> copy; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java index 183e3bedac5f..ca8c55498d07 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpanProcessors.java @@ -17,18 +17,22 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Spliterator; import io.opentelemetry.sdk.trace.SpanProcessor; +import org.springframework.util.Assert; + /** * A collection of {@link SpanProcessor span processors}. * * @author Moritz Halbritter * @since 3.2.0 */ +@FunctionalInterface public interface SpanProcessors extends Iterable { /** @@ -47,16 +51,6 @@ default Spliterator spliterator() { return list().spliterator(); } - /** - * Constructs a {@link SpanProcessors} instance with the given list of - * {@link SpanProcessor span processors}. - * @param spanProcessors the list of span processors - * @return the constructed {@link SpanProcessors} instance - */ - static SpanProcessors of(List spanProcessors) { - return () -> spanProcessors; - } - /** * Constructs a {@link SpanProcessors} instance with the given {@link SpanProcessor * span processors}. @@ -67,4 +61,16 @@ static SpanProcessors of(SpanProcessor... spanProcessors) { return of(Arrays.asList(spanProcessors)); } + /** + * Constructs a {@link SpanProcessors} instance with the given list of + * {@link SpanProcessor span processors}. + * @param spanProcessors the list of span processors + * @return the constructed {@link SpanProcessors} instance + */ + static SpanProcessors of(Collection spanProcessors) { + Assert.notNull(spanProcessors, "SpanProcessors must not be null"); + List copy = List.copyOf(spanProcessors); + return () -> copy; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java index 56d01c91a232..7c8dadadb908 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyThreadPool.java @@ -26,8 +26,9 @@ import org.springframework.boot.autoconfigure.web.ServerProperties; /** - * Creates a {@link ThreadPool} for Jetty, applying the - * {@link ServerProperties.Jetty.Threads} properties. + * Creates a {@link ThreadPool} for Jetty, applying + * {@link org.springframework.boot.autoconfigure.web.ServerProperties.Jetty.Threads + * ServerProperties.Jetty.Threads Jetty thread properties}. * * @author Moritz Halbritter */ @@ -52,9 +53,7 @@ private static BlockingQueue determineBlockingQueue(Integer maxQueueCa if (maxQueueCapacity == 0) { return new SynchronousQueue<>(); } - else { - return new BlockingArrayQueue<>(maxQueueCapacity); - } + return new BlockingArrayQueue<>(maxQueueCapacity); } } From fe3579bd1e0814cbfefd157d1e057480b65d42b4 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:54:40 +0100 Subject: [PATCH 0022/1656] Upgrade to Angus Mail 2.0.2 Closes gh-35943 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index dfff8d658703..eea4a1b8c77c 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -50,7 +50,7 @@ bom { ] } } - library("Angus Mail", "1.1.0") { + library("Angus Mail", "2.0.2") { group("org.eclipse.angus") { modules = [ "angus-core", From fd2ae822f0d6d7c874115038073005f298863f46 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:54:46 +0100 Subject: [PATCH 0023/1656] Upgrade to Brave 5.16.0 Closes gh-35944 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index eea4a1b8c77c..8278f7b917a9 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -109,7 +109,7 @@ bom { ] } } - library("Brave", "5.15.1") { + library("Brave", "5.16.0") { group("io.zipkin.brave") { imports = [ "brave-bom" From 57a44bf55d02fdf0e2d1ad4c94a06cb8f81b1462 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:54:51 +0100 Subject: [PATCH 0024/1656] Upgrade to Build Helper Maven Plugin 3.4.0 Closes gh-35945 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 8278f7b917a9..dac5a516fe62 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -116,7 +116,7 @@ bom { ] } } - library("Build Helper Maven Plugin", "3.3.0") { + library("Build Helper Maven Plugin", "3.4.0") { group("org.codehaus.mojo") { plugins = [ "build-helper-maven-plugin" From 6b1a1141aaddc160eef23c72d5f9071244ab2c41 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:54:56 +0100 Subject: [PATCH 0025/1656] Upgrade to Cassandra Driver 4.16.0 Closes gh-35946 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index dac5a516fe62..fca2d584008a 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -153,7 +153,7 @@ bom { ] } } - library("Cassandra Driver", "4.15.0") { + library("Cassandra Driver", "4.16.0") { group("com.datastax.oss") { imports = [ "java-driver-bom" From 3d336f92044dbf824cadc181312f8e2b992019a1 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:00 +0100 Subject: [PATCH 0026/1656] Upgrade to Elasticsearch Client 8.8.1 Closes gh-35947 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index fca2d584008a..8d82984d26ae 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -262,7 +262,7 @@ bom { ] } } - library("Elasticsearch Client", "8.7.1") { + library("Elasticsearch Client", "8.8.1") { group("org.elasticsearch.client") { modules = [ "elasticsearch-rest-client" { From 454aae92d674206ff73155c1cdf7fae660db5488 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:05 +0100 Subject: [PATCH 0027/1656] Upgrade to Flyway 9.19.4 Closes gh-35948 --- .../autoconfigure/flyway/FlywayAutoConfiguration.java | 2 +- .../flyway/Flyway90AutoConfigurationTests.java | 2 ++ .../flyway/FlywayAutoConfigurationTests.java | 11 ++++++++++- .../autoconfigure/flyway/FlywayPropertiesTests.java | 2 +- .../spring-boot-dependencies/build.gradle | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) 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 3b9294ce6fbc..0c5928ed27c3 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 @@ -288,7 +288,7 @@ private void configureSqlServerKerberosLoginFile(FluentConfiguration configurati SQLServerConfigurationExtension sqlServerConfigurationExtension = configuration.getPluginRegister() .getPlugin(SQLServerConfigurationExtension.class); Assert.state(sqlServerConfigurationExtension != null, "Flyway SQL Server extension missing"); - sqlServerConfigurationExtension.setKerberosLoginFile(sqlServerKerberosLoginFile); + sqlServerConfigurationExtension.getKerberos().getLogin().setFile(sqlServerKerberosLoginFile); } private void configureCallbacks(FluentConfiguration configuration, List callbacks) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java index 6acaa1851ecb..97fd9ad3e6d9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import static org.assertj.core.api.Assertions.assertThat; @@ -34,6 +35,7 @@ * * @author Andy Wilkinson */ +@ClassPathExclusions("flyway-*.jar") @ClassPathOverrides("org.flywaydb:flyway-core:9.0.4") class Flyway90AutoConfigurationTests { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index b2148d973e5d..8aa67ff18922 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -33,6 +33,7 @@ import org.flywaydb.core.api.callback.Event; import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.internal.license.FlywayTeamsUpgradeRequiredException; +import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform; import org.jooq.DSLContext; import org.jooq.SQLDialect; @@ -700,7 +701,15 @@ void outputQueryResultsIsCorrectlyMapped() { void sqlServerKerberosLoginFileIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.sql-server-kerberos-login-file=/tmp/config") - .run(validateFlywayTeamsPropertyOnly("sqlserver.kerberos.login.file")); + .run((context) -> { + assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(SQLServerConfigurationExtension.class) + .getKerberos() + .getLogin() + .getFile()).isEqualTo("/tmp/config"); + }); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java index 98c3454b227f..0c7bb4110a2d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java @@ -116,7 +116,7 @@ void expectedPropertiesAreManaged() { "javaMigrationClassProvider", "pluginRegister", "resourceProvider", "resolvers"); // Properties we don't want to expose ignoreProperties(configuration, "resolversAsClassNames", "callbacksAsClassNames", "driver", "modernConfig", - "currentResolvedEnvironment", "reportFilename"); + "currentResolvedEnvironment", "reportFilename", "reportEnabled"); // Handled by the conversion service ignoreProperties(configuration, "baselineVersionAsString", "encodingAsString", "locationsAsStrings", "targetAsString"); diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 8d82984d26ae..136e6df1cd74 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -279,7 +279,7 @@ bom { ] } } - library("Flyway", "9.16.3") { + library("Flyway", "9.19.4") { group("org.flywaydb") { modules = [ "flyway-core", From e4b207e73b937bc833420124c9866f9e8f38521a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:10 +0100 Subject: [PATCH 0028/1656] Upgrade to Git Commit ID Maven Plugin 6.0.0 Closes gh-35949 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 136e6df1cd74..f1f616cf793f 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -299,7 +299,7 @@ bom { ] } } - library("Git Commit ID Maven Plugin", "5.0.1") { + library("Git Commit ID Maven Plugin", "6.0.0") { group("io.github.git-commit-id") { plugins = [ "git-commit-id-maven-plugin" From 9d8caf1133c9c10604dfb392944dde28a39960aa Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:15 +0100 Subject: [PATCH 0029/1656] Upgrade to Hazelcast 5.3.1 Closes gh-35950 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index f1f616cf793f..569a3727097e 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -357,7 +357,7 @@ bom { ] } } - library("Hazelcast", "5.2.4") { + library("Hazelcast", "5.3.1") { group("com.hazelcast") { modules = [ "hazelcast", From 152fc42d4e467365ac08226202c45d3df51fccf9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:20 +0100 Subject: [PATCH 0030/1656] Upgrade to Hibernate 6.2.5.Final Closes gh-35951 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 569a3727097e..9946b8d70fd9 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -365,7 +365,7 @@ bom { ] } } - library("Hibernate", "6.2.4.Final") { + library("Hibernate", "6.2.5.Final") { group("org.hibernate.orm") { modules = [ "hibernate-agroal", From fc269a5c698685ba53567a3699cf493beda953e0 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:25 +0100 Subject: [PATCH 0031/1656] Upgrade to Infinispan 14.0.11.Final Closes gh-35952 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 9946b8d70fd9..785264a311c3 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -453,7 +453,7 @@ bom { ] } } - library("Infinispan", "14.0.10.Final") { + library("Infinispan", "14.0.11.Final") { group("org.infinispan") { imports = [ "infinispan-bom" From dfe317ef81f14a8507ce092acd7daecf23a8a972 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:29 +0100 Subject: [PATCH 0032/1656] Upgrade to Jedis 4.4.3 Closes gh-35953 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 785264a311c3..f04b99c6d32c 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -645,7 +645,7 @@ bom { ] } } - library("Jedis", "4.3.2") { + library("Jedis", "4.4.3") { group("redis.clients") { modules = [ "jedis" From e82bd223a036fe0826bdaa95002a3b9145bca826 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:34 +0100 Subject: [PATCH 0033/1656] Upgrade to Kafka 3.5.0 Closes gh-35954 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index f04b99c6d32c..b649b4b62e8a 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -736,7 +736,7 @@ bom { ] } } - library("Kafka", "3.4.1") { + library("Kafka", "3.5.0") { group("org.apache.kafka") { modules = [ "connect", From fdc6f544052f068cb35e2a9ad80a07e84dd0075e Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:39 +0100 Subject: [PATCH 0034/1656] Upgrade to Kotlin Coroutines 1.7.1 Closes gh-35955 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index b649b4b62e8a..bb8f848edef7 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -787,7 +787,7 @@ bom { ] } } - library("Kotlin Coroutines", "1.6.4") { + library("Kotlin Coroutines", "1.7.1") { group("org.jetbrains.kotlinx") { imports = [ "kotlinx-coroutines-bom" From 1e17d8eeeac091d625b6853f0c517dcd9b218c65 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:44 +0100 Subject: [PATCH 0035/1656] Upgrade to Liquibase 4.22.0 Closes gh-35956 --- .../spring-boot-actuator/build.gradle | 1 + .../spring-boot-autoconfigure/build.gradle | 1 + .../flyway/FlywayAutoConfigurationTests.java | 16 +++++++--------- .../spring-boot-dependencies/build.gradle | 2 +- spring-boot-project/spring-boot/build.gradle | 1 + 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/build.gradle b/spring-boot-project/spring-boot-actuator/build.gradle index c4e393059178..3f7999ca49d4 100644 --- a/spring-boot-project/spring-boot-actuator/build.gradle +++ b/spring-boot-project/spring-boot-actuator/build.gradle @@ -50,6 +50,7 @@ dependencies { optional("org.hibernate.validator:hibernate-validator") optional("org.influxdb:influxdb-java") optional("org.liquibase:liquibase-core") { + exclude group: "javax.activation", module: "javax.activation-api" exclude(group: "javax.xml.bind", module: "jaxb-api") } optional("org.mongodb:mongodb-driver-reactivestreams") diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index db2a24fb5066..03e84c83456a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -130,6 +130,7 @@ dependencies { exclude group: "javax.xml.bind", module: "jaxb-api" } optional("org.liquibase:liquibase-core") { + exclude group: "javax.activation", module: "javax.activation-api" exclude group: "javax.xml.bind", module: "jaxb-api" } optional("org.messaginghub:pooled-jms") { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 8aa67ff18922..b2149465d8b2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -701,15 +701,13 @@ void outputQueryResultsIsCorrectlyMapped() { void sqlServerKerberosLoginFileIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.sql-server-kerberos-login-file=/tmp/config") - .run((context) -> { - assertThat(context.getBean(Flyway.class) - .getConfiguration() - .getPluginRegister() - .getPlugin(SQLServerConfigurationExtension.class) - .getKerberos() - .getLogin() - .getFile()).isEqualTo("/tmp/config"); - }); + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(SQLServerConfigurationExtension.class) + .getKerberos() + .getLogin() + .getFile()).isEqualTo("/tmp/config")); } @Test diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index bb8f848edef7..3cab5e9da2ae 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -803,7 +803,7 @@ bom { } library("Liquibase", "4.20.0") { prohibit { - versionRange "[4.21.0,4.21.2)" + versionRange "[4.21.0,4.22.0]" because "https://github.com/liquibase/liquibase/issues/4135" } group("org.liquibase") { diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index f0831197439b..3a862fe98b67 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -75,6 +75,7 @@ dependencies { exclude(group: "javax.xml.bind", module: "jaxb-api") } optional("org.liquibase:liquibase-core") { + exclude group: "javax.activation", module: "javax.activation-api" exclude(group: "javax.xml.bind", module: "jaxb-api") } optional("org.postgresql:postgresql") From 6745ec92240fc847d6eb3be5ce5fbcdc8e67a8e0 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:49 +0100 Subject: [PATCH 0036/1656] Upgrade to Maven Assembly Plugin 3.6.0 Closes gh-35957 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 3cab5e9da2ae..549c6d1a493d 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -853,7 +853,7 @@ bom { ] } } - library("Maven Assembly Plugin", "3.5.0") { + library("Maven Assembly Plugin", "3.6.0") { group("org.apache.maven.plugins") { plugins = [ "maven-assembly-plugin" From 559989a0adb81fa982f3ea829bd5722988cab872 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:55 +0100 Subject: [PATCH 0037/1656] Upgrade to Maven Dependency Plugin 3.6.0 Closes gh-35958 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 549c6d1a493d..217888ccde6a 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -874,7 +874,7 @@ bom { ] } } - library("Maven Dependency Plugin", "3.5.0") { + library("Maven Dependency Plugin", "3.6.0") { group("org.apache.maven.plugins") { plugins = [ "maven-dependency-plugin" From 3c482a024c7f95f0cf6f76097529707d3355dde2 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:55:59 +0100 Subject: [PATCH 0038/1656] Upgrade to Maven Failsafe Plugin 3.1.2 Closes gh-35959 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 217888ccde6a..1c0b3997017d 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -895,7 +895,7 @@ bom { ] } } - library("Maven Failsafe Plugin", "3.0.0") { + library("Maven Failsafe Plugin", "3.1.2") { group("org.apache.maven.plugins") { plugins = [ "maven-failsafe-plugin" From b167c6e45dce1b261be23f2ea6d5c77b401152d9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:05 +0100 Subject: [PATCH 0039/1656] Upgrade to Maven Invoker Plugin 3.6.0 Closes gh-35960 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 1c0b3997017d..eb43f034503c 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -916,7 +916,7 @@ bom { ] } } - library("Maven Invoker Plugin", "3.5.1") { + library("Maven Invoker Plugin", "3.6.0") { group("org.apache.maven.plugins") { plugins = [ "maven-invoker-plugin" From 853db91e3125d5c20c9d42b715d96902af53d880 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:10 +0100 Subject: [PATCH 0040/1656] Upgrade to Maven Shade Plugin 3.5.0 Closes gh-35961 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index eb43f034503c..973b2216a385 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -944,7 +944,7 @@ bom { ] } } - library("Maven Shade Plugin", "3.4.1") { + library("Maven Shade Plugin", "3.5.0") { group("org.apache.maven.plugins") { plugins = [ "maven-shade-plugin" From 0cac2e26036449cb89ea161a922fec4433ff8fe7 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:15 +0100 Subject: [PATCH 0041/1656] Upgrade to Maven Source Plugin 3.3.0 Closes gh-35962 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 973b2216a385..caebd11efcf4 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -951,7 +951,7 @@ bom { ] } } - library("Maven Source Plugin", "3.2.1") { + library("Maven Source Plugin", "3.3.0") { group("org.apache.maven.plugins") { plugins = [ "maven-source-plugin" From 045307994bb87797bd1e0acf3430b1c6915c1291 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:19 +0100 Subject: [PATCH 0042/1656] Upgrade to Maven Surefire Plugin 3.1.2 Closes gh-35963 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index caebd11efcf4..38c491b33c14 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -958,7 +958,7 @@ bom { ] } } - library("Maven Surefire Plugin", "3.0.0") { + library("Maven Surefire Plugin", "3.1.2") { group("org.apache.maven.plugins") { plugins = [ "maven-surefire-plugin" From a5df44cbcaac8faa28374af854f83f860b2bc15f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:24 +0100 Subject: [PATCH 0043/1656] Upgrade to Maven War Plugin 3.4.0 Closes gh-35964 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 38c491b33c14..f49779303979 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -965,7 +965,7 @@ bom { ] } } - library("Maven War Plugin", "3.3.2") { + library("Maven War Plugin", "3.4.0") { group("org.apache.maven.plugins") { plugins = [ "maven-war-plugin" From 9fb9d5518d2d9cb141458f70683bd3e8d12ecb64 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:29 +0100 Subject: [PATCH 0044/1656] Upgrade to Mockito 5.4.0 Closes gh-35965 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index f49779303979..4c7782f43887 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -991,7 +991,7 @@ bom { ] } } - library("Mockito", "5.3.1") { + library("Mockito", "5.4.0") { group("org.mockito") { imports = [ "mockito-bom" From d5fcfabb10cd5dc89b8f437d93b5e993c0389ddd Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:34 +0100 Subject: [PATCH 0045/1656] Upgrade to Native Build Tools Plugin 0.9.23 Closes gh-35966 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 946d095164b4..15ed1df1ccc8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 kotlinVersion=1.8.22 -nativeBuildToolsVersion=0.9.22 +nativeBuildToolsVersion=0.9.23 springFrameworkVersion=6.0.10 tomcatVersion=10.1.10 From fe1f675c43f412309a80c0eb1a3b26e8c82f16f8 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:39 +0100 Subject: [PATCH 0046/1656] Upgrade to Neo4j Java Driver 5.9.0 Closes gh-35967 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 4c7782f43887..682e6ccd77c5 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1044,7 +1044,7 @@ bom { ] } } - library("Neo4j Java Driver", "5.8.0") { + library("Neo4j Java Driver", "5.9.0") { group("org.neo4j.driver") { modules = [ "neo4j-java-driver" From 2ce6458cd491dbccb9b094da970739559315f423 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:44 +0100 Subject: [PATCH 0047/1656] Upgrade to OkHttp 4.11.0 Closes gh-35968 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 682e6ccd77c5..6f7a4de9f48e 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1058,7 +1058,7 @@ bom { ] } } - library("OkHttp", "4.10.0") { + library("OkHttp", "4.11.0") { group("com.squareup.okhttp3") { imports = [ "okhttp-bom" From be1eb32ac064b47e4457eb3c81268b8cf1b14eda Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:48 +0100 Subject: [PATCH 0048/1656] Upgrade to OpenTelemetry 1.27.0 Closes gh-35969 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 6f7a4de9f48e..c1298bbd5537 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1065,7 +1065,7 @@ bom { ] } } - library("OpenTelemetry", "1.25.0") { + library("OpenTelemetry", "1.27.0") { group("io.opentelemetry") { imports = [ "opentelemetry-bom" From e94f35f85a3016a2bab5c684f3bc5d43c164109b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:56:58 +0100 Subject: [PATCH 0049/1656] Upgrade to Rabbit Stream Client 0.10.0 Closes gh-35971 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index c1298bbd5537..5f71b6e91a9c 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1188,7 +1188,7 @@ bom { ] } } - library("Rabbit Stream Client", "0.9.0") { + library("Rabbit Stream Client", "0.10.0") { group("com.rabbitmq") { modules = [ "stream-client" From a63cf9dd7f6ea59599987e3be0b9ea2670914b4a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:57:03 +0100 Subject: [PATCH 0050/1656] Upgrade to REST Assured 5.3.1 Closes gh-35972 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 5f71b6e91a9c..be9428d7ede1 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1209,7 +1209,7 @@ bom { ] } } - library("REST Assured", "5.3.0") { + library("REST Assured", "5.3.1") { group("io.rest-assured") { imports = [ "rest-assured-bom" From 7053c3e0fcf8b84c8615a6f5419eb80ebbcb05b3 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:57:12 +0100 Subject: [PATCH 0051/1656] Upgrade to Selenium 4.10.0 Closes gh-35974 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index be9428d7ede1..d3c0aadaca16 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1316,7 +1316,7 @@ bom { ] } } - library("Selenium", "4.8.3") { + library("Selenium", "4.10.0") { group("org.seleniumhq.selenium") { modules = [ "lift", From 9ab94ef8a3321beff343dfa1f02894bb756c1403 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:57:17 +0100 Subject: [PATCH 0052/1656] Upgrade to Selenium HtmlUnit 4.10.0 Closes gh-35975 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index d3c0aadaca16..51f79be01606 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1344,7 +1344,7 @@ bom { ] } } - library("Selenium HtmlUnit", "4.8.3") { + library("Selenium HtmlUnit", "4.10.0") { group("org.seleniumhq.selenium") { modules = [ "htmlunit-driver" From 4dc0b26eeadb7407685dcad668217738a5af3379 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:57:22 +0100 Subject: [PATCH 0053/1656] Upgrade to Spring AMQP 3.0.5 Closes gh-35976 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 51f79be01606..c2af53dad5fa 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1382,7 +1382,7 @@ bom { ] } } - library("Spring AMQP", "3.0.5-SNAPSHOT") { + library("Spring AMQP", "3.0.5") { group("org.springframework.amqp") { modules = [ "spring-amqp", From ca5bd37e81929c340b0aca6e270decb0f7afb360 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:57:27 +0100 Subject: [PATCH 0054/1656] Upgrade to Spring Framework 6.1.0-M1 Closes gh-35977 Closes gh-35980 --- buildSrc/build.gradle | 9 +-- gradle.properties | 2 +- .../OrderedServerHttpObservationFilter.java | 1 + .../test/web/client/TestRestTemplate.java | 3 - .../context/SpringBootContextLoaderTests.java | 2 +- ...pringBootTestContextBootstrapperTests.java | 2 +- .../build.gradle | 5 ++ .../spring-boot-gradle-plugin/build.gradle | 5 ++ .../spring-boot-loader-tools/build.gradle | 11 ++++ .../client/ClientHttpRequestFactories.java | 11 ---- ...lientHttpRequestFactoriesRuntimeHints.java | 1 - .../ClientHttpRequestFactorySettings.java | 59 +++++++++++++------ .../boot/web/client/RestTemplateBuilder.java | 11 ++-- .../HttpWebServiceMessageSenderBuilder.java | 2 +- ...lientHttpRequestFactoriesOkHttp3Tests.java | 7 --- ...lientHttpRequestFactoriesOkHttp4Tests.java | 7 --- ...HttpRequestFactoriesRuntimeHintsTests.java | 9 --- .../ClientHttpRequestFactoriesTests.java | 41 ------------- ...ClientHttpRequestFactorySettingsTests.java | 14 ----- .../spring-boot-image-tests/build.gradle | 5 ++ 20 files changed, 78 insertions(+), 129 deletions(-) diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 2968c3e14e79..a0fa56fff9d6 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -14,15 +14,10 @@ new File(new File("$projectDir").parentFile, "gradle.properties").withInputStrea def properties = new Properties() properties.load(it) ext.set("kotlinVersion", properties["kotlinVersion"]) - ext.set("springFrameworkVersion", properties["springFrameworkVersion"]) - if (properties["springFrameworkVersion"].contains("-")) { - repositories { - maven { url "https://repo.spring.io/milestone" } - maven { url "https://repo.spring.io/snapshot" } - } - } } +ext.set("springFrameworkVersion", "6.0.10") + sourceCompatibility = 17 targetCompatibility = 17 diff --git a/gradle.properties b/gradle.properties index 15ed1df1ccc8..eb9bbe14aff8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 kotlinVersion=1.8.22 nativeBuildToolsVersion=0.9.23 -springFrameworkVersion=6.0.10 +springFrameworkVersion=6.1.0-M1 tomcatVersion=10.1.10 kotlin.stdlib.default.dependency=false diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java index 9d3146f1dbee..4541f6e16521 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java @@ -28,6 +28,7 @@ * * @author Moritz Halbritter */ +@SuppressWarnings({ "deprecation", "removal" }) class OrderedServerHttpObservationFilter extends ServerHttpObservationFilter implements OrderedWebFilter { private final int order; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java index dca0856b7089..24e38b8ab503 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java @@ -1021,9 +1021,6 @@ public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClien if (settings.connectTimeout() != null) { setConnectTimeout((int) settings.connectTimeout().toMillis()); } - if (settings.bufferRequestBody() != null) { - setBufferRequestBody(settings.bufferRequestBody()); - } } private HttpClient createHttpClient(Duration readTimeout, boolean ssl) { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java index 6715719eab48..5102e242d562 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java @@ -255,7 +255,7 @@ private String[] getActiveProfiles(Class testClass) { private Map getMergedContextConfigurationProperties(Class testClass) { TestContext context = new ExposedTestContextManager(testClass).getExposedTestContext(); MergedContextConfiguration config = (MergedContextConfiguration) ReflectionTestUtils.getField(context, - "mergedContextConfiguration"); + "mergedConfig"); return TestPropertySourceUtils.convertInlinedPropertiesToMap(config.getPropertySourceProperties()); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java index db724a374a1e..c38c81fc8121 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java @@ -110,7 +110,7 @@ private TestContext buildTestContext(Class testClass) { } private MergedContextConfiguration getMergedContextConfiguration(TestContext context) { - return (MergedContextConfiguration) ReflectionTestUtils.getField(context, "mergedContextConfiguration"); + return (MergedContextConfiguration) ReflectionTestUtils.getField(context, "mergedConfig"); } @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle index d890be6a2af9..8f5db820dfba 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/build.gradle @@ -14,6 +14,11 @@ configurations.all { if (dependency.requested.group.startsWith("com.fasterxml.jackson")) { dependency.useVersion("2.14.2") } + // Downgrade Spring Framework as Gradle cannot cope with 6.1.0-M1's + // multi-version jar files with bytecode in META-INF/versions/21 + if (dependency.requested.group.equals("org.springframework")) { + dependency.useVersion("6.0.10") + } } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle index 80b5f7e9402b..7939ef31bb98 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle @@ -23,6 +23,11 @@ configurations { if (dependency.requested.group.startsWith("com.fasterxml.jackson")) { dependency.useVersion("2.14.2") } + // Downgrade Spring Framework as Gradle cannot cope with 6.1.0-M1's + // multi-version jar files with bytecode in META-INF/versions/21 + if (dependency.requested.group.equals("org.springframework")) { + dependency.useVersion("6.0.10") + } } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle index c6b187317071..755f1cc7bc73 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle @@ -17,6 +17,17 @@ configurations { extendsFrom dependencyManagement transitive = false } + all { + resolutionStrategy { + eachDependency { dependency -> + // Downgrade Spring Framework as Gradle cannot cope with 6.1.0-M1's + // multi-version jar files with bytecode in META-INF/versions/21 + if (dependency.requested.group.equals("org.springframework")) { + dependency.useVersion("6.0.10") + } + } + } + } } dependencies { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java index 4f6de573f682..61b3e9361017 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java @@ -151,7 +151,6 @@ static HttpComponentsClientHttpRequestFactory get(ClientHttpRequestFactorySettin settings.sslBundle()); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout); - map.from(settings::bufferRequestBody).to(requestFactory::setBufferRequestBody); return requestFactory; } @@ -187,8 +186,6 @@ private static HttpClient createHttpClient(Duration readTimeout, SslBundle sslBu static class OkHttp { static OkHttp3ClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) { - Assert.state(settings.bufferRequestBody() == null, - () -> "OkHttp3ClientHttpRequestFactory does not support request body buffering"); OkHttp3ClientHttpRequestFactory requestFactory = createRequestFactory(settings.sslBundle()); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout); @@ -227,7 +224,6 @@ static SimpleClientHttpRequestFactory get(ClientHttpRequestFactorySettings setti PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout); map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout); - map.from(settings::bufferRequestBody).to(requestFactory::setBufferRequestBody); return requestFactory; } @@ -274,8 +270,6 @@ private static void configure(ClientHttpRequestFactory requestFactory, PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(settings::connectTimeout).to((connectTimeout) -> setConnectTimeout(unwrapped, connectTimeout)); map.from(settings::readTimeout).to((readTimeout) -> setReadTimeout(unwrapped, readTimeout)); - map.from(settings::bufferRequestBody) - .to((bufferRequestBody) -> setBufferRequestBody(unwrapped, bufferRequestBody)); } private static ClientHttpRequestFactory unwrapRequestFactoryIfNecessary( @@ -305,11 +299,6 @@ private static void setReadTimeout(ClientHttpRequestFactory factory, Duration re invoke(factory, method, timeout); } - private static void setBufferRequestBody(ClientHttpRequestFactory factory, boolean bufferRequestBody) { - Method method = findMethod(factory, "setBufferRequestBody", boolean.class); - invoke(factory, method, bufferRequestBody); - } - private static Method findMethod(ClientHttpRequestFactory requestFactory, String methodName, Class... parameters) { Method method = ReflectionUtils.findMethod(requestFactory.getClass(), methodName, parameters); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java index c47ef109a64c..457110d6c116 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java @@ -69,7 +69,6 @@ private void registerReflectionHints(ReflectionHints hints, Class requestFactoryType) { registerMethod(hints, requestFactoryType, "setConnectTimeout", int.class); registerMethod(hints, requestFactoryType, "setReadTimeout", int.class); - registerMethod(hints, requestFactoryType, "setBufferRequestBody", boolean.class); } private void registerMethod(ReflectionHints hints, Class requestFactoryType, diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettings.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettings.java index 22deb5a4a16b..204acffb933d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettings.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettings.java @@ -26,7 +26,6 @@ * * @param connectTimeout the connect timeout * @param readTimeout the read timeout - * @param bufferRequestBody if request body buffering is used * @param sslBundle the SSL bundle providing SSL configuration * @author Andy Wilkinson * @author Phillip Webb @@ -34,8 +33,7 @@ * @since 3.0.0 * @see ClientHttpRequestFactories */ -public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTimeout, Boolean bufferRequestBody, - SslBundle sslBundle) { +public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTimeout, SslBundle sslBundle) { /** * Use defaults for the {@link ClientHttpRequestFactory} which can differ depending on @@ -48,15 +46,29 @@ public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration * Create a new {@link ClientHttpRequestFactorySettings} instance. * @param connectTimeout the connection timeout * @param readTimeout the read timeout - * @param bufferRequestBody the bugger request body - * @param sslBundle the ssl bundle - * @since 3.1.0 + * @param bufferRequestBody if request body buffering is used + * @deprecated since 3.2.0 for removal in 3.4.0 as support for buffering has been + * removed in Spring Framework 6.1 */ - public ClientHttpRequestFactorySettings { + @Deprecated(since = "3.2.0", forRemoval = true) + public ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTimeout, Boolean bufferRequestBody) { + this(connectTimeout, readTimeout, (SslBundle) null); } - public ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTimeout, Boolean bufferRequestBody) { - this(connectTimeout, readTimeout, bufferRequestBody, null); + /** + * Create a new {@link ClientHttpRequestFactorySettings} instance. + * @param connectTimeout the connection timeout + * @param readTimeout the read timeout + * @param bufferRequestBody if request body buffering is used + * @param sslBundle the ssl bundle + * @since 3.1.0 + * @deprecated since 3.2.0 for removal in 3.4.0 as support for buffering has been + * removed in Spring Framework 6.1 + */ + @Deprecated(since = "3.2.0", forRemoval = true) + public ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTimeout, Boolean bufferRequestBody, + SslBundle sslBundle) { + this(connectTimeout, readTimeout, sslBundle); } /** @@ -66,8 +78,7 @@ public ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTi * @return a new {@link ClientHttpRequestFactorySettings} instance */ public ClientHttpRequestFactorySettings withConnectTimeout(Duration connectTimeout) { - return new ClientHttpRequestFactorySettings(connectTimeout, this.readTimeout, this.bufferRequestBody, - this.sslBundle); + return new ClientHttpRequestFactorySettings(connectTimeout, this.readTimeout, this.sslBundle); } /** @@ -78,19 +89,19 @@ public ClientHttpRequestFactorySettings withConnectTimeout(Duration connectTimeo */ public ClientHttpRequestFactorySettings withReadTimeout(Duration readTimeout) { - return new ClientHttpRequestFactorySettings(this.connectTimeout, readTimeout, this.bufferRequestBody, - this.sslBundle); + return new ClientHttpRequestFactorySettings(this.connectTimeout, readTimeout, this.sslBundle); } /** - * Return a new {@link ClientHttpRequestFactorySettings} instance with an updated - * buffer request body setting. + * Has no effect as support for buffering has been removed in Spring Framework 6.1. * @param bufferRequestBody the new buffer request body setting * @return a new {@link ClientHttpRequestFactorySettings} instance + * @deprecated since 3.2.0 for removal in 3.4.0 as support for buffering has been + * removed in Spring Framework 6.1 */ + @Deprecated(since = "3.2.0", forRemoval = true) public ClientHttpRequestFactorySettings withBufferRequestBody(Boolean bufferRequestBody) { - return new ClientHttpRequestFactorySettings(this.connectTimeout, this.readTimeout, bufferRequestBody, - this.sslBundle); + return this; } /** @@ -101,8 +112,18 @@ public ClientHttpRequestFactorySettings withBufferRequestBody(Boolean bufferRequ * @since 3.1.0 */ public ClientHttpRequestFactorySettings withSslBundle(SslBundle sslBundle) { - return new ClientHttpRequestFactorySettings(this.connectTimeout, this.readTimeout, this.bufferRequestBody, - sslBundle); + return new ClientHttpRequestFactorySettings(this.connectTimeout, this.readTimeout, sslBundle); + } + + /** + * Returns whether request body buffering is used. + * @return whether request body buffering is used + * @deprecated since 3.2.0 for removal in 3.4.0 as support for buffering has been + * removed in Spring Framework 6.1 + */ + @Deprecated(since = "3.2.0", forRemoval = true) + public Boolean bufferRequestBody() { + return null; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java index 9a981759cbf7..93e4e1b91840 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java @@ -440,19 +440,18 @@ public RestTemplateBuilder setReadTimeout(Duration readTimeout) { } /** - * Sets if the underlying {@link ClientHttpRequestFactory} should buffer the - * {@linkplain ClientHttpRequest#getBody() request body} internally. + * Has no effect as support for buffering has been removed in Spring Framework 6.1. * @param bufferRequestBody value of the bufferRequestBody parameter * @return a new builder instance. * @since 2.2.0 + * @deprecated since 3.2.0 for removal in 3.4.0 as support for buffering has been + * removed in Spring Framework 6.1 * @see SimpleClientHttpRequestFactory#setBufferRequestBody(boolean) * @see HttpComponentsClientHttpRequestFactory#setBufferRequestBody(boolean) */ + @Deprecated(since = "3.2.0", forRemoval = true) public RestTemplateBuilder setBufferRequestBody(boolean bufferRequestBody) { - return new RestTemplateBuilder(this.requestFactorySettings.withBufferRequestBody(bufferRequestBody), - this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory, - this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders, - this.customizers, this.requestCustomizers); + return this; } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java index 8f5dbb3d7c0e..5d25c93e601a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java @@ -113,7 +113,7 @@ public WebServiceMessageSender build() { private ClientHttpRequestFactory getRequestFactory() { ClientHttpRequestFactorySettings settings = new ClientHttpRequestFactorySettings(this.connectTimeout, - this.readTimeout, null, this.sslBundle); + this.readTimeout, this.sslBundle); return (this.requestFactory != null) ? this.requestFactory.apply(settings) : ClientHttpRequestFactories.get(settings); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java index 59e4c3144474..b54e8050d2e5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java @@ -27,7 +27,6 @@ import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link ClientHttpRequestFactories} when OkHttp 3 is the predominant HTTP @@ -50,12 +49,6 @@ void okHttp3IsBeingUsed() { .startsWith("okhttp-3."); } - @Test - void getFailsWhenBufferRequestBodyIsEnabled() { - assertThatIllegalStateException().isThrownBy(() -> ClientHttpRequestFactories - .get(ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true))); - } - @Override protected long connectTimeout(OkHttp3ClientHttpRequestFactory requestFactory) { return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).connectTimeoutMillis(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java index 13158708f54d..d1f533f73146 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java @@ -26,7 +26,6 @@ import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link ClientHttpRequestFactories} when OkHttp 4 is the predominant HTTP @@ -48,12 +47,6 @@ void okHttp4IsBeingUsed() { .startsWith("okhttp-4."); } - @Test - void getFailsWhenBufferRequestBodyIsEnabled() { - assertThatIllegalStateException().isThrownBy(() -> ClientHttpRequestFactories - .get(ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true))); - } - @Override protected long connectTimeout(OkHttp3ClientHttpRequestFactory requestFactory) { return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).connectTimeoutMillis(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java index bb143d3297bb..bbfdafccf580 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java @@ -59,12 +59,6 @@ void shouldRegisterHttpComponentHints() { assertThat(reflection .onMethod(method(HttpComponentsClientHttpRequestFactory.class, "setConnectTimeout", int.class))) .accepts(hints); - assertThat( - reflection.onMethod(method(HttpComponentsClientHttpRequestFactory.class, "setReadTimeout", int.class))) - .accepts(hints); - assertThat(reflection - .onMethod(method(HttpComponentsClientHttpRequestFactory.class, "setBufferRequestBody", boolean.class))) - .accepts(hints); } @Test @@ -88,9 +82,6 @@ void shouldRegisterSimpleHttpHints() { .accepts(hints); assertThat(reflection.onMethod(method(SimpleClientHttpRequestFactory.class, "setReadTimeout", int.class))) .accepts(hints); - assertThat(reflection - .onMethod(method(SimpleClientHttpRequestFactory.class, "setBufferRequestBody", boolean.class))) - .accepts(hints); } private static Method method(Class target, String name, Class... parameterTypes) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java index 34d591efb33d..546b862d987a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java @@ -100,14 +100,6 @@ void getOfUnknownTypeWithReadTimeoutCreatesFactoryAndConfiguresReadTimeout() { .isEqualTo(Duration.ofSeconds(90).toMillis()); } - @Test - void getOfUnknownTypeWithBodyBufferingCreatesFactoryAndConfiguresBodyBuffering() { - ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(TestClientHttpRequestFactory.class, - ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true)); - assertThat(requestFactory).isInstanceOf(TestClientHttpRequestFactory.class); - assertThat(((TestClientHttpRequestFactory) requestFactory).bufferRequestBody).isTrue(); - } - @Test void getOfUnconfigurableTypeWithConnectTimeoutThrows() { assertThatIllegalStateException() @@ -124,14 +116,6 @@ void getOfUnconfigurableTypeWithReadTimeoutThrows() { .withMessageContaining("suitable setReadTimeout method"); } - @Test - void getOfUnconfigurableTypeWithBodyBufferingThrows() { - assertThatIllegalStateException() - .isThrownBy(() -> ClientHttpRequestFactories.get(UnconfigurableClientHttpRequestFactory.class, - ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true))) - .withMessageContaining("suitable setBufferRequestBody method"); - } - @Test void getOfTypeWithDeprecatedConnectTimeoutThrowsWithConnectTimeout() { assertThatIllegalStateException() @@ -148,14 +132,6 @@ void getOfTypeWithDeprecatedReadTimeoutThrowsWithReadTimeout() { .withMessageContaining("setReadTimeout method marked as deprecated"); } - @Test - void getOfTypeWithDeprecatedBufferRequestBodyThrowsWithBufferRequestBody() { - assertThatIllegalStateException() - .isThrownBy(() -> ClientHttpRequestFactories.get(DeprecatedMethodsClientHttpRequestFactory.class, - ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(false))) - .withMessageContaining("setBufferRequestBody method marked as deprecated"); - } - @Test void connectTimeoutCanBeConfiguredOnAWrappedRequestFactory() { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); @@ -176,25 +152,12 @@ void readTimeoutCanBeConfiguredOnAWrappedRequestFactory() { assertThat(requestFactory).hasFieldOrPropertyWithValue("readTimeout", 1234); } - @Test - void bufferRequestBodyCanBeConfiguredOnAWrappedRequestFactory() { - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); - assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true); - BufferingClientHttpRequestFactory result = ClientHttpRequestFactories.get( - () -> new BufferingClientHttpRequestFactory(requestFactory), - ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(false)); - assertThat(result).extracting("requestFactory").isSameAs(requestFactory); - assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", false); - } - public static class TestClientHttpRequestFactory implements ClientHttpRequestFactory { private int connectTimeout; private int readTimeout; - private boolean bufferRequestBody; - @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { throw new UnsupportedOperationException(); @@ -208,10 +171,6 @@ public void setReadTimeout(int timeout) { this.readTimeout = timeout; } - public void setBufferRequestBody(boolean bufferRequestBody) { - this.bufferRequestBody = bufferRequestBody; - } - } public static class UnconfigurableClientHttpRequestFactory implements ClientHttpRequestFactory { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettingsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettingsTests.java index 8103a3ae6973..c9145b7dd266 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettingsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactorySettingsTests.java @@ -39,7 +39,6 @@ void defaultsHasNullValues() { ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS; assertThat(settings.connectTimeout()).isNull(); assertThat(settings.readTimeout()).isNull(); - assertThat(settings.bufferRequestBody()).isNull(); assertThat(settings.sslBundle()).isNull(); } @@ -49,7 +48,6 @@ void withConnectTimeoutReturnsInstanceWithUpdatedConnectionTimeout() { .withConnectTimeout(ONE_SECOND); assertThat(settings.connectTimeout()).isEqualTo(ONE_SECOND); assertThat(settings.readTimeout()).isNull(); - assertThat(settings.bufferRequestBody()).isNull(); assertThat(settings.sslBundle()).isNull(); } @@ -59,17 +57,6 @@ void withReadTimeoutReturnsInstanceWithUpdatedReadTimeout() { .withReadTimeout(ONE_SECOND); assertThat(settings.connectTimeout()).isNull(); assertThat(settings.readTimeout()).isEqualTo(ONE_SECOND); - assertThat(settings.bufferRequestBody()).isNull(); - assertThat(settings.sslBundle()).isNull(); - } - - @Test - void withBufferRequestBodyReturnsInstanceWithUpdatedBufferRequestBody() { - ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS - .withBufferRequestBody(true); - assertThat(settings.connectTimeout()).isNull(); - assertThat(settings.readTimeout()).isNull(); - assertThat(settings.bufferRequestBody()).isTrue(); assertThat(settings.sslBundle()).isNull(); } @@ -79,7 +66,6 @@ void withSslBundleReturnsInstanceWithUpdatedSslBundle() { ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(sslBundle); assertThat(settings.connectTimeout()).isNull(); assertThat(settings.readTimeout()).isNull(); - assertThat(settings.bufferRequestBody()).isNull(); assertThat(settings.sslBundle()).isSameAs(sslBundle); } diff --git a/spring-boot-system-tests/spring-boot-image-tests/build.gradle b/spring-boot-system-tests/spring-boot-image-tests/build.gradle index 9de3b9c9fbfe..e4236f1b82fc 100644 --- a/spring-boot-system-tests/spring-boot-image-tests/build.gradle +++ b/spring-boot-system-tests/spring-boot-image-tests/build.gradle @@ -19,6 +19,11 @@ configurations { if (dependency.requested.group.startsWith("com.fasterxml.jackson")) { dependency.useVersion("2.14.2") } + // Downgrade Spring Framework as Gradle cannot cope with 6.1.0-M1's + // multi-version jar files with bytecode in META-INF/versions/21 + if (dependency.requested.group.equals("org.springframework")) { + dependency.useVersion("6.0.10") + } } } } From 5cd18a05fc1ebf204a52c44ec8e9464809f11079 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:57:32 +0100 Subject: [PATCH 0055/1656] Upgrade to SQLite JDBC 3.42.0.0 Closes gh-35978 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index c2af53dad5fa..f32eba420cf9 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1500,7 +1500,7 @@ bom { ] } } - library("SQLite JDBC", "3.41.2.2") { + library("SQLite JDBC", "3.42.0.0") { group("org.xerial") { modules = [ "sqlite-jdbc" From 962445ea6c244b9a241a1a60662baead19888232 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 09:57:37 +0100 Subject: [PATCH 0056/1656] Upgrade to Versions Maven Plugin 2.16.0 Closes gh-35979 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index f32eba420cf9..775aeaa90234 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1576,7 +1576,7 @@ bom { ] } } - library("Versions Maven Plugin", "2.15.0") { + library("Versions Maven Plugin", "2.16.0") { group("org.codehaus.mojo") { plugins = [ "versions-maven-plugin" From a94ac2fb4465d46db3dbb4e4014dd04c04738d52 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 12:16:44 +0100 Subject: [PATCH 0057/1656] Upgrade to Rabbit AMQP Client 5.18.0 Closes gh-35981 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 775aeaa90234..133b97fcad5e 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1181,7 +1181,7 @@ bom { ] } } - library("Rabbit AMQP Client", "5.17.0") { + library("Rabbit AMQP Client", "5.18.0") { group("com.rabbitmq") { modules = [ "amqp-client" From 1f9ce508f7f996bcba8d7707a06ab0210ec5089a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 12:16:49 +0100 Subject: [PATCH 0058/1656] Upgrade to SnakeYAML 2.0 Closes gh-35982 --- .../spring-boot-dependencies/build.gradle | 2 +- .../spring-boot-docs/build.gradle | 11 +++++++ .../env/OriginTrackedYamlLoaderTests.java | 4 +-- ...lPropertySourceLoaderSnakeYaml20Tests.java | 29 ------------------- 4 files changed, 14 insertions(+), 32 deletions(-) delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml20Tests.java diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 133b97fcad5e..994d576ea33a 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1375,7 +1375,7 @@ bom { ] } } - library("SnakeYAML", "1.33") { + library("SnakeYAML", "2.0") { group("org.yaml") { modules = [ "snakeyaml" diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 583522f59050..3d0d6ddcd8a5 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -17,6 +17,17 @@ configurations { remoteSpringApplicationExample springApplicationExample testSlices + asciidoctorExtensions { + resolutionStrategy { + eachDependency { dependency -> + // Downgrade SnakeYAML as Asciidoctor fails due to an incompatibility + // in the Pysch gem + if (dependency.requested.group.equals("org.yaml")) { + dependency.useVersion("1.33") + } + } + } + } } jar { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java index b51d41a9cdfe..ce5fa86434b1 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.yaml.snakeyaml.constructor.ConstructorException; +import org.yaml.snakeyaml.composer.ComposerException; import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.boot.origin.TextResourceOrigin; @@ -134,7 +134,7 @@ void unsupportedType() { String yaml = "value: !!java.net.URL [!!java.lang.String [!!java.lang.StringBuilder [\"http://localhost:9000/\"]]]"; Resource resource = new ByteArrayResource(yaml.getBytes(StandardCharsets.UTF_8)); this.loader = new OriginTrackedYamlLoader(resource); - assertThatExceptionOfType(ConstructorException.class).isThrownBy(this.loader::load); + assertThatExceptionOfType(ComposerException.class).isThrownBy(this.loader::load); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml20Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml20Tests.java deleted file mode 100644 index 684440176523..000000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderSnakeYaml20Tests.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2012-2023 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.env; - -import org.springframework.boot.testsupport.classpath.ClassPathOverrides; - -/** - * Tests for {@link YamlPropertySourceLoader} with SnakeYAML 2.0. - * - * @author Andy Wilkinson - */ -@ClassPathOverrides("org.yaml:snakeyaml:2.0") -class YamlPropertySourceLoaderSnakeYaml20Tests extends YamlPropertySourceLoaderTests { - -} From d9aac063a2cc764c1d6e569c02dc2c7efebc8353 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 19 Jun 2023 15:30:02 +0100 Subject: [PATCH 0059/1656] Prohibit upgrades to Oracle Database 23.2.0.0 Closes gh-35970 --- spring-boot-project/spring-boot-dependencies/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 994d576ea33a..e5569d72ef91 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1073,6 +1073,9 @@ bom { } } library("Oracle Database", "21.9.0.0") { + prohibit { + versionRange "23.2.0.0" + } group("com.oracle.database.jdbc") { imports = [ "ojdbc-bom" From b6120d504a1fffbbfd7a39b912b57b3407af5824 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 21 Jun 2023 19:33:12 -0700 Subject: [PATCH 0060/1656] Replace LoggingSystemProperties constants with an Enum Extract contants from `LoggingSystemProperty` and `LogbackLoggingSystemProperties` in enum classes. Closes gh-36015 --- .../springframework/boot/logging/LogFile.java | 10 +- .../boot/logging/LoggingSystemProperties.java | 146 ++++++++++++++---- .../boot/logging/LoggingSystemProperty.java | 113 ++++++++++++++ .../boot/logging/java/SimpleFormatter.java | 6 +- .../LogbackLoggingSystemProperties.java | 70 +++++---- .../logback/RollingPolicySystemProperty.java | 82 ++++++++++ ...ngApplicationListenerIntegrationTests.java | 4 +- .../LoggingApplicationListenerTests.java | 27 ++-- .../logging/AbstractLoggingSystemTests.java | 7 +- .../boot/logging/LogFileTests.java | 16 +- .../logging/LoggingSystemPropertiesTests.java | 27 ++-- .../logging/java/JavaLoggingSystemTests.java | 4 +- .../log4j2/Log4J2LoggingSystemTests.java | 6 +- .../logging/log4j2/Log4j2FileXmlTests.java | 17 +- .../boot/logging/log4j2/Log4j2XmlTests.java | 8 +- .../LogbackLoggingSystemPropertiesTests.java | 33 ++-- .../logback/LogbackLoggingSystemTests.java | 13 +- 17 files changed, 457 insertions(+), 132 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/RollingPolicySystemProperty.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java index b973bc90019d..a1a201202e72 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -86,13 +86,13 @@ public void applyToSystemProperties() { * @param properties the properties to apply to */ public void applyTo(Properties properties) { - put(properties, LoggingSystemProperties.LOG_PATH, this.path); - put(properties, LoggingSystemProperties.LOG_FILE, toString()); + put(properties, LoggingSystemProperty.LOG_PATH, this.path); + put(properties, LoggingSystemProperty.LOG_FILE, toString()); } - private void put(Properties properties, String key, String value) { + private void put(Properties properties, LoggingSystemProperty property, String value) { if (StringUtils.hasLength(value)) { - properties.put(key, value); + properties.put(property.getEnvironmentVariableName(), value); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java index ff4c6c7eebe1..456bb010d075 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java @@ -37,68 +37,120 @@ * @author Robert Thornton * @author Eddú Meléndez * @since 2.0.0 + * @see LoggingSystemProperty */ public class LoggingSystemProperties { /** * The name of the System property that contains the process ID. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#PID} */ - public static final String PID_KEY = "PID"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String PID_KEY = LoggingSystemProperty.PID.getEnvironmentVariableName(); /** * The name of the System property that contains the exception conversion word. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#EXCEPTION_CONVERSION_WORD} */ - public static final String EXCEPTION_CONVERSION_WORD = "LOG_EXCEPTION_CONVERSION_WORD"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String EXCEPTION_CONVERSION_WORD = LoggingSystemProperty.EXCEPTION_CONVERSION_WORD + .getEnvironmentVariableName(); /** * The name of the System property that contains the log file. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#LOG_FILE} */ - public static final String LOG_FILE = "LOG_FILE"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String LOG_FILE = LoggingSystemProperty.LOG_FILE.getEnvironmentVariableName(); /** * The name of the System property that contains the log path. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#LOG_PATH} */ - public static final String LOG_PATH = "LOG_PATH"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String LOG_PATH = LoggingSystemProperty.LOG_PATH.getEnvironmentVariableName(); /** * The name of the System property that contains the console log pattern. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#CONSOLE_PATTERN} */ - public static final String CONSOLE_LOG_PATTERN = "CONSOLE_LOG_PATTERN"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String CONSOLE_LOG_PATTERN = LoggingSystemProperty.CONSOLE_PATTERN.getEnvironmentVariableName(); /** * The name of the System property that contains the console log charset. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#CONSOLE_CHARSET} */ - public static final String CONSOLE_LOG_CHARSET = "CONSOLE_LOG_CHARSET"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String CONSOLE_LOG_CHARSET = LoggingSystemProperty.CONSOLE_CHARSET.getEnvironmentVariableName(); /** * The log level threshold for console log. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#CONSOLE_THRESHOLD} */ - public static final String CONSOLE_LOG_THRESHOLD = "CONSOLE_LOG_THRESHOLD"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String CONSOLE_LOG_THRESHOLD = LoggingSystemProperty.CONSOLE_THRESHOLD + .getEnvironmentVariableName(); /** * The name of the System property that contains the file log pattern. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#FILE_PATTERN} */ - public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String FILE_LOG_PATTERN = LoggingSystemProperty.FILE_PATTERN.getEnvironmentVariableName(); /** * The name of the System property that contains the file log charset. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#FILE_CHARSET} */ - public static final String FILE_LOG_CHARSET = "FILE_LOG_CHARSET"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String FILE_LOG_CHARSET = LoggingSystemProperty.FILE_CHARSET.getEnvironmentVariableName(); /** * The log level threshold for file log. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#FILE_THRESHOLD} */ - public static final String FILE_LOG_THRESHOLD = "FILE_LOG_THRESHOLD"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String FILE_LOG_THRESHOLD = LoggingSystemProperty.FILE_THRESHOLD.getEnvironmentVariableName(); /** * The name of the System property that contains the log level pattern. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#LEVEL_PATTERN} */ - public static final String LOG_LEVEL_PATTERN = "LOG_LEVEL_PATTERN"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String LOG_LEVEL_PATTERN = LoggingSystemProperty.LEVEL_PATTERN.getEnvironmentVariableName(); /** * The name of the System property that contains the log date-format pattern. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link LoggingSystemProperty#getEnvironmentVariableName()} on + * {@link LoggingSystemProperty#DATEFORMAT_PATTERN} */ - public static final String LOG_DATEFORMAT_PATTERN = "LOG_DATEFORMAT_PATTERN"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String LOG_DATEFORMAT_PATTERN = LoggingSystemProperty.DATEFORMAT_PATTERN + .getEnvironmentVariableName(); private static final BiConsumer systemPropertySetter = (name, value) -> { if (System.getProperty(name) == null && value != null) { @@ -144,22 +196,6 @@ public final void apply(LogFile logFile) { apply(logFile, resolver); } - protected void apply(LogFile logFile, PropertyResolver resolver) { - setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "logging.exception-conversion-word"); - setSystemProperty(PID_KEY, new ApplicationPid().toString()); - setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "logging.pattern.console"); - setSystemProperty(resolver, CONSOLE_LOG_CHARSET, "logging.charset.console", getDefaultCharset().name()); - setSystemProperty(resolver, CONSOLE_LOG_THRESHOLD, "logging.threshold.console"); - setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "logging.pattern.dateformat"); - setSystemProperty(resolver, FILE_LOG_PATTERN, "logging.pattern.file"); - setSystemProperty(resolver, FILE_LOG_CHARSET, "logging.charset.file", getDefaultCharset().name()); - setSystemProperty(resolver, FILE_LOG_THRESHOLD, "logging.threshold.file"); - setSystemProperty(resolver, LOG_LEVEL_PATTERN, "logging.pattern.level"); - if (logFile != null) { - logFile.applyToSystemProperties(); - } - } - private PropertyResolver getPropertyResolver() { if (this.environment instanceof ConfigurableEnvironment configurableEnvironment) { PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver( @@ -171,10 +207,59 @@ private PropertyResolver getPropertyResolver() { return this.environment; } + protected void apply(LogFile logFile, PropertyResolver resolver) { + String defaultCharsetName = getDefaultCharset().name(); + setSystemProperty(LoggingSystemProperty.PID, new ApplicationPid().toString()); + setSystemProperty(LoggingSystemProperty.CONSOLE_CHARSET, resolver, defaultCharsetName); + setSystemProperty(LoggingSystemProperty.FILE_CHARSET, resolver, defaultCharsetName); + setSystemProperty(LoggingSystemProperty.CONSOLE_THRESHOLD, resolver); + setSystemProperty(LoggingSystemProperty.FILE_THRESHOLD, resolver); + setSystemProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD, resolver); + setSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN, resolver); + setSystemProperty(LoggingSystemProperty.FILE_PATTERN, resolver); + setSystemProperty(LoggingSystemProperty.LEVEL_PATTERN, resolver); + setSystemProperty(LoggingSystemProperty.DATEFORMAT_PATTERN, resolver); + if (logFile != null) { + logFile.applyToSystemProperties(); + } + } + + private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver) { + setSystemProperty(property, resolver, null); + } + + private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver, String defaultValue) { + String value = (property.getApplicationPropertyName() != null) + ? resolver.getProperty(property.getApplicationPropertyName()) : null; + value = (value != null) ? value : defaultValue; + setSystemProperty(property.getEnvironmentVariableName(), value); + } + + private void setSystemProperty(LoggingSystemProperty property, String value) { + setSystemProperty(property.getEnvironmentVariableName(), value); + } + + /** + * Set a system property. + * @param resolver the resolver used to get the property value + * @param systemPropertyName the system property name + * @param propertyName the application property name + * @deprecated since 3.2.0 for removal in 3.4.0 with no replacement + */ + @Deprecated(since = "3.2.0", forRemoval = true) protected final void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName) { setSystemProperty(resolver, systemPropertyName, propertyName, null); } + /** + * Set a system property. + * @param resolver the resolver used to get the property value + * @param systemPropertyName the system property name + * @param propertyName the application property name + * @param defaultValue the default value if none can be resolved + * @deprecated since 3.2.0 for removal in 3.4.0 with no replacement + */ + @Deprecated(since = "3.2.0", forRemoval = true) protected final void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName, String defaultValue) { String value = resolver.getProperty(propertyName); @@ -182,6 +267,11 @@ protected final void setSystemProperty(PropertyResolver resolver, String systemP setSystemProperty(systemPropertyName, value); } + /** + * Set a system property. + * @param name the property name + * @param value the value + */ protected final void setSystemProperty(String name, String value) { this.setter.accept(name, value); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java new file mode 100644 index 000000000000..d6a731453189 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java @@ -0,0 +1,113 @@ +/* + * Copyright 2012-2023 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.logging; + +/** + * Logging system properties that can later be used by log configuration files. + * + * @author Phillip Webb + * @since 3.2.0 + * @see LoggingSystemProperties + */ +public enum LoggingSystemProperty { + + /** + * Logging system property for the process ID. + */ + PID("PID"), + + /** + * Logging system property for the log file. + */ + LOG_FILE("LOG_FILE"), + + /** + * Logging system property for the log path. + */ + LOG_PATH("LOG_PATH"), + + /** + * Logging system property for the console log charset. + */ + CONSOLE_CHARSET("CONSOLE_LOG_CHARSET", "logging.charset.console"), + + /** + * Logging system property for the file log charset. + */ + FILE_CHARSET("FILE_LOG_CHARSET", "logging.charset.file"), + + /** + * Logging system property for the console log. + */ + CONSOLE_THRESHOLD("CONSOLE_LOG_THRESHOLD", "logging.threshold.console"), + + /** + * Logging system property for the file log. + */ + FILE_THRESHOLD("FILE_LOG_THRESHOLD", "logging.threshold.file"), + + /** + * Logging system property for the exception conversion word. + */ + EXCEPTION_CONVERSION_WORD("LOG_EXCEPTION_CONVERSION_WORD", "logging.exception-conversion-word"), + + /** + * Logging system property for the console log pattern. + */ + CONSOLE_PATTERN("CONSOLE_LOG_PATTERN", "logging.pattern.console"), + + /** + * Logging system property for the file log pattern. + */ + FILE_PATTERN("FILE_LOG_PATTERN", "logging.pattern.file"), + + /** + * Logging system property for the log level pattern. + */ + LEVEL_PATTERN("LOG_LEVEL_PATTERN", "logging.pattern.level"), + + /** + * Logging system property for the date-format pattern. + */ + DATEFORMAT_PATTERN("LOG_DATEFORMAT_PATTERN", "logging.pattern.dateformat"); + + private final String environmentVariableName; + + private final String applicationPropertyName; + + LoggingSystemProperty(String environmentVariableName) { + this(environmentVariableName, null); + } + + LoggingSystemProperty(String environmentVariableName, String applicationPropertyName) { + this.environmentVariableName = environmentVariableName; + this.applicationPropertyName = applicationPropertyName; + } + + /** + * Return the name of environment variable that can be used to access this property. + * @return the environment variable name + */ + public String getEnvironmentVariableName() { + return this.environmentVariableName; + } + + String getApplicationPropertyName() { + return this.applicationPropertyName; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/java/SimpleFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/java/SimpleFormatter.java index f92e5eae0edb..1701a6a03a23 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/java/SimpleFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/java/SimpleFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -22,7 +22,7 @@ import java.util.logging.Formatter; import java.util.logging.LogRecord; -import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; /** * Simple 'Java Logging' {@link Formatter}. @@ -36,7 +36,7 @@ public class SimpleFormatter extends Formatter { private final String format = getOrUseDefault("LOG_FORMAT", DEFAULT_FORMAT); - private final String pid = getOrUseDefault(LoggingSystemProperties.PID_KEY, "????"); + private final String pid = getOrUseDefault(LoggingSystemProperty.PID.getEnvironmentVariableName(), "????"); private final Date date = new Date(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java index f9d97937d757..3b7b715e1615 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -35,6 +35,7 @@ * * @author Phillip Webb * @since 2.4.0 + * @see RollingPolicySystemProperty */ public class LogbackLoggingSystemProperties extends LoggingSystemProperties { @@ -44,28 +45,53 @@ public class LogbackLoggingSystemProperties extends LoggingSystemProperties { /** * The name of the System property that contains the rolled-over log file name * pattern. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link RollingPolicySystemProperty#getEnvironmentVariableName()} on + * {@link RollingPolicySystemProperty#FILE_NAME_PATTERN} */ - public static final String ROLLINGPOLICY_FILE_NAME_PATTERN = "LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String ROLLINGPOLICY_FILE_NAME_PATTERN = RollingPolicySystemProperty.FILE_NAME_PATTERN + .getEnvironmentVariableName(); /** * The name of the System property that contains the clean history on start flag. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link RollingPolicySystemProperty#getEnvironmentVariableName()} on + * {@link RollingPolicySystemProperty#CLEAN_HISTORY_ON_START} */ - public static final String ROLLINGPOLICY_CLEAN_HISTORY_ON_START = "LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String ROLLINGPOLICY_CLEAN_HISTORY_ON_START = RollingPolicySystemProperty.CLEAN_HISTORY_ON_START + .getEnvironmentVariableName(); /** * The name of the System property that contains the file log max size. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link RollingPolicySystemProperty#getEnvironmentVariableName()} on + * {@link RollingPolicySystemProperty#MAX_FILE_SIZE} */ - public static final String ROLLINGPOLICY_MAX_FILE_SIZE = "LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String ROLLINGPOLICY_MAX_FILE_SIZE = RollingPolicySystemProperty.MAX_FILE_SIZE + .getEnvironmentVariableName(); /** * The name of the System property that contains the file total size cap. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link RollingPolicySystemProperty#getEnvironmentVariableName()} on + * {@link RollingPolicySystemProperty#TOTAL_SIZE_CAP} */ - public static final String ROLLINGPOLICY_TOTAL_SIZE_CAP = "LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String ROLLINGPOLICY_TOTAL_SIZE_CAP = RollingPolicySystemProperty.TOTAL_SIZE_CAP + .getEnvironmentVariableName(); /** * The name of the System property that contains the file log max history. + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of calling + * {@link RollingPolicySystemProperty#getEnvironmentVariableName()} on + * {@link RollingPolicySystemProperty#MAX_HISTORY} */ - public static final String ROLLINGPOLICY_MAX_HISTORY = "LOGBACK_ROLLINGPOLICY_MAX_HISTORY"; + @Deprecated(since = "3.2.0", forRemoval = true) + public static final String ROLLINGPOLICY_MAX_HISTORY = RollingPolicySystemProperty.MAX_HISTORY + .getEnvironmentVariableName(); public LogbackLoggingSystemProperties(Environment environment) { super(environment); @@ -100,32 +126,24 @@ private void applyJBossLoggingProperties() { } private void applyRollingPolicyProperties(PropertyResolver resolver) { - applyRollingPolicy(resolver, ROLLINGPOLICY_FILE_NAME_PATTERN, "logging.logback.rollingpolicy.file-name-pattern", - "logging.pattern.rolling-file-name"); - applyRollingPolicy(resolver, ROLLINGPOLICY_CLEAN_HISTORY_ON_START, - "logging.logback.rollingpolicy.clean-history-on-start", "logging.file.clean-history-on-start"); - applyRollingPolicy(resolver, ROLLINGPOLICY_MAX_FILE_SIZE, "logging.logback.rollingpolicy.max-file-size", - "logging.file.max-size", DataSize.class); - applyRollingPolicy(resolver, ROLLINGPOLICY_TOTAL_SIZE_CAP, "logging.logback.rollingpolicy.total-size-cap", - "logging.file.total-size-cap", DataSize.class); - applyRollingPolicy(resolver, ROLLINGPOLICY_MAX_HISTORY, "logging.logback.rollingpolicy.max-history", - "logging.file.max-history"); + applyRollingPolicy(RollingPolicySystemProperty.FILE_NAME_PATTERN, resolver); + applyRollingPolicy(RollingPolicySystemProperty.CLEAN_HISTORY_ON_START, resolver); + applyRollingPolicy(RollingPolicySystemProperty.MAX_FILE_SIZE, resolver, DataSize.class); + applyRollingPolicy(RollingPolicySystemProperty.TOTAL_SIZE_CAP, resolver, DataSize.class); + applyRollingPolicy(RollingPolicySystemProperty.MAX_HISTORY, resolver); } - private void applyRollingPolicy(PropertyResolver resolver, String systemPropertyName, String propertyName, - String deprecatedPropertyName) { - applyRollingPolicy(resolver, systemPropertyName, propertyName, deprecatedPropertyName, String.class); + private void applyRollingPolicy(RollingPolicySystemProperty property, PropertyResolver resolver) { + applyRollingPolicy(property, resolver, String.class); } - private void applyRollingPolicy(PropertyResolver resolver, String systemPropertyName, String propertyName, - String deprecatedPropertyName, Class type) { - T value = getProperty(resolver, propertyName, type); - if (value == null) { - value = getProperty(resolver, deprecatedPropertyName, type); - } + private void applyRollingPolicy(RollingPolicySystemProperty property, PropertyResolver resolver, + Class type) { + T value = getProperty(resolver, property.getApplicationPropertyName(), type); + value = (value != null) ? value : getProperty(resolver, property.getDeprecatedApplicationPropertyName(), type); if (value != null) { String stringValue = String.valueOf((value instanceof DataSize dataSize) ? dataSize.toBytes() : value); - setSystemProperty(systemPropertyName, stringValue); + setSystemProperty(property.getEnvironmentVariableName(), stringValue); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/RollingPolicySystemProperty.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/RollingPolicySystemProperty.java new file mode 100644 index 000000000000..f75db8f2386f --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/RollingPolicySystemProperty.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2023 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.logging.logback; + +/** + * Logback rolling policy system properties that can later be used by log configuration + * files. + * + * @author Phillip Webb + * @since 3.2.0 + * @see LogbackLoggingSystemProperties + */ +public enum RollingPolicySystemProperty { + + /** + * Logging system property for the rolled-over log file name pattern. + */ + FILE_NAME_PATTERN("file-name-pattern", "logging.pattern.rolling-file-name"), + + /** + * Logging system property for the clean history on start flag. + */ + CLEAN_HISTORY_ON_START("clean-history-on-start", "logging.file.clean-history-on-start"), + + /** + * Logging system property for the file log max size. + */ + MAX_FILE_SIZE("max-file-size", "logging.file.max-size"), + + /** + * Logging system property for the file total size cap. + */ + TOTAL_SIZE_CAP("total-size-cap", "logging.file.total-size-cap"), + + /** + * Logging system property for the file log max history. + */ + MAX_HISTORY("max-history", "logging.file.max-history"); + + private final String environmentVariableName; + + private final String applicationPropertyName; + + private final String deprecatedApplicationPropertyName; + + RollingPolicySystemProperty(String applicationPropertyName, String deprecatedApplicationPropertyName) { + this.environmentVariableName = "LOGBACK_ROLLINGPOLICY_" + name(); + this.applicationPropertyName = "logging.logback.rollingpolicy." + applicationPropertyName; + this.deprecatedApplicationPropertyName = deprecatedApplicationPropertyName; + } + + /** + * Return the name of environment variable that can be used to access this property. + * @return the environment variable name + */ + public String getEnvironmentVariableName() { + return this.environmentVariableName; + } + + String getApplicationPropertyName() { + return this.applicationPropertyName; + } + + String getDeprecatedApplicationPropertyName() { + return this.deprecatedApplicationPropertyName; + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerIntegrationTests.java index 398bc8298296..d7c8b12a4901 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerIntegrationTests.java @@ -30,7 +30,7 @@ import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.context.ApplicationListener; @@ -69,7 +69,7 @@ void logFileRegisteredInTheContextWhenApplicable(@TempDir File tempDir) { assertThat(service.logFile).hasToString(logFile); } finally { - System.clearProperty(LoggingSystemProperties.LOG_FILE); + System.clearProperty(LoggingSystemProperty.LOG_FILE.getEnvironmentVariableName()); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java index d08311428194..4bfeac363066 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java @@ -58,7 +58,7 @@ import org.springframework.boot.logging.LoggerGroups; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.boot.logging.java.JavaLoggingSystem; import org.springframework.boot.system.ApplicationPid; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; @@ -470,13 +470,13 @@ void systemPropertiesAreSetForLoggingConfiguration() { "logging.pattern.file=file", "logging.pattern.level=level", "logging.pattern.rolling-file-name=my.log.%d{yyyyMMdd}.%i.gz"); this.listener.initialize(this.context.getEnvironment(), this.context.getClassLoader()); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)).isEqualTo("console"); - assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_PATTERN)).isEqualTo("file"); - assertThat(System.getProperty(LoggingSystemProperties.EXCEPTION_CONVERSION_WORD)).isEqualTo("conversion"); - assertThat(System.getProperty(LoggingSystemProperties.LOG_FILE)).isEqualTo(this.logFile.getAbsolutePath()); - assertThat(System.getProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN)).isEqualTo("level"); - assertThat(System.getProperty(LoggingSystemProperties.LOG_PATH)).isEqualTo("path"); - assertThat(System.getProperty(LoggingSystemProperties.PID_KEY)).isNotNull(); + assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN)).isEqualTo("console"); + assertThat(getSystemProperty(LoggingSystemProperty.FILE_PATTERN)).isEqualTo("file"); + assertThat(getSystemProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD)).isEqualTo("conversion"); + assertThat(getSystemProperty(LoggingSystemProperty.LOG_FILE)).isEqualTo(this.logFile.getAbsolutePath()); + assertThat(getSystemProperty(LoggingSystemProperty.LEVEL_PATTERN)).isEqualTo("level"); + assertThat(getSystemProperty(LoggingSystemProperty.LOG_PATH)).isEqualTo("path"); + assertThat(getSystemProperty(LoggingSystemProperty.PID)).isNotNull(); } @Test @@ -484,15 +484,14 @@ void environmentPropertiesIgnoreUnresolvablePlaceholders() { // gh-7719 addPropertiesToEnvironment(this.context, "logging.pattern.console=console ${doesnotexist}"); this.listener.initialize(this.context.getEnvironment(), this.context.getClassLoader()); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)) - .isEqualTo("console ${doesnotexist}"); + assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN)).isEqualTo("console ${doesnotexist}"); } @Test void environmentPropertiesResolvePlaceholders() { addPropertiesToEnvironment(this.context, "logging.pattern.console=console ${pid}"); this.listener.initialize(this.context.getEnvironment(), this.context.getClassLoader()); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)) + assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN)) .isEqualTo(this.context.getEnvironment().getProperty("logging.pattern.console")); } @@ -500,7 +499,7 @@ void environmentPropertiesResolvePlaceholders() { void logFilePropertiesCanReferenceSystemProperties() { addPropertiesToEnvironment(this.context, "logging.file.name=" + this.tempDir + "${PID}.log"); this.listener.initialize(this.context.getEnvironment(), this.context.getClassLoader()); - assertThat(System.getProperty(LoggingSystemProperties.LOG_FILE)) + assertThat(getSystemProperty(LoggingSystemProperty.LOG_FILE)) .isEqualTo(this.tempDir + new ApplicationPid().toString() + ".log"); } @@ -575,6 +574,10 @@ void loggingGroupsCanBeDefined() { assertTraceEnabled("com.foo.baz", true); } + private String getSystemProperty(LoggingSystemProperty property) { + return System.getProperty(property.getEnvironmentVariableName()); + } + private void assertTraceEnabled(String name, boolean expected) { assertThat(this.loggerContext.getLogger(name).isTraceEnabled()).isEqualTo(expected); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java index 3ccc16255886..c80bf5654b57 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -50,8 +50,9 @@ void reinstateTempDir() { @AfterEach void clear() { - System.clearProperty(LoggingSystemProperties.LOG_FILE); - System.clearProperty(LoggingSystemProperties.PID_KEY); + for (LoggingSystemProperty property : LoggingSystemProperty.values()) { + System.getProperties().remove(property.getEnvironmentVariableName()); + } } protected final String[] getSpringConfigLocations(AbstractLoggingSystem system) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LogFileTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LogFileTests.java index 46ab410e0ee2..6639436655dd 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LogFileTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LogFileTests.java @@ -57,8 +57,9 @@ private void testLoggingFile(PropertyResolver resolver) { Properties properties = new Properties(); logFile.applyTo(properties); assertThat(logFile).hasToString("log.file"); - assertThat(properties.getProperty(LoggingSystemProperties.LOG_FILE)).isEqualTo("log.file"); - assertThat(properties.getProperty(LoggingSystemProperties.LOG_PATH)).isNull(); + assertThat(properties.getProperty(LoggingSystemProperty.LOG_FILE.getEnvironmentVariableName())) + .isEqualTo("log.file"); + assertThat(properties.getProperty(LoggingSystemProperty.LOG_PATH.getEnvironmentVariableName())).isNull(); } @Test @@ -72,9 +73,10 @@ private void testLoggingPath(PropertyResolver resolver) { Properties properties = new Properties(); logFile.applyTo(properties); assertThat(logFile).hasToString("logpath" + File.separatorChar + "spring.log"); - assertThat(properties.getProperty(LoggingSystemProperties.LOG_FILE)) + assertThat(properties.getProperty(LoggingSystemProperty.LOG_FILE.getEnvironmentVariableName())) .isEqualTo("logpath" + File.separatorChar + "spring.log"); - assertThat(properties.getProperty(LoggingSystemProperties.LOG_PATH)).isEqualTo("logpath"); + assertThat(properties.getProperty(LoggingSystemProperty.LOG_PATH.getEnvironmentVariableName())) + .isEqualTo("logpath"); } @Test @@ -91,8 +93,10 @@ private void testLoggingFileAndPath(PropertyResolver resolver) { Properties properties = new Properties(); logFile.applyTo(properties); assertThat(logFile).hasToString("log.file"); - assertThat(properties.getProperty(LoggingSystemProperties.LOG_FILE)).isEqualTo("log.file"); - assertThat(properties.getProperty(LoggingSystemProperties.LOG_PATH)).isEqualTo("logpath"); + assertThat(properties.getProperty(LoggingSystemProperty.LOG_FILE.getEnvironmentVariableName())) + .isEqualTo("log.file"); + assertThat(properties.getProperty(LoggingSystemProperty.LOG_PATH.getEnvironmentVariableName())) + .isEqualTo("logpath"); } private PropertyResolver getPropertyResolver(Map properties) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java index 3fb0106d718a..c374f0d31c0e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java @@ -43,8 +43,9 @@ class LoggingSystemPropertiesTests { @BeforeEach void captureSystemPropertyNames() { - System.getProperties().remove(LoggingSystemProperties.CONSOLE_LOG_CHARSET); - System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET); + for (LoggingSystemProperty property : LoggingSystemProperty.values()) { + System.getProperties().remove(property.getEnvironmentVariableName()); + } this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); } @@ -56,58 +57,62 @@ void restoreSystemProperties() { @Test void pidIsSet() { new LoggingSystemProperties(new MockEnvironment()).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.PID_KEY)).isNotNull(); + assertThat(getSystemProperty(LoggingSystemProperty.PID)).isNotNull(); } @Test void consoleLogPatternIsSet() { new LoggingSystemProperties(new MockEnvironment().withProperty("logging.pattern.console", "console pattern")) .apply(null); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)).isEqualTo("console pattern"); + assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN)).isEqualTo("console pattern"); } @Test void consoleCharsetWhenNoPropertyUsesUtf8() { new LoggingSystemProperties(new MockEnvironment()).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_CHARSET)).isEqualTo("UTF-8"); + assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_CHARSET)).isEqualTo("UTF-8"); } @Test void consoleCharsetIsSet() { new LoggingSystemProperties(new MockEnvironment().withProperty("logging.charset.console", "UTF-16")) .apply(null); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_CHARSET)).isEqualTo("UTF-16"); + assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_CHARSET)).isEqualTo("UTF-16"); } @Test void fileLogPatternIsSet() { new LoggingSystemProperties(new MockEnvironment().withProperty("logging.pattern.file", "file pattern")) .apply(null); - assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_PATTERN)).isEqualTo("file pattern"); + assertThat(getSystemProperty(LoggingSystemProperty.FILE_PATTERN)).isEqualTo("file pattern"); } @Test void fileCharsetWhenNoPropertyUsesUtf8() { new LoggingSystemProperties(new MockEnvironment()).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_CHARSET)).isEqualTo("UTF-8"); + assertThat(getSystemProperty(LoggingSystemProperty.FILE_CHARSET)).isEqualTo("UTF-8"); } @Test void fileCharsetIsSet() { new LoggingSystemProperties(new MockEnvironment().withProperty("logging.charset.file", "UTF-16")).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_CHARSET)).isEqualTo("UTF-16"); + assertThat(getSystemProperty(LoggingSystemProperty.FILE_CHARSET)).isEqualTo("UTF-16"); } @Test void consoleLogPatternCanReferencePid() { new LoggingSystemProperties(environment("logging.pattern.console", "${PID:unknown}")).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)).matches("[0-9]+"); + assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN)).matches("[0-9]+"); } @Test void fileLogPatternCanReferencePid() { new LoggingSystemProperties(environment("logging.pattern.file", "${PID:unknown}")).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_PATTERN)).matches("[0-9]+"); + assertThat(getSystemProperty(LoggingSystemProperty.FILE_PATTERN)).matches("[0-9]+"); + } + + private String getSystemProperty(LoggingSystemProperty property) { + return System.getProperty(property.getEnvironmentVariableName()); } private Environment environment(String key, Object value) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java index cb73992349a9..c13156c843d4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/java/JavaLoggingSystemTests.java @@ -33,7 +33,7 @@ import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.util.ClassUtils; @@ -113,7 +113,7 @@ void testCustomFormatter(CapturedOutput output) { @Test void testSystemPropertyInitializesFormat(CapturedOutput output) { - System.setProperty(LoggingSystemProperties.PID_KEY, "1234"); + System.setProperty(LoggingSystemProperty.PID.getEnvironmentVariableName(), "1234"); this.loggingSystem.beforeInitialize(); this.loggingSystem.initialize(null, "classpath:" + ClassUtils.addResourcePathToPackagePath(getClass(), "logging.properties"), null); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index 7aabe82966fb..a177de6bae83 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -53,7 +53,7 @@ import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.logging.ConfigureClasspathToPreferLog4j2; import org.springframework.boot.testsupport.system.CapturedOutput; @@ -361,7 +361,7 @@ void beforeInitializeFilterDisablesErrorLogging() { @Test void customExceptionConversionWord(CapturedOutput output) { - System.setProperty(LoggingSystemProperties.EXCEPTION_CONVERSION_WORD, "%ex"); + System.setProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD.getEnvironmentVariableName(), "%ex"); try { this.loggingSystem.beforeInitialize(); this.logger.info("Hidden"); @@ -373,7 +373,7 @@ void customExceptionConversionWord(CapturedOutput output) { assertThat(output).contains("java.lang.RuntimeException: Expected").doesNotContain("Wrapped by:"); } finally { - System.clearProperty(LoggingSystemProperties.EXCEPTION_CONVERSION_WORD); + System.clearProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD.getEnvironmentVariableName()); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2FileXmlTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2FileXmlTests.java index e5ea6eb56bdd..53517e76dc29 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2FileXmlTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2FileXmlTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import static org.assertj.core.api.Assertions.assertThat; @@ -42,7 +42,9 @@ class Log4j2FileXmlTests extends Log4j2XmlTests { @AfterEach void stopConfiguration() { super.stopConfiguration(); - System.clearProperty(LoggingSystemProperties.LOG_FILE); + for (LoggingSystemProperty property : LoggingSystemProperty.values()) { + System.getProperties().remove(property.getEnvironmentVariableName()); + } } @Test @@ -52,7 +54,7 @@ void whenLogExceptionConversionWordIsNotConfiguredThenFileAppenderUsesDefault() @Test void whenLogExceptionConversionWordIsSetThenFileAppenderUsesIt() { - withSystemProperty(LoggingSystemProperties.EXCEPTION_CONVERSION_WORD, "custom", + withSystemProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD.getEnvironmentVariableName(), "custom", () -> assertThat(fileAppenderPattern()).contains("custom")); } @@ -63,7 +65,7 @@ void whenLogLevelPatternIsNotConfiguredThenFileAppenderUsesDefault() { @Test void whenLogLevelPatternIsSetThenFileAppenderUsesIt() { - withSystemProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN, "custom", + withSystemProperty(LoggingSystemProperty.LEVEL_PATTERN.getEnvironmentVariableName(), "custom", () -> assertThat(fileAppenderPattern()).contains("custom")); } @@ -74,7 +76,7 @@ void whenLogLDateformatPatternIsNotConfiguredThenFileAppenderUsesDefault() { @Test void whenLogDateformatPatternIsSetThenFileAppenderUsesIt() { - withSystemProperty(LoggingSystemProperties.LOG_DATEFORMAT_PATTERN, "dd-MM-yyyy", + withSystemProperty(LoggingSystemProperty.DATEFORMAT_PATTERN.getEnvironmentVariableName(), "dd-MM-yyyy", () -> assertThat(fileAppenderPattern()).contains("dd-MM-yyyy")); } @@ -85,7 +87,8 @@ protected String getConfigFileName() { @Override protected void prepareConfiguration() { - System.setProperty(LoggingSystemProperties.LOG_FILE, new File(this.temp, "test.log").getAbsolutePath()); + System.setProperty(LoggingSystemProperty.LOG_FILE.getEnvironmentVariableName(), + new File(this.temp, "test.log").getAbsolutePath()); super.prepareConfiguration(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2XmlTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2XmlTests.java index 850d7a4d6b45..86ecd1e293c7 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2XmlTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2XmlTests.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import static org.assertj.core.api.Assertions.assertThat; @@ -53,7 +53,7 @@ void whenLogExceptionConversionWordIsNotConfiguredThenConsoleUsesDefault() { @Test void whenLogExceptionConversionWordIsSetThenConsoleUsesIt() { - withSystemProperty(LoggingSystemProperties.EXCEPTION_CONVERSION_WORD, "custom", + withSystemProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD.getEnvironmentVariableName(), "custom", () -> assertThat(consolePattern()).contains("custom")); } @@ -64,7 +64,7 @@ void whenLogLevelPatternIsNotConfiguredThenConsoleUsesDefault() { @Test void whenLogLevelPatternIsSetThenConsoleUsesIt() { - withSystemProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN, "custom", + withSystemProperty(LoggingSystemProperty.LEVEL_PATTERN.getEnvironmentVariableName(), "custom", () -> assertThat(consolePattern()).contains("custom")); } @@ -75,7 +75,7 @@ void whenLogLDateformatPatternIsNotConfiguredThenConsoleUsesDefault() { @Test void whenLogDateformatPatternIsSetThenConsoleUsesIt() { - withSystemProperty(LoggingSystemProperties.LOG_DATEFORMAT_PATTERN, "dd-MM-yyyy", + withSystemProperty(LoggingSystemProperty.DATEFORMAT_PATTERN.getEnvironmentVariableName(), "dd-MM-yyyy", () -> assertThat(consolePattern()).contains("dd-MM-yyyy")); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java index 2ab61b672f98..45c82c533de1 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java @@ -26,6 +26,7 @@ import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.mock.env.MockEnvironment; @@ -44,8 +45,9 @@ class LogbackLoggingSystemPropertiesTests { @BeforeEach void captureSystemPropertyNames() { - System.getProperties().remove(LoggingSystemProperties.CONSOLE_LOG_CHARSET); - System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET); + for (LoggingSystemProperty property : LoggingSystemProperty.values()) { + System.getProperties().remove(property.getEnvironmentVariableName()); + } this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); this.environment = new MockEnvironment(); this.environment @@ -62,7 +64,8 @@ void restoreSystemProperties() { void applySetsStandardSystemProperties() { this.environment.setProperty("logging.pattern.console", "boot"); new LogbackLoggingSystemProperties(this.environment).apply(); - assertThat(System.getProperties()).containsEntry(LoggingSystemProperties.CONSOLE_LOG_PATTERN, "boot"); + assertThat(System.getProperties()) + .containsEntry(LoggingSystemProperty.CONSOLE_PATTERN.getEnvironmentVariableName(), "boot"); } @Test @@ -74,11 +77,11 @@ void applySetsLogbackSystemProperties() { this.environment.setProperty("logging.logback.rollingpolicy.max-history", "mh"); new LogbackLoggingSystemProperties(this.environment).apply(); assertThat(System.getProperties()) - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_FILE_NAME_PATTERN, "fnp") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_CLEAN_HISTORY_ON_START, "chos") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_FILE_SIZE, "1024") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_TOTAL_SIZE_CAP, "2048") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_HISTORY, "mh"); + .containsEntry(RollingPolicySystemProperty.FILE_NAME_PATTERN.getEnvironmentVariableName(), "fnp") + .containsEntry(RollingPolicySystemProperty.CLEAN_HISTORY_ON_START.getEnvironmentVariableName(), "chos") + .containsEntry(RollingPolicySystemProperty.MAX_FILE_SIZE.getEnvironmentVariableName(), "1024") + .containsEntry(RollingPolicySystemProperty.TOTAL_SIZE_CAP.getEnvironmentVariableName(), "2048") + .containsEntry(RollingPolicySystemProperty.MAX_HISTORY.getEnvironmentVariableName(), "mh"); } @Test @@ -90,24 +93,24 @@ void applySetsLogbackSystemPropertiesFromDeprecated() { this.environment.setProperty("logging.file.max-history", "mh"); new LogbackLoggingSystemProperties(this.environment).apply(); assertThat(System.getProperties()) - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_FILE_NAME_PATTERN, "fnp") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_CLEAN_HISTORY_ON_START, "chos") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_FILE_SIZE, "1024") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_TOTAL_SIZE_CAP, "2048") - .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_HISTORY, "mh"); + .containsEntry(RollingPolicySystemProperty.FILE_NAME_PATTERN.getEnvironmentVariableName(), "fnp") + .containsEntry(RollingPolicySystemProperty.CLEAN_HISTORY_ON_START.getEnvironmentVariableName(), "chos") + .containsEntry(RollingPolicySystemProperty.MAX_FILE_SIZE.getEnvironmentVariableName(), "1024") + .containsEntry(RollingPolicySystemProperty.TOTAL_SIZE_CAP.getEnvironmentVariableName(), "2048") + .containsEntry(RollingPolicySystemProperty.MAX_HISTORY.getEnvironmentVariableName(), "mh"); } @Test void consoleCharsetWhenNoPropertyUsesDefault() { new LoggingSystemProperties(new MockEnvironment()).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_CHARSET)) + assertThat(System.getProperty(LoggingSystemProperty.CONSOLE_CHARSET.getEnvironmentVariableName())) .isEqualTo(Charset.defaultCharset().name()); } @Test void fileCharsetWhenNoPropertyUsesDefault() { new LoggingSystemProperties(new MockEnvironment()).apply(null); - assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_CHARSET)) + assertThat(System.getProperty(LoggingSystemProperty.FILE_CHARSET.getEnvironmentVariableName())) .isEqualTo(Charset.defaultCharset().name()); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index a0a27b077e7e..515d09a1e197 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -56,6 +56,7 @@ import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystemProperties; +import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; @@ -102,8 +103,9 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { @BeforeEach void setup() { - System.getProperties().remove(LoggingSystemProperties.CONSOLE_LOG_CHARSET); - System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET); + for (LoggingSystemProperty property : LoggingSystemProperty.values()) { + System.getProperties().remove(property.getEnvironmentVariableName()); + } this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); this.loggingSystem.cleanUp(); this.logger = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(getClass()); @@ -503,7 +505,7 @@ void exceptionsIncludeClassPackaging(CapturedOutput output) { @Test void customExceptionConversionWord(CapturedOutput output) { - System.setProperty(LoggingSystemProperties.EXCEPTION_CONVERSION_WORD, "%ex"); + System.setProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD.getEnvironmentVariableName(), "%ex"); try { this.loggingSystem.beforeInitialize(); this.logger.info("Hidden"); @@ -514,7 +516,7 @@ void customExceptionConversionWord(CapturedOutput output) { assertThat(output).contains("java.lang.RuntimeException: Expected").doesNotContain("Wrapped by:"); } finally { - System.clearProperty(LoggingSystemProperties.EXCEPTION_CONVERSION_WORD); + System.clearProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD.getEnvironmentVariableName()); } } @@ -525,7 +527,8 @@ void initializeShouldSetSystemProperty() { this.logger.info("Hidden"); LogFile logFile = getLogFile(tmpDir() + "/example.log", null, false); initialize(this.initializationContext, "classpath:logback-nondefault.xml", logFile); - assertThat(System.getProperty(LoggingSystemProperties.LOG_FILE)).endsWith("example.log"); + assertThat(System.getProperty(LoggingSystemProperty.LOG_FILE.getEnvironmentVariableName())) + .endsWith("example.log"); } @Test From c1b295fd71573209122b2321b256675babfacc96 Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Tue, 20 Jun 2023 21:35:36 -0700 Subject: [PATCH 0061/1656] Log correlation IDs when Micrometer tracing is being used Add support for logging correlation IDs with Logback or Log4J2 whenever Micrometer tracing is being used. The `LoggingSystemProperties` class now accepts a defualt value resolver which will be used whenever a value isn't in the environment. The `AbstractLoggingSystem` provides a resolver that supports the `logging.pattern.correlation` property and will return a value whenever `LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY` is set. Using `LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY` allows us to provide a consistent width for the correlation ID, even when it's missing from the MDC. The exact correlation pattern returned will depend on the `LoggingSytem` implementation. Currently Logback and Log4J2 are supported and both make use of a custom converter which delegates to a new `CorrelationIdFormatter` class. Closes gh-33280 --- ...ogCorrelationEnvironmentPostProcessor.java | 69 ++++++ .../main/resources/META-INF/spring.factories | 4 + ...relationEnvironmentPostProcessorTests.java | 67 ++++++ .../src/docs/asciidoc/actuator/tracing.adoc | 20 +- .../src/docs/asciidoc/features/logging.adoc | 1 + .../boot/logging/AbstractLoggingSystem.java | 33 ++- .../boot/logging/CorrelationIdFormatter.java | 199 ++++++++++++++++++ .../boot/logging/LoggingSystem.java | 10 +- .../boot/logging/LoggingSystemProperties.java | 29 ++- .../boot/logging/LoggingSystemProperty.java | 7 +- .../log4j2/CorrelationIdConverter.java | 69 ++++++ .../logging/log4j2/Log4J2LoggingSystem.java | 33 +-- .../logback/CorrelationIdConverter.java | 62 ++++++ .../logback/DefaultLogbackConfiguration.java | 6 +- .../logging/logback/LogbackLoggingSystem.java | 11 +- .../LogbackLoggingSystemProperties.java | 14 ++ .../logging/logback/LogbackRuntimeHints.java | 5 +- ...itional-spring-configuration-metadata.json | 6 + .../boot/logging/log4j2/log4j2-file.xml | 4 +- .../boot/logging/log4j2/log4j2.xml | 4 +- .../boot/logging/logback/defaults.xml | 5 +- .../LoggingApplicationListenerTests.java | 3 +- .../logging/AbstractLoggingSystemTests.java | 18 ++ .../logging/CorrelationIdFormatterTests.java | 122 +++++++++++ .../logging/LoggingSystemPropertiesTests.java | 21 ++ .../log4j2/CorrelationIdConverterTests.java | 65 ++++++ .../log4j2/Log4J2LoggingSystemTests.java | 77 ++++++- .../log4j2/TestLog4J2LoggingSystem.java | 18 +- .../logback/CorrelationIdConverterTests.java | 67 ++++++ .../logback/LogbackLoggingSystemTests.java | 87 +++++++- 30 files changed, 1086 insertions(+), 50 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/CorrelationIdConverter.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/CorrelationIdConverter.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/CorrelationIdConverterTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/CorrelationIdConverterTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java new file mode 100644 index 000000000000..d7e86849d05e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.tracing; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.util.ClassUtils; + +/** + * {@link EnvironmentPostProcessor} to add a {@link PropertySource} to support log + * correlation IDs when Micrometer is present. Adds support for the + * {@value LoggingSystem#EXPECT_CORRELATION_ID_PROPERTY} property by delegating to + * {@code management.tracing.enabled}. + * + * @author Jonatan Ivanov + * @author Phillip Webb + */ +class LogCorrelationEnvironmentPostProcessor implements EnvironmentPostProcessor { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + if (ClassUtils.isPresent("io.micrometer.tracing.Tracer", application.getClassLoader())) { + environment.getPropertySources().addLast(new LogCorrelationPropertySource(this, environment)); + } + } + + /** + * Log correlation {@link PropertySource}. + */ + private static class LogCorrelationPropertySource extends PropertySource { + + private static final String NAME = "logCorrelation"; + + private final Environment environment; + + LogCorrelationPropertySource(Object source, Environment environment) { + super(NAME, source); + this.environment = environment; + } + + @Override + public Object getProperty(String name) { + if (name.equals(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY)) { + return this.environment.getProperty("management.tracing.enabled", Boolean.class, Boolean.TRUE); + } + return null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index 7d4fdd7b4051..eb43227d975b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,3 +1,7 @@ # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer + +# Environment Post Processors +org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.boot.actuate.autoconfigure.tracing.LogCorrelationEnvironmentPostProcessor diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java new file mode 100644 index 000000000000..a75a91a28bd5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.tracing; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.StandardEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LogCorrelationEnvironmentPostProcessor}. + * + * @author Jonatan Ivanov + * @author Phillip Webb + */ +class LogCorrelationEnvironmentPostProcessorTests { + + private final ConfigurableEnvironment environment = new StandardEnvironment(); + + private final SpringApplication application = new SpringApplication(); + + private final LogCorrelationEnvironmentPostProcessor postProcessor = new LogCorrelationEnvironmentPostProcessor(); + + @Test + void getExpectCorrelationIdPropertyWhenMicrometerPresentReturnsTrue() { + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) + .isTrue(); + } + + @Test + @ClassPathExclusions("micrometer-tracing-*.jar") + void getExpectCorrelationIdPropertyWhenMicrometerMissingReturnsFalse() { + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) + .isFalse(); + } + + @Test + void getExpectCorrelationIdPropertyWhenTracingDisabledReturnsFalse() { + TestPropertyValues.of("management.tracing.enabled=false").applyTo(this.environment); + this.postProcessor.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) + .isFalse(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc index 650b7efe2494..0b9378d1acd5 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc @@ -65,7 +65,25 @@ Now open the Zipkin UI at `http://localhost:9411` and press the "Run Query" butt You should see one trace. Press the "Show" button to see the details of that trace. -TIP: You can include the current trace and span id in the logs by setting the `logging.pattern.level` property to `%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]` + + +[[actuator.micrometer-tracing.logging]] +=== Logging Correlation IDs +Correlation IDs provide a helpful way to link lines in your log files to distributed traces. +By default, as long as configprop:management.tracing.enabled[] has not been set to `false`, Spring Boot will include correlation IDs in your logs whenever you are using Micrometer tracing. + +The default correlation ID is built from `traceId` and `spanId` https://logback.qos.ch/manual/mdc.html[MDC] values. +For example, if Micrometer tracing has added an MDC `traceId` of `803B448A0489F84084905D3093480352` and an MDC `spanId` of `3425F23BB2432450` the log output will include the correlation ID `[803B448A0489F84084905D3093480352-3425F23BB2432450]`. + +If you prefer to use a different format for your correlation ID, you can use the configprop:logging.pattern.correlation[] property to define one. +For example, the following will provide a correlation ID for Logback in format previously used by Spring Sleuth: + +[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +---- + logging: + pattern: + correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}]" +---- diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc index 5f36f43f630e..b1d81e5ef07a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc @@ -32,6 +32,7 @@ The following items are output: * Process ID. * A `---` separator to distinguish the start of actual log messages. * Thread name: Enclosed in square brackets (may be truncated for console output). +* Correlation ID: If tracing is enabled (not shown in the sample above) * Logger name: This is usually the source class name (often abbreviated). * The log message. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java index 48be4c91f46a..1d22b9aee8c6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -22,6 +22,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.function.Function; import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; @@ -174,7 +175,35 @@ protected final String getPackagedConfigFile(String fileName) { } protected final void applySystemProperties(Environment environment, LogFile logFile) { - new LoggingSystemProperties(environment).apply(logFile); + new LoggingSystemProperties(environment, getDefaultValueResolver(environment), null).apply(logFile); + } + + /** + * Return the default value resolver to use when resolving system properties. + * @param environment the environment + * @return the default value resolver + * @since 3.2.0 + */ + protected Function getDefaultValueResolver(Environment environment) { + String defaultLogCorrelationPattern = getDefaultLogCorrelationPattern(); + return (name) -> { + if (StringUtils.hasLength(defaultLogCorrelationPattern) + && LoggingSystemProperty.CORRELATION_PATTERN.getApplicationPropertyName().equals(name) + && environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) { + return defaultLogCorrelationPattern; + } + return null; + }; + } + + /** + * Return the default log correlation pattern or {@code null} if log correlation + * patterns are not supported. + * @return the default log correlation pattern + * @since 3.2.0 + */ + protected String getDefaultLogCorrelationPattern() { + return null; } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java new file mode 100644 index 000000000000..5270833a6c90 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012-2023 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.logging; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Utility class that can be used to format a correlation identifier for logging based on + * w3c + * recommendations. + *

+ * The formatter can be configured with a comma-separated list of names and the expected + * length of their resolved value. Each item should be specified in the form + * {@code "(length)"}. For example, {@code "traceId(32),spanId(16)"} specifies the + * names {@code "traceId"} and {@code "spanId"} with expected lengths of {@code 32} and + * {@code 16} respectively. + *

+ * Correlation IDs are formatted as dash separated strings surrounded in square brackets. + * Formatted output is always of a fixed width and with trailing whitespace. Dashes are + * omitted of none of the named items can be resolved. + *

+ * The following example would return a formatted result of + * {@code "[01234567890123456789012345678901-0123456789012345] "}:

+ * CorrelationIdFormatter formatter = CorrelationIdFormatter.of("traceId(32),spanId(16)");
+ * Map<String, String> mdc = Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345");
+ * return formatter.format(mdc::get);
+ * 
+ *

+ * If {@link #of(String)} is called with an empty spec the {@link #DEFAULT} formatter will + * be used. + * + * @author Phillip Webb + * @since 3.2.0 + * @see #of(String) + * @see #of(Collection) + */ +public final class CorrelationIdFormatter { + + /** + * Default {@link CorrelationIdFormatter}. + */ + public static final CorrelationIdFormatter DEFAULT = CorrelationIdFormatter.of("traceId(32),spanId(16)"); + + private final List parts; + + private final String blank; + + private CorrelationIdFormatter(List parts) { + this.parts = parts; + this.blank = String.format("[%s] ", parts.stream().map(Part::blank).collect(Collectors.joining(" "))); + } + + /** + * Format a correlation from the values in the given resolver. + * @param resolver the resolver used to resolve named values + * @return a formatted correlation id + */ + public String format(Function resolver) { + StringBuilder result = new StringBuilder(); + formatTo(resolver, result); + return result.toString(); + } + + /** + * Format a correlation from the values in the given resolver and append it to the + * given {@link Appendable}. + * @param resolver the resolver used to resolve named values + * @param appendable the appendable for the formatted correlation id + */ + public void formatTo(Function resolver, Appendable appendable) { + Predicate canResolve = (part) -> StringUtils.hasLength(resolver.apply(part.name())); + try { + if (this.parts.stream().anyMatch(canResolve)) { + appendable.append("["); + for (Iterator iterator = this.parts.iterator(); iterator.hasNext();) { + appendable.append(iterator.next().resolve(resolver)); + appendable.append((!iterator.hasNext()) ? "" : "-"); + } + appendable.append("] "); + } + else { + appendable.append(this.blank); + } + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + @Override + public String toString() { + return this.parts.stream().map(Part::toString).collect(Collectors.joining(",")); + } + + /** + * Create a new {@link CorrelationIdFormatter} instance from the given specification. + * @param spec a comma separated specification + * @return a new {@link CorrelationIdFormatter} instance + */ + public static CorrelationIdFormatter of(String spec) { + try { + return (!StringUtils.hasText(spec)) ? DEFAULT : of(List.of(spec.split(","))); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to parse correlation formatter spec '%s'".formatted(spec), ex); + } + } + + /** + * Create a new {@link CorrelationIdFormatter} instance from the given specification. + * @param spec a pre-separated specification + * @return a new {@link CorrelationIdFormatter} instance + */ + public static CorrelationIdFormatter of(String[] spec) { + return of((spec != null) ? Arrays.asList(spec) : Collections.emptyList()); + } + + /** + * Create a new {@link CorrelationIdFormatter} instance from the given specification. + * @param spec a pre-separated specification + * @return a new {@link CorrelationIdFormatter} instance + */ + public static CorrelationIdFormatter of(Collection spec) { + if (CollectionUtils.isEmpty(spec)) { + return DEFAULT; + } + List parts = spec.stream().map(Part::of).toList(); + return new CorrelationIdFormatter(parts); + } + + /** + * A part of the correlation id. + * + * @param name the name of the correlation part + * @param length the expected length of the correlation part + */ + static final record Part(String name, int length) { + + private static final Pattern pattern = Pattern.compile("^(.+?)\\((\\d+)\\)?$"); + + String resolve(Function resolver) { + String resolved = resolver.apply(name()); + if (resolved == null) { + return blank(); + } + int padding = length() - resolved.length(); + return resolved + " ".repeat((padding > 0) ? padding : 0); + } + + String blank() { + return " ".repeat(this.length); + } + + @Override + public String toString() { + return "%s(%s)".formatted(name(), length()); + } + + static Part of(String part) { + Matcher matcher = pattern.matcher(part.trim()); + Assert.state(matcher.matches(), () -> "Invalid specification part '%s'".formatted(part)); + String name = matcher.group(1); + int length = Integer.parseInt(matcher.group(2)); + return new Part(name, length); + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java index d35b5ba4c64b..1fd3a398378d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -23,6 +23,7 @@ import java.util.Set; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -58,6 +59,13 @@ public abstract class LoggingSystem { private static final LoggingSystemFactory SYSTEM_FACTORY = LoggingSystemFactory.fromSpringFactories(); + /** + * The name of an {@link Environment} property used to indicate that a correlation ID + * is expected to be logged at some point. + * @since 3.2.0 + */ + public static final String EXPECT_CORRELATION_ID_PROPERTY = "logging.expect-correlation-id"; + /** * Return the {@link LoggingSystemProperties} that should be applied. * @param environment the {@link ConfigurableEnvironment} used to obtain value diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java index 456bb010d075..8b1b20f57f10 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java @@ -19,6 +19,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.function.BiConsumer; +import java.util.function.Function; import org.springframework.boot.system.ApplicationPid; import org.springframework.core.env.ConfigurableEnvironment; @@ -36,6 +37,7 @@ * @author Vedran Pavic * @author Robert Thornton * @author Eddú Meléndez + * @author Jonatan Ivanov * @since 2.0.0 * @see LoggingSystemProperty */ @@ -160,6 +162,8 @@ public class LoggingSystemProperties { private final Environment environment; + private final Function defaultValueResolver; + private final BiConsumer setter; /** @@ -167,20 +171,34 @@ public class LoggingSystemProperties { * @param environment the source environment */ public LoggingSystemProperties(Environment environment) { - this(environment, systemPropertySetter); + this(environment, null); } /** * Create a new {@link LoggingSystemProperties} instance. * @param environment the source environment - * @param setter setter used to apply the property + * @param setter setter used to apply the property or {@code null} for system + * properties * @since 2.4.2 */ public LoggingSystemProperties(Environment environment, BiConsumer setter) { + this(environment, null, setter); + } + + /** + * Create a new {@link LoggingSystemProperties} instance. + * @param environment the source environment + * @param defaultValueResolver function used to resolve default values or {@code null} + * @param setter setter used to apply the property or {@code null} for system + * properties + * @since 3.2.0 + */ + public LoggingSystemProperties(Environment environment, Function defaultValueResolver, + BiConsumer setter) { Assert.notNull(environment, "Environment must not be null"); - Assert.notNull(setter, "Setter must not be null"); this.environment = environment; - this.setter = setter; + this.defaultValueResolver = (defaultValueResolver != null) ? defaultValueResolver : (name) -> null; + this.setter = (setter != null) ? setter : systemPropertySetter; } protected Charset getDefaultCharset() { @@ -219,6 +237,7 @@ protected void apply(LogFile logFile, PropertyResolver resolver) { setSystemProperty(LoggingSystemProperty.FILE_PATTERN, resolver); setSystemProperty(LoggingSystemProperty.LEVEL_PATTERN, resolver); setSystemProperty(LoggingSystemProperty.DATEFORMAT_PATTERN, resolver); + setSystemProperty(LoggingSystemProperty.CORRELATION_PATTERN, resolver); if (logFile != null) { logFile.applyToSystemProperties(); } @@ -231,6 +250,7 @@ private void setSystemProperty(LoggingSystemProperty property, PropertyResolver private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver, String defaultValue) { String value = (property.getApplicationPropertyName() != null) ? resolver.getProperty(property.getApplicationPropertyName()) : null; + value = (value != null) ? value : this.defaultValueResolver.apply(property.getApplicationPropertyName()); value = (value != null) ? value : defaultValue; setSystemProperty(property.getEnvironmentVariableName(), value); } @@ -263,6 +283,7 @@ protected final void setSystemProperty(PropertyResolver resolver, String systemP protected final void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName, String defaultValue) { String value = resolver.getProperty(propertyName); + value = (value != null) ? value : this.defaultValueResolver.apply(systemPropertyName); value = (value != null) ? value : defaultValue; setSystemProperty(systemPropertyName, value); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java index d6a731453189..b835feeb547d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java @@ -83,7 +83,12 @@ public enum LoggingSystemProperty { /** * Logging system property for the date-format pattern. */ - DATEFORMAT_PATTERN("LOG_DATEFORMAT_PATTERN", "logging.pattern.dateformat"); + DATEFORMAT_PATTERN("LOG_DATEFORMAT_PATTERN", "logging.pattern.dateformat"), + + /** + * Logging system property for the correlation pattern. + */ + CORRELATION_PATTERN("LOG_CORRELATION_PATTERN", "logging.pattern.correlation"); private final String environmentVariableName; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/CorrelationIdConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/CorrelationIdConverter.java new file mode 100644 index 000000000000..5dcf9195da48 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/CorrelationIdConverter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2023 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.logging.log4j2; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.MdcPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.apache.logging.log4j.util.PerformanceSensitive; +import org.apache.logging.log4j.util.ReadOnlyStringMap; + +import org.springframework.boot.logging.CorrelationIdFormatter; +import org.springframework.util.ObjectUtils; + +/** + * Log4j2 {@link LogEventPatternConverter} to convert a {@link CorrelationIdFormatter} + * pattern into formatted output using data from the {@link LogEvent#getContextData() + * MDC}. + * + * @author Phillip Webb + * @since 3.2.0 + * @see MdcPatternConverter + */ +@Plugin(name = "CorrelationIdConverter", category = PatternConverter.CATEGORY) +@ConverterKeys("correlationId") +@PerformanceSensitive("allocation") +public final class CorrelationIdConverter extends LogEventPatternConverter { + + private final CorrelationIdFormatter formatter; + + private CorrelationIdConverter(CorrelationIdFormatter formatter) { + super("correlationId{%s}".formatted(formatter), "mdc"); + this.formatter = formatter; + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + ReadOnlyStringMap contextData = event.getContextData(); + this.formatter.formatTo(contextData::getValue, toAppendTo); + } + + /** + * Factory method to create a new {@link CorrelationIdConverter}. + * @param options options, may be null or first element contains name of property to + * format. + * @return instance of PropertiesPatternConverter. + */ + public static CorrelationIdConverter newInstance(String[] options) { + String pattern = (!ObjectUtils.isEmpty(options)) ? options[0] : null; + return new CorrelationIdConverter(CorrelationIdFormatter.of(pattern)); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java index 1ff7c840ed07..da996bfd5c97 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java @@ -249,27 +249,29 @@ public void initialize(LoggingInitializationContext initializationContext, Strin @Override protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { - if (logFile != null) { - loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile, getOverrides(initializationContext)); - } - else { - loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile, getOverrides(initializationContext)); - } - } - - private List getOverrides(LoggingInitializationContext initializationContext) { - BindResult> overrides = Binder.get(initializationContext.getEnvironment()) - .bind("logging.log4j2.config.override", Bindable.listOf(String.class)); - return overrides.orElse(Collections.emptyList()); + String location = (logFile != null) ? getPackagedConfigFile("log4j2-file.xml") + : getPackagedConfigFile("log4j2.xml"); + load(initializationContext, location, logFile); } @Override protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { + load(initializationContext, location, logFile); + } + + private void load(LoggingInitializationContext initializationContext, String location, LogFile logFile) { + List overrides = getOverrides(initializationContext); if (initializationContext != null) { applySystemProperties(initializationContext.getEnvironment(), logFile); } - loadConfiguration(location, logFile, getOverrides(initializationContext)); + loadConfiguration(location, logFile, overrides); + } + + private List getOverrides(LoggingInitializationContext initializationContext) { + BindResult> overrides = Binder.get(initializationContext.getEnvironment()) + .bind("logging.log4j2.config.override", Bindable.listOf(String.class)); + return overrides.orElse(Collections.emptyList()); } /** @@ -492,6 +494,11 @@ private void markAsUninitialized(LoggerContext loggerContext) { loggerContext.setExternalContext(null); } + @Override + protected String getDefaultLogCorrelationPattern() { + return "%correlationId"; + } + /** * Get the Spring {@link Environment} attached to the given {@link LoggerContext} or * {@code null} if no environment is available. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/CorrelationIdConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/CorrelationIdConverter.java new file mode 100644 index 000000000000..87b1e792b6c8 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/CorrelationIdConverter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2023 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.logging.logback; + +import java.util.Map; + +import ch.qos.logback.classic.pattern.MDCConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.pattern.DynamicConverter; + +import org.springframework.boot.logging.CorrelationIdFormatter; +import org.springframework.core.env.Environment; + +/** + * Logback {@link DynamicConverter} to convert a {@link CorrelationIdFormatter} pattern + * into formatted output using data from the {@link ILoggingEvent#getMDCPropertyMap() MDC} + * and {@link Environment}. + * + * @author Phillip Webb + * @since 3.2.0 + * @see MDCConverter + */ +public class CorrelationIdConverter extends DynamicConverter { + + private CorrelationIdFormatter formatter; + + @Override + public void start() { + this.formatter = CorrelationIdFormatter.of(getOptionList()); + super.start(); + } + + @Override + public void stop() { + this.formatter = null; + super.stop(); + } + + @Override + public String convert(ILoggingEvent event) { + if (this.formatter == null) { + return ""; + } + Map mdc = event.getMDCPropertyMap(); + return this.formatter.format(mdc::get); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java index 0329653359da..c86cd372e5f9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -43,6 +43,7 @@ * @author Vedran Pavic * @author Robert Thornton * @author Scott Frederick + * @author Jonatan Ivanov */ class DefaultLogbackConfiguration { @@ -68,12 +69,14 @@ void apply(LogbackConfigurator config) { private void defaults(LogbackConfigurator config) { config.conversionRule("clr", ColorConverter.class); + config.conversionRule("correlationId", CorrelationIdConverter.class); config.conversionRule("wex", WhitespaceThrowableProxyConverter.class); config.conversionRule("wEx", ExtendedWhitespaceThrowableProxyConverter.class); config.getContext() .putProperty("CONSOLE_LOG_PATTERN", resolve(config, "${CONSOLE_LOG_PATTERN:-" + "%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) " - + "%clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} " + + "%clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} " + + "%clr(${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} " + "%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}")); String defaultCharset = Charset.defaultCharset().name(); config.getContext() @@ -82,6 +85,7 @@ private void defaults(LogbackConfigurator config) { config.getContext() .putProperty("FILE_LOG_PATTERN", resolve(config, "${FILE_LOG_PATTERN:-" + "%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] " + + "${LOG_CORRELATION_PATTERN:-}" + "%-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}")); config.getContext() .putProperty("FILE_LOG_CHARSET", resolve(config, "${FILE_LOG_CHARSET:-" + defaultCharset + "}")); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index ee31b9b07a45..6babadee5245 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -109,7 +109,7 @@ public LogbackLoggingSystem(ClassLoader classLoader) { @Override public LoggingSystemProperties getSystemProperties(ConfigurableEnvironment environment) { - return new LogbackLoggingSystemProperties(environment); + return new LogbackLoggingSystemProperties(environment, getDefaultValueResolver(environment), null); } @Override @@ -186,6 +186,7 @@ public void initialize(LoggingInitializationContext initializationContext, Strin if (!initializeFromAotGeneratedArtifactsIfPossible(initializationContext, logFile)) { super.initialize(initializationContext, configLocation, logFile); } + loggerContext.putObject(Environment.class.getName(), initializationContext.getEnvironment()); loggerContext.getTurboFilterList().remove(FILTER); markAsInitialized(loggerContext); if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) { @@ -223,7 +224,8 @@ protected void loadDefaults(LoggingInitializationContext initializationContext, } Environment environment = initializationContext.getEnvironment(); // Apply system properties directly in case the same JVM runs multiple apps - new LogbackLoggingSystemProperties(environment, context::putProperty).apply(logFile); + new LogbackLoggingSystemProperties(environment, getDefaultValueResolver(environment), context::putProperty) + .apply(logFile); LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(context) : new LogbackConfigurator(context); new DefaultLogbackConfiguration(logFile).apply(configurator); @@ -415,6 +417,11 @@ private void markAsUninitialized(LoggerContext loggerContext) { loggerContext.removeObject(LoggingSystem.class.getName()); } + @Override + protected String getDefaultLogCorrelationPattern() { + return "%correlationId"; + } + @Override public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { String key = BeanFactoryInitializationAotContribution.class.getName(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java index 3b7b715e1615..df821a473394 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java @@ -18,6 +18,7 @@ import java.nio.charset.Charset; import java.util.function.BiConsumer; +import java.util.function.Function; import ch.qos.logback.core.util.FileSize; @@ -107,6 +108,19 @@ public LogbackLoggingSystemProperties(Environment environment, BiConsumer defaultValueResolver, + BiConsumer setter) { + super(environment, defaultValueResolver, setter); + } + @Override protected Charset getDefaultCharset() { return Charset.defaultCharset(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java index 532a2adf78d4..44e50d50900d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -59,7 +59,8 @@ private void registerHintsForBuiltInLogbackConverters(ReflectionHints reflection private void registerHintsForSpringBootConverters(ReflectionHints reflection) { registerForPublicConstructorInvocation(reflection, ColorConverter.class, - ExtendedWhitespaceThrowableProxyConverter.class, WhitespaceThrowableProxyConverter.class); + ExtendedWhitespaceThrowableProxyConverter.class, WhitespaceThrowableProxyConverter.class, + CorrelationIdConverter.class); } private void registerForPublicConstructorInvocation(ReflectionHints reflection, Class... classes) { diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 4835aa0c30a3..17c21808424d 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -165,6 +165,12 @@ "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener", "defaultValue": "%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" }, + { + "name": "logging.pattern.correlation", + "type": "java.lang.String", + "description": "Appender pattern for log correlation. Supported only with the default Logback setup.", + "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener" + }, { "name": "logging.pattern.dateformat", "type": "java.lang.String", diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml index d7c510bb7a98..ee6812d92a66 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml @@ -4,8 +4,8 @@ %xwEx %5p yyyy-MM-dd'T'HH:mm:ss.SSSXXX - %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- [%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml index 65f1a1b612d7..de8588498e1e 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml @@ -4,8 +4,8 @@ %xwEx %5p yyyy-MM-dd'T'HH:mm:ss.SSSXXX - %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- [%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml index bc2ec1238193..d10022ca0705 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml @@ -6,13 +6,14 @@ Default logback configuration provided for import + - + - + diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java index 4bfeac363066..d422f10c49a5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java @@ -92,6 +92,7 @@ * @author Ben Hale * @author Fahim Farook * @author Eddú Meléndez + * @author Jonatan Ivanov */ @ExtendWith(OutputCaptureExtension.class) @ClassPathExclusions("log4j*.jar") @@ -467,7 +468,7 @@ void closingChildContextDoesNotCleanUpLoggingSystem() { void systemPropertiesAreSetForLoggingConfiguration() { addPropertiesToEnvironment(this.context, "logging.exception-conversion-word=conversion", "logging.file.name=" + this.logFile, "logging.file.path=path", "logging.pattern.console=console", - "logging.pattern.file=file", "logging.pattern.level=level", + "logging.pattern.file=file", "logging.pattern.level=level", "logging.pattern.correlation=correlation", "logging.pattern.rolling-file-name=my.log.%d{yyyyMMdd}.%i.gz"); this.listener.initialize(this.context.getEnvironment(), this.context.getClassLoader()); assertThat(getSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN)).isEqualTo("console"); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java index c80bf5654b57..cba035869fff 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java @@ -16,14 +16,19 @@ package org.springframework.boot.logging; +import java.io.File; import java.nio.file.Path; +import java.util.Arrays; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.io.TempDir; +import org.slf4j.MDC; import org.springframework.util.StringUtils; +import static org.assertj.core.api.Assertions.contentOf; + /** * Base for {@link LoggingSystem} tests. * @@ -41,6 +46,7 @@ public abstract class AbstractLoggingSystemTests { void configureTempDir(@TempDir Path temp) { this.originalTempDirectory = System.getProperty(JAVA_IO_TMPDIR); System.setProperty(JAVA_IO_TMPDIR, temp.toAbsolutePath().toString()); + MDC.clear(); } @AfterEach @@ -53,6 +59,7 @@ void clear() { for (LoggingSystemProperty property : LoggingSystemProperty.values()) { System.getProperties().remove(property.getEnvironmentVariableName()); } + MDC.clear(); } protected final String[] getSpringConfigLocations(AbstractLoggingSystem system) { @@ -79,4 +86,15 @@ protected final String tmpDir() { return path; } + protected final String getLineWithText(File file, CharSequence outputSearch) { + return getLineWithText(contentOf(file), outputSearch); + } + + protected final String getLineWithText(CharSequence output, CharSequence outputSearch) { + return Arrays.stream(output.toString().split("\\r?\\n")) + .filter((line) -> line.contains(outputSearch)) + .findFirst() + .orElse(null); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java new file mode 100644 index 000000000000..b34771352a47 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2012-2023 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.logging; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link CorrelationIdFormatter}. + * + * @author Phillip Webb + */ +class CorrelationIdFormatterTests { + + @Test + void formatWithDefaultSpecWhenHasBothParts() { + Map context = new HashMap<>(); + context.put("traceId", "01234567890123456789012345678901"); + context.put("spanId", "0123456789012345"); + String formatted = CorrelationIdFormatter.DEFAULT.format(context::get); + assertThat(formatted).isEqualTo("[01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void formatWithDefaultSpecWhenHasNoParts() { + Map context = new HashMap<>(); + String formatted = CorrelationIdFormatter.DEFAULT.format(context::get); + assertThat(formatted).isEqualTo("[ ] "); + } + + @Test + void formatWithDefaultSpecWhenHasOnlyFirstPart() { + Map context = new HashMap<>(); + context.put("traceId", "01234567890123456789012345678901"); + String formatted = CorrelationIdFormatter.DEFAULT.format(context::get); + assertThat(formatted).isEqualTo("[01234567890123456789012345678901- ] "); + } + + @Test + void formatWithDefaultSpecWhenHasOnlySecondPart() { + Map context = new HashMap<>(); + context.put("spanId", "0123456789012345"); + String formatted = CorrelationIdFormatter.DEFAULT.format(context::get); + assertThat(formatted).isEqualTo("[ -0123456789012345] "); + } + + @Test + void formatWhenPartsAreShort() { + Map context = new HashMap<>(); + context.put("traceId", "0123456789012345678901234567"); + context.put("spanId", "012345678901"); + String formatted = CorrelationIdFormatter.DEFAULT.format(context::get); + assertThat(formatted).isEqualTo("[0123456789012345678901234567 -012345678901 ] "); + } + + @Test + void formatWhenPartsAreLong() { + Map context = new HashMap<>(); + context.put("traceId", "01234567890123456789012345678901FFFF"); + context.put("spanId", "0123456789012345FFFF"); + String formatted = CorrelationIdFormatter.DEFAULT.format(context::get); + assertThat(formatted).isEqualTo("[01234567890123456789012345678901FFFF-0123456789012345FFFF] "); + } + + @Test + void formatWithCustomSpec() { + Map context = new HashMap<>(); + context.put("a", "01234567890123456789012345678901"); + context.put("b", "0123456789012345"); + String formatted = CorrelationIdFormatter.of("a(32),b(16)").format(context::get); + assertThat(formatted).isEqualTo("[01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void formatToWithDefaultSpec() { + Map context = new HashMap<>(); + context.put("traceId", "01234567890123456789012345678901"); + context.put("spanId", "0123456789012345"); + StringBuilder formatted = new StringBuilder(); + CorrelationIdFormatter.of("").formatTo(context::get, formatted); + assertThat(formatted).hasToString("[01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void ofWhenSpecIsMalformed() { + assertThatIllegalStateException().isThrownBy(() -> CorrelationIdFormatter.of("good(12),bad")) + .withMessage("Unable to parse correlation formatter spec 'good(12),bad'") + .havingCause() + .withMessage("Invalid specification part 'bad'"); + } + + @Test + void ofWhenSpecIsEmpty() { + assertThat(CorrelationIdFormatter.of("")).isSameAs(CorrelationIdFormatter.DEFAULT); + } + + @Test + void toStringReturnsSpec() { + assertThat(CorrelationIdFormatter.DEFAULT).hasToString("traceId(32),spanId(16)"); + assertThat(CorrelationIdFormatter.of("a(32),b(16)")).hasToString("a(32),b(16)"); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java index c374f0d31c0e..0f5a3a44ceb1 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.junit.jupiter.api.AfterEach; @@ -36,6 +37,7 @@ * * @author Andy Wilkinson * @author Eddú Meléndez + * @author Jonatan Ivanov */ class LoggingSystemPropertiesTests { @@ -115,6 +117,25 @@ private String getSystemProperty(LoggingSystemProperty property) { return System.getProperty(property.getEnvironmentVariableName()); } + @Test + void correlationPatternIsSet() { + new LoggingSystemProperties( + new MockEnvironment().withProperty("logging.pattern.correlation", "correlation pattern")) + .apply(null); + assertThat(System.getProperty(LoggingSystemProperty.CORRELATION_PATTERN.getEnvironmentVariableName())) + .isEqualTo("correlation pattern"); + } + + @Test + void defaultValueResolverIsUsed() { + MockEnvironment environment = new MockEnvironment(); + Map defaultValues = Map + .of(LoggingSystemProperty.CORRELATION_PATTERN.getApplicationPropertyName(), "default correlation pattern"); + new LoggingSystemProperties(environment, defaultValues::get, null).apply(null); + assertThat(System.getProperty(LoggingSystemProperty.CORRELATION_PATTERN.getEnvironmentVariableName())) + .isEqualTo("default correlation pattern"); + } + private Environment environment(String key, Object value) { StandardEnvironment environment = new StandardEnvironment(); environment.getPropertySources().addLast(new MapPropertySource("test", Collections.singletonMap(key, value))); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/CorrelationIdConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/CorrelationIdConverterTests.java new file mode 100644 index 000000000000..87d9b22cf3f5 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/CorrelationIdConverterTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2023 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.logging.log4j2; + +import java.util.Map; + +import org.apache.logging.log4j.core.AbstractLogEvent; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CorrelationIdConverter}. + * + * @author Phillip Webb + */ +class CorrelationIdConverterTests { + + private CorrelationIdConverter converter = CorrelationIdConverter.newInstance(null); + + private final LogEvent event = new TestLogEvent(); + + @Test + void defaultPattern() { + StringBuilder result = new StringBuilder(); + this.converter.format(this.event, result); + assertThat(result).hasToString("[01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void customPattern() { + this.converter = CorrelationIdConverter.newInstance(new String[] { "traceId(0),spanId(0)" }); + StringBuilder result = new StringBuilder(); + this.converter.format(this.event, result); + assertThat(result).hasToString("[01234567890123456789012345678901-0123456789012345] "); + } + + static class TestLogEvent extends AbstractLogEvent { + + @Override + public ReadOnlyStringMap getContextData() { + return new JdkMapAdapterStringMap( + Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index a177de6bae83..ceb5b64650a0 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -39,6 +39,7 @@ import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.Reconfigurable; import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; +import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; import org.apache.logging.log4j.jul.Log4jBridgeHandler; import org.apache.logging.log4j.util.PropertiesUtil; @@ -47,12 +48,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.MDC; import org.springframework.boot.logging.AbstractLoggingSystemTests; +import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; +import org.springframework.boot.logging.LoggingSystemProperties; import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.logging.ConfigureClasspathToPreferLog4j2; @@ -86,12 +90,11 @@ @ConfigureClasspathToPreferLog4j2 class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { - private final TestLog4J2LoggingSystem loggingSystem = new TestLog4J2LoggingSystem(); + private TestLog4J2LoggingSystem loggingSystem; - private final MockEnvironment environment = new MockEnvironment(); + private MockEnvironment environment; - private final LoggingInitializationContext initializationContext = new LoggingInitializationContext( - this.environment); + private LoggingInitializationContext initializationContext; private Logger logger; @@ -99,6 +102,10 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { @BeforeEach void setup() { + PluginRegistry.getInstance().clear(); + this.loggingSystem = new TestLog4J2LoggingSystem(); + this.environment = new MockEnvironment(); + this.initializationContext = new LoggingInitializationContext(this.environment); LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); this.configuration = loggerContext.getConfiguration(); this.loggingSystem.cleanUp(); @@ -113,6 +120,7 @@ void cleanUp() { loggerContext.stop(); loggerContext.start(((Reconfigurable) this.configuration).reconfigure()); cleanUpPropertySources(); + PluginRegistry.getInstance().clear(); } @SuppressWarnings("unchecked") @@ -495,6 +503,67 @@ void nonFileUrlsAreResolvedUsingLog4J2UrlConnectionFactory() { .withMessageContaining("http has not been enabled"); } + @Test + void correlationLoggingToFileWhenExpectCorrelationIdTrueAndMdcContent() { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + new LoggingSystemProperties(this.environment).apply(); + File file = new File(tmpDir(), "log4j2-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, logFile); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")) + .contains(" [01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void correlationLoggingToConsoleWhenExpectCorrelationIdTrueAndMdcContent(CapturedOutput output) { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")) + .contains(" [01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void correlationLoggingToConsoleWhenExpectCorrelationIdFalseAndMdcContent(CapturedOutput output) { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "false"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).doesNotContain("0123456789012345"); + } + + @Test + void correlationLoggingToConsoleWhenExpectCorrelationIdTrueAndNoMdcContent(CapturedOutput output) { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")) + .contains(" [ ] "); + } + + @Test + void correlationLoggingToConsoleWhenHasCorrelationPattern(CapturedOutput output) { + this.environment.setProperty("logging.pattern.correlation", "%correlationId{spanId(0),traceId(0)}"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")) + .contains(" [0123456789012345-01234567890123456789012345678901] "); + } + private String getRelativeClasspathLocation(String fileName) { String defaultPath = ClassUtils.getPackageName(getClass()); defaultPath = defaultPath.replace('.', '/'); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java index cc945ae690a5..47f746592c03 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -27,6 +27,8 @@ class TestLog4J2LoggingSystem extends Log4J2LoggingSystem { private final List availableClasses = new ArrayList<>(); + private String[] standardConfigLocations; + TestLog4J2LoggingSystem() { super(TestLog4J2LoggingSystem.class.getClassLoader()); } @@ -44,4 +46,18 @@ void availableClasses(String... classNames) { Collections.addAll(this.availableClasses, classNames); } + @Override + protected String[] getStandardConfigLocations() { + return (this.standardConfigLocations != null) ? this.standardConfigLocations + : super.getStandardConfigLocations(); + } + + void setStandardConfigLocations(boolean standardConfigLocations) { + this.standardConfigLocations = (!standardConfigLocations) ? new String[0] : null; + } + + void setStandardConfigLocations(String[] standardConfigLocations) { + this.standardConfigLocations = standardConfigLocations; + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/CorrelationIdConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/CorrelationIdConverterTests.java new file mode 100644 index 000000000000..061251739dc7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/CorrelationIdConverterTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2023 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.logging.logback; + +import java.util.List; +import java.util.Map; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.LoggingEvent; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CorrelationIdConverter}. + * + * @author Phillip Webb + */ +class CorrelationIdConverterTests { + + private final CorrelationIdConverter converter; + + private final LoggingEvent event = new LoggingEvent(); + + CorrelationIdConverterTests() { + this.converter = new CorrelationIdConverter(); + this.converter.setContext(new LoggerContext()); + } + + @Test + void defaultPattern() { + addMdcProperties(this.event); + this.converter.start(); + String converted = this.converter.convert(this.event); + this.converter.stop(); + assertThat(converted).isEqualTo("[01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void customPattern() { + this.converter.setOptionList(List.of("traceId(0)", "spanId(0)")); + addMdcProperties(this.event); + this.converter.start(); + String converted = this.converter.convert(this.event); + this.converter.stop(); + assertThat(converted).isEqualTo("[01234567890123456789012345678901-0123456789012345] "); + } + + private void addMdcProperties(LoggingEvent event) { + event.setMDCPropertyMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 515d09a1e197..8673dbd701cf 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -45,6 +45,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.slf4j.bridge.SLF4JBridgeHandler; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; @@ -87,6 +88,7 @@ * @author Robert Thornton * @author Eddú Meléndez * @author Scott Frederick + * @author Jonatan Ivanov */ @ExtendWith(OutputCaptureExtension.class) class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { @@ -547,6 +549,7 @@ void initializeShouldApplyLogbackSystemPropertiesToTheContext() { (field) -> expectedProperties.add((String) field.get(null)), this::isPublicStaticFinal); expectedProperties.removeAll(Arrays.asList("LOG_FILE", "LOG_PATH")); expectedProperties.add("org.jboss.logging.provider"); + expectedProperties.add("LOG_CORRELATION_PATTERN"); assertThat(properties).containsOnlyKeys(expectedProperties); assertThat(properties).containsEntry("CONSOLE_LOG_CHARSET", Charset.defaultCharset().name()); } @@ -679,8 +682,81 @@ void springProfileIfNestedWithinSecondPhaseElementSanityChecker(CapturedOutput o assertThat(output).contains(" elements cannot be nested within an"); } + @Test + void correlationLoggingToFileWhenExpectCorrelationIdTrueAndMdcContent() { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + File file = new File(tmpDir(), "logback-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + initialize(this.initializationContext, null, logFile); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")) + .contains(" [01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void correlationLoggingToConsoleWhenExpectCorrelationIdTrueAndMdcContent(CapturedOutput output) { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + initialize(this.initializationContext, null, null); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")) + .contains(" [01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void correlationLoggingToConsoleWhenExpectCorrelationIdFalseAndMdcContent(CapturedOutput output) { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "false"); + initialize(this.initializationContext, null, null); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).doesNotContain("0123456789012345"); + } + + @Test + void correlationLoggingToConsoleWhenExpectCorrelationIdTrueAndNoMdcContent(CapturedOutput output) { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")) + .contains(" [ ] "); + } + + @Test + void correlationLoggingToConsoleWhenHasCorrelationPattern(CapturedOutput output) { + this.environment.setProperty("logging.pattern.correlation", "%correlationId{spanId(0),traceId(0)}"); + initialize(this.initializationContext, null, null); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")) + .contains(" [0123456789012345-01234567890123456789012345678901] "); + } + + @Test + void correlationLoggingToConsoleWhenUsingXmlConfiguration(CapturedOutput output) { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + initialize(this.initializationContext, "classpath:logback-include-base.xml", null); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")) + .contains(" [01234567890123456789012345678901-0123456789012345] "); + } + + @Test + void correlationLoggingToConsoleWhenUsingFileConfiguration() { + this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true"); + File file = new File(tmpDir(), "logback-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + initialize(this.initializationContext, "classpath:logback-include-base.xml", logFile); + MDC.setContextMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345")); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")) + .contains(" [01234567890123456789012345678901-0123456789012345] "); + } + private void initialize(LoggingInitializationContext context, String configLocation, LogFile logFile) { this.loggingSystem.getSystemProperties((ConfigurableEnvironment) context.getEnvironment()).apply(logFile); + this.loggingSystem.beforeInitialize(); this.loggingSystem.initialize(context, configLocation, logFile); } @@ -702,15 +778,4 @@ private static SizeAndTimeBasedRollingPolicy getRollingPolicy() { return (SizeAndTimeBasedRollingPolicy) getFileAppender().getRollingPolicy(); } - private String getLineWithText(File file, CharSequence outputSearch) { - return getLineWithText(contentOf(file), outputSearch); - } - - private String getLineWithText(CharSequence output, CharSequence outputSearch) { - return Arrays.stream(output.toString().split("\\r?\\n")) - .filter((line) -> line.contains(outputSearch)) - .findFirst() - .orElse(null); - } - } From 493777d3c950ca0e9278dbededf9a48f41cffefc Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 21 Jun 2023 21:56:20 -0700 Subject: [PATCH 0062/1656] Include the application name on each log line when it is available Update Logback and Log4J2 so that they include the application name on each log line. If `spring.application.name` had not been set, or if `logging.include-application-name` is `false` then the name is not logged. Closes gh-35593 --- .../spring-boot-docs/build.gradle | 2 +- .../src/docs/asciidoc/features/logging.adoc | 2 ++ .../boot/logging/LoggingSystemProperties.java | 12 +++++++++++ .../boot/logging/LoggingSystemProperty.java | 5 +++++ .../logback/DefaultLogbackConfiguration.java | 4 ++-- ...itional-spring-configuration-metadata.json | 7 +++++++ .../boot/logging/log4j2/log4j2-file.xml | 4 ++-- .../boot/logging/log4j2/log4j2.xml | 4 ++-- .../boot/logging/logback/defaults.xml | 4 ++-- .../logging/LoggingSystemPropertiesTests.java | 19 +++++++++++++++++ .../log4j2/Log4J2LoggingSystemTests.java | 21 +++++++++++++++++++ .../logback/LogbackLoggingSystemTests.java | 17 +++++++++++++++ .../src/main/resources/application.properties | 1 + .../src/main/resources/log4j2.xml | 2 +- .../src/main/resources/application.properties | 1 + 15 files changed, 95 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 3d0d6ddcd8a5..baaf0f823289 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -293,7 +293,7 @@ task runSpringApplicationExample(type: org.springframework.boot.build.docs.Appli task runLoggingFormatExample(type: org.springframework.boot.build.docs.ApplicationRunner) { classpath = configurations.springApplicationExample + sourceSets.main.output mainClass = "org.springframework.boot.docs.features.springapplication.MyApplication" - args = ["--spring.main.banner-mode=off", "--server.port=0"] + args = ["--spring.main.banner-mode=off", "--server.port=0", "--spring.application.name=myapp"] output = file("$buildDir/example-output/logging-format.txt") expectedLogging = "Started MyApplication in " normalizeTomcatPort() diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc index b1d81e5ef07a..102998a9d8c9 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc @@ -31,6 +31,7 @@ The following items are output: * Log Level: `ERROR`, `WARN`, `INFO`, `DEBUG`, or `TRACE`. * Process ID. * A `---` separator to distinguish the start of actual log messages. +* Application name: Enclosed in square brackets (logged by default only if configprop:spring.application.name[] is set) * Thread name: Enclosed in square brackets (may be truncated for console output). * Correlation ID: If tracing is enabled (not shown in the sample above) * Logger name: This is usually the source class name (often abbreviated). @@ -39,6 +40,7 @@ The following items are output: NOTE: Logback does not have a `FATAL` level. It is mapped to `ERROR`. +TIP: If you have a configprop:spring.application.name[] property but don't want it logged you can set configprop:logging.include-application-name[] to `false`. [[features.logging.console-output]] diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java index 8b1b20f57f10..17b2e1a3aec9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java @@ -27,6 +27,7 @@ import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySourcesPropertyResolver; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Utility to set system properties that can later be used by log configuration files. @@ -227,6 +228,7 @@ private PropertyResolver getPropertyResolver() { protected void apply(LogFile logFile, PropertyResolver resolver) { String defaultCharsetName = getDefaultCharset().name(); + setApplicationNameSystemProperty(resolver); setSystemProperty(LoggingSystemProperty.PID, new ApplicationPid().toString()); setSystemProperty(LoggingSystemProperty.CONSOLE_CHARSET, resolver, defaultCharsetName); setSystemProperty(LoggingSystemProperty.FILE_CHARSET, resolver, defaultCharsetName); @@ -243,6 +245,16 @@ protected void apply(LogFile logFile, PropertyResolver resolver) { } } + private void setApplicationNameSystemProperty(PropertyResolver resolver) { + if (resolver.getProperty("logging.include-application-name", Boolean.class, Boolean.TRUE)) { + String applicationName = resolver.getProperty("spring.application.name"); + if (StringUtils.hasText(applicationName)) { + setSystemProperty(LoggingSystemProperty.APPLICATION_NAME.getEnvironmentVariableName(), + "[%s] ".formatted(applicationName)); + } + } + } + private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver) { setSystemProperty(property, resolver, null); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java index b835feeb547d..489ebec89feb 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java @@ -25,6 +25,11 @@ */ public enum LoggingSystemProperty { + /** + * Logging system property for the application name that should be logged. + */ + APPLICATION_NAME("LOGGED_APPLICATION_NAME"), + /** * Logging system property for the process ID. */ diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java index c86cd372e5f9..0298aa6325ff 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -75,7 +75,7 @@ private void defaults(LogbackConfigurator config) { config.getContext() .putProperty("CONSOLE_LOG_PATTERN", resolve(config, "${CONSOLE_LOG_PATTERN:-" + "%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) " - + "%clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} " + + "%clr(${PID:- }){magenta} %clr(---){faint} %clr(${LOGGED_APPLICATION_NAME:-}[%15.15t]){faint} " + "%clr(${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} " + "%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}")); String defaultCharset = Charset.defaultCharset().name(); @@ -84,7 +84,7 @@ private void defaults(LogbackConfigurator config) { config.getContext().putProperty("CONSOLE_LOG_THRESHOLD", resolve(config, "${CONSOLE_LOG_THRESHOLD:-TRACE}")); config.getContext() .putProperty("FILE_LOG_PATTERN", resolve(config, "${FILE_LOG_PATTERN:-" - + "%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] " + + "%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- ${LOGGED_APPLICATION_NAME:-}[%t] " + "${LOG_CORRELATION_PATTERN:-}" + "%-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}")); config.getContext() diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 17c21808424d..a22819a934c5 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -103,6 +103,13 @@ "description": "Log groups to quickly change multiple loggers at the same time. For instance, `logging.group.db=org.hibernate,org.springframework.jdbc`.", "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener" }, + { + "name": "logging.include-application-name", + "type": "java.lang.String>", + "description": "Whether to include the application name in the logs.", + "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener", + "defaultValue": true + }, { "name": "logging.level", "type": "java.util.Map", diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml index ee6812d92a66..fb3edde9dfe7 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml @@ -4,8 +4,8 @@ %xwEx %5p yyyy-MM-dd'T'HH:mm:ss.SSSXXX - %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- [%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- ${sys:LOGGED_APPLICATION_NAME}[%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml index de8588498e1e..600f2fa207ed 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml @@ -4,8 +4,8 @@ %xwEx %5p yyyy-MM-dd'T'HH:mm:ss.SSSXXX - %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- [%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME}[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- ${sys:LOGGED_APPLICATION_NAME}[%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml index d10022ca0705..9c02f84e4099 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml @@ -10,10 +10,10 @@ Default logback configuration provided for import - + - + diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java index 0f5a3a44ceb1..2c5a1aaf2855 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java @@ -136,6 +136,25 @@ void defaultValueResolverIsUsed() { .isEqualTo("default correlation pattern"); } + @Test + void loggedApplicationNameWhenHasApplicationName() { + new LoggingSystemProperties(new MockEnvironment().withProperty("spring.application.name", "test")).apply(null); + assertThat(getSystemProperty(LoggingSystemProperty.APPLICATION_NAME)).isEqualTo("[test] "); + } + + @Test + void loggedApplicationNameWhenHasNoApplicationName() { + new LoggingSystemProperties(new MockEnvironment()).apply(null); + assertThat(getSystemProperty(LoggingSystemProperty.APPLICATION_NAME)).isNull(); + } + + @Test + void loggedApplicationNameWhenApplicationNameLoggingDisabled() { + new LoggingSystemProperties(new MockEnvironment().withProperty("spring.application.name", "test") + .withProperty("logging.include-application-name", "false")).apply(null); + assertThat(getSystemProperty(LoggingSystemProperty.APPLICATION_NAME)).isNull(); + } + private Environment environment(String key, Object value) { StandardEnvironment environment = new StandardEnvironment(); environment.getPropertySources().addLast(new MapPropertySource("test", Collections.singletonMap(key, value))); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index ceb5b64650a0..fdd2add6889f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -564,6 +564,27 @@ void correlationLoggingToConsoleWhenHasCorrelationPattern(CapturedOutput output) .contains(" [0123456789012345-01234567890123456789012345678901] "); } + @Test + void applicationNameLoggingWhenHasApplicationName(CapturedOutput output) { + this.environment.setProperty("spring.application.name", "myapp"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).contains("[myapp] "); + } + + @Test + void applicationNameLoggingWhenDisabled(CapturedOutput output) { + this.environment.setProperty("spring.application.name", "myapp"); + this.environment.setProperty("logging.include-application-name", "false"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).doesNotContain("myapp"); + } + private String getRelativeClasspathLocation(String fileName) { String defaultPath = ClassUtils.getPackageName(getClass()); defaultPath = defaultPath.replace('.', '/'); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 8673dbd701cf..685923ead60c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -754,6 +754,23 @@ void correlationLoggingToConsoleWhenUsingFileConfiguration() { .contains(" [01234567890123456789012345678901-0123456789012345] "); } + @Test + void applicationNameLoggingWhenHasApplicationName(CapturedOutput output) { + this.environment.setProperty("spring.application.name", "myapp"); + initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).contains("[myapp] "); + } + + @Test + void applicationNameLoggingWhenDisabled(CapturedOutput output) { + this.environment.setProperty("spring.application.name", "myapp"); + this.environment.setProperty("logging.include-application-name", "false"); + initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).doesNotContain("myapp"); + } + private void initialize(LoggingInitializationContext context, String configLocation, LogFile logFile) { this.loggingSystem.getSystemProperties((ConfigurableEnvironment) context.getEnvironment()).apply(logFile); this.loggingSystem.beforeInitialize(); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/application.properties index 2583f7fcefd9..d622bb1f7ca4 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/application.properties +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/application.properties @@ -1,3 +1,4 @@ +spring.application.name=sample spring.security.user.name=user spring.security.user.password=password management.endpoint.shutdown.enabled=true diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml index 5320cd61c746..1c84d286b093 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml @@ -2,7 +2,7 @@ ???? - %clr{%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx + %clr{%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties index 81cc777bfc89..2c35d22ff033 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties @@ -1,3 +1,4 @@ +spring.application.name=sample service.name=Phil spring.security.user.name=user From 228b8eb8e4285cb79549e27842cf705acb6c69e9 Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Thu, 22 Jun 2023 14:43:55 -0700 Subject: [PATCH 0063/1656] Polish log correlation docs Docs related to gh-33280 (log correlation) and gh-35593 (application name in each log line) need some polishing: - Fix project names - Show how to avoid having the application name duplicated in logs - Call out that a trailing space is needed in the correlation pattern Closes gh-36035 See gh-33280 See gh-35593 --- .../src/docs/asciidoc/actuator/tracing.adoc | 14 +++++++++----- .../spring-boot-smoke-test-actuator/build.gradle | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc index 0b9378d1acd5..53ab1290e237 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc @@ -69,22 +69,26 @@ Press the "Show" button to see the details of that trace. [[actuator.micrometer-tracing.logging]] === Logging Correlation IDs -Correlation IDs provide a helpful way to link lines in your log files to distributed traces. -By default, as long as configprop:management.tracing.enabled[] has not been set to `false`, Spring Boot will include correlation IDs in your logs whenever you are using Micrometer tracing. +Correlation IDs provide a helpful way to link lines in your log files to spans/traces. +By default, as long as configprop:management.tracing.enabled[] has not been set to `false`, Spring Boot will include correlation IDs in your logs whenever you are using Micrometer Tracing. The default correlation ID is built from `traceId` and `spanId` https://logback.qos.ch/manual/mdc.html[MDC] values. -For example, if Micrometer tracing has added an MDC `traceId` of `803B448A0489F84084905D3093480352` and an MDC `spanId` of `3425F23BB2432450` the log output will include the correlation ID `[803B448A0489F84084905D3093480352-3425F23BB2432450]`. +For example, if Micrometer Tracing has added an MDC `traceId` of `803B448A0489F84084905D3093480352` and an MDC `spanId` of `3425F23BB2432450` the log output will include the correlation ID `[803B448A0489F84084905D3093480352-3425F23BB2432450]`. If you prefer to use a different format for your correlation ID, you can use the configprop:logging.pattern.correlation[] property to define one. -For example, the following will provide a correlation ID for Logback in format previously used by Spring Sleuth: +For example, the following will provide a correlation ID for Logback in format previously used by Spring Cloud Sleuth: [source,yaml,indent=0,subs="verbatim",configprops,configblocks] ---- logging: pattern: - correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}]" + correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] " + include-application-name: false ---- +NOTE: In the example above, configprop:logging.include-application-name[] is set to `false` to avoid the application name being duplicated in the log messages (configprop:logging.pattern.correlation[] already contains it). +It's also worth mentioning that configprop:logging.pattern.correlation[] contains a trailing space so that it is separated from the logger name that comes right after it by default. + [[actuator.micrometer-tracing.tracer-implementations]] diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle index b616d3a9697a..656ad05a752d 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle @@ -11,6 +11,7 @@ dependencies { implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-validation")) + implementation 'io.micrometer:micrometer-tracing-bridge-brave' runtimeOnly("com.h2database:h2") From 8f7fdc507e0fa3a4a8d5849832daf50cb9af55a4 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 23 Jun 2023 08:26:32 +0200 Subject: [PATCH 0064/1656] Polish CorrelationIdFormatter --- .../springframework/boot/logging/CorrelationIdFormatter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java index 5270833a6c90..5dcab9f4d503 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java @@ -47,7 +47,7 @@ *

* Correlation IDs are formatted as dash separated strings surrounded in square brackets. * Formatted output is always of a fixed width and with trailing whitespace. Dashes are - * omitted of none of the named items can be resolved. + * omitted if none of the named items can be resolved. *

* The following example would return a formatted result of * {@code "[01234567890123456789012345678901-0123456789012345] "}:

@@ -164,7 +164,7 @@ public static CorrelationIdFormatter of(Collection spec) {
 	 * @param name the name of the correlation part
 	 * @param length the expected length of the correlation part
 	 */
-	static final record Part(String name, int length) {
+	record Part(String name, int length) {
 
 		private static final Pattern pattern = Pattern.compile("^(.+?)\\((\\d+)\\)?$");
 

From 50d8b20d6c496481bf1bd042e234bdd2dd557119 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Fri, 23 Jun 2023 16:01:26 +0100
Subject: [PATCH 0065/1656] Re-enable forward merge issues for merges into main

---
 git/hooks/prepare-forward-merge | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/git/hooks/prepare-forward-merge b/git/hooks/prepare-forward-merge
index dea35e7391d8..786afcfb312a 100755
--- a/git/hooks/prepare-forward-merge
+++ b/git/hooks/prepare-forward-merge
@@ -27,7 +27,7 @@ end
 def rewrite_message(message_file, fixed)
   current_branch = `git rev-parse --abbrev-ref HEAD`.strip
   if current_branch == "main"
-    return nil
+    current_branch = $main_branch
   end
   rewritten_message = ""
   message = File.read(message_file)
@@ -68,6 +68,4 @@ end
 $log.debug "Searching for forward merge"
 fixed = get_fixed_issues()
 rewritten_message = rewrite_message(message_file, fixed)
-unless rewritten_message.nil?
-  File.write(message_file, rewritten_message)
-end
+File.write(message_file, rewritten_message)

From b645eb32ac14af60f9acfa6866b20bc9b15e2940 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Fri, 23 Jun 2023 17:28:22 +0100
Subject: [PATCH 0066/1656] Remove deprecated code that was to be removed in
 3.2

Closes gh-36034
---
 ...mpositeHealthContributorConfiguration.java |  45 ----
 ...mpositeHealthContributorConfiguration.java |  14 +-
 ...eactiveHealthContributorConfiguration.java |  14 +-
 .../metrics/MetricsProperties.java            |  53 -----
 .../JerseyServerMetricsAutoConfiguration.java |  23 +-
 .../observation/ObservationProperties.java    |  10 +-
 .../GraphQlObservationAutoConfiguration.java  |   1 -
 ...lientHttpObservationConventionAdapter.java |  62 -----
 .../ClientObservationConventionAdapter.java   |  76 ------
 ...tpClientObservationsAutoConfiguration.java |   5 +-
 .../RestTemplateObservationConfiguration.java |  34 +--
 .../WebClientObservationConfiguration.java    |  34 +--
 ...erRequestObservationConventionAdapter.java |  79 -------
 .../WebFluxObservationAutoConfiguration.java  |  43 +---
 ...erRequestObservationConventionAdapter.java |  76 ------
 .../WebMvcObservationAutoConfiguration.java   |  53 +----
 ...itional-spring-configuration-metadata.json |  20 +-
 ...ntributorConfigurationReflectionTests.java |  57 -----
 ...ntributorConfigurationReflectionTests.java |  60 -----
 ...HttpObservationConventionAdapterTests.java |  90 -------
 ...ientObservationConventionAdapterTests.java | 100 --------
 ...TemplateObservationConfigurationTests.java |  59 +----
 ...ebClientObservationConfigurationTests.java |  24 +-
 ...uestObservationConventionAdapterTests.java |  64 -----
 ...FluxObservationAutoConfigurationTests.java |  73 ------
 ...uestObservationConventionAdapterTests.java | 111 ---------
 ...bMvcObservationAutoConfigurationTests.java |  81 -------
 .../PrometheusPushGatewayManager.java         |   9 +-
 ...faultRestTemplateExchangeTagsProvider.java |  49 ----
 .../web/client/RestTemplateExchangeTags.java  | 144 ------------
 .../RestTemplateExchangeTagsProvider.java     |  49 ----
 .../DefaultWebClientExchangeTagsProvider.java |  49 ----
 .../client/WebClientExchangeTags.java         | 127 ----------
 .../client/WebClientExchangeTagsProvider.java |  46 ----
 .../server/DefaultWebFluxTagsProvider.java    |  89 -------
 .../web/reactive/server/WebFluxTags.java      | 191 ---------------
 .../server/WebFluxTagsContributor.java        |  44 ----
 .../reactive/server/WebFluxTagsProvider.java  |  44 ----
 .../web/reactive/server/package-info.java     |  20 --
 .../servlet/DefaultWebMvcTagsProvider.java    |  94 --------
 .../metrics/web/servlet/WebMvcTags.java       | 193 ---------------
 .../web/servlet/WebMvcTagsContributor.java    |  58 -----
 .../web/servlet/WebMvcTagsProvider.java       |  58 -----
 .../metrics/web/servlet/package-info.java     |  20 --
 .../DefaultWebMvcTagsProviderTests.java       | 120 ----------
 .../endpoint/web/servlet/WebMvcTagsTests.java | 184 ---------------
 .../PrometheusPushGatewayManagerTests.java    |  12 -
 .../client/RestTemplateExchangeTagsTests.java | 118 ----------
 ...ultWebClientExchangeTagsProviderTests.java | 100 --------
 .../client/WebClientExchangeTagsTests.java    | 182 ---------------
 .../DefaultWebFluxTagsProviderTests.java      |  82 -------
 .../web/reactive/server/WebFluxTagsTests.java | 220 ------------------
 .../flyway/FlywayAutoConfiguration.java       |  10 -
 .../liquibase/LiquibaseProperties.java        |  12 -
 .../session/SessionAutoConfiguration.java     |   2 +-
 .../autoconfigure/web/ServerProperties.java   |  29 +--
 .../NettyWebServerFactoryCustomizer.java      |  10 -
 .../web/servlet/WebMvcAutoConfiguration.java  |  19 --
 .../web/servlet/WebMvcProperties.java         |  18 --
 ...itional-spring-configuration-metadata.json |  33 ++-
 .../LiquibaseAutoConfigurationTests.java      |   8 -
 .../web/ServerPropertiesTests.java            |  26 ---
 .../JettyWebServerFactoryCustomizerTests.java |  24 --
 .../NettyWebServerFactoryCustomizerTests.java |  12 -
 ...TomcatWebServerFactoryCustomizerTests.java |  51 ----
 ...dertowWebServerFactoryCustomizerTests.java |  12 +-
 ...ervletWebServerFactoryCustomizerTests.java |   5 +-
 .../servlet/WebMvcAutoConfigurationTests.java |  52 -----
 ...endencyInjectionTestExecutionListener.java |  63 -----
 .../actuate/metrics/AutoConfigureMetrics.java |  45 ----
 .../actuate/metrics/package-info.java         |  20 --
 ...nfigureMetricsMissingIntegrationTests.java |  53 -----
 ...nfigureMetricsPresentIntegrationTests.java |  53 -----
 ...ConfigureMetricsSpringBootApplication.java |  37 ---
 ...ltTestExecutionListenersPostProcessor.java |  48 ----
 .../SpringBootTestContextBootstrapper.java    |  14 --
 ...stContextBootstrapperIntegrationTests.java |   7 -
 ...ltTestExecutionListenersPostProcessor.java |  52 -----
 .../PropertyDescriptorResolverTests.java      |  11 -
 .../DeprecatedConstructorBinding.java         |  39 ----
 ...edImmutableMultiConstructorProperties.java |  46 ----
 .../properties/ConfigurationProperties.java   |   3 +-
 .../properties/ConstructorBinding.java        |  52 -----
 .../context/properties/ConstructorBound.java  |   3 +-
 .../boot/jackson/JsonMixinModule.java         |  21 --
 .../ClientHttpRequestFactorySupplier.java     |  41 ----
 .../boot/web/client/RestTemplateBuilder.java  |   5 +-
 .../AbstractServletWebServerFactory.java      |   5 +-
 .../boot/web/servlet/server/Session.java      |  32 +--
 .../ConfigurationPropertiesBeanTests.java     |  32 ---
 .../boot/jackson/JsonMixinModuleTests.java    |  10 -
 .../TomcatServletWebServerFactoryTests.java   |   5 -
 .../AbstractServletWebServerFactoryTests.java |  14 --
 93 files changed, 117 insertions(+), 4580 deletions(-)
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapter.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationReflectionTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationReflectionTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapterTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapterTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/package-info.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTags.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsProvider.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/package-info.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcTagsTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java
 delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java
 delete mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/SpringBootDependencyInjectionTestExecutionListener.java
 delete mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetrics.java
 delete mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/package-info.java
 delete mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsMissingIntegrationTests.java
 delete mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsPresentIntegrationTests.java
 delete mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsSpringBootApplication.java
 delete mode 100644 spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/DefaultTestExecutionListenersPostProcessor.java
 delete mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/TestDefaultTestExecutionListenersPostProcessor.java
 delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java
 delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java
 delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java
 delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySupplier.java

diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java
index 5a7454b08a50..3b5aeda866da 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java
@@ -16,12 +16,9 @@
 
 package org.springframework.boot.actuate.autoconfigure.health;
 
-import java.lang.reflect.Constructor;
 import java.util.Map;
 import java.util.function.Function;
 
-import org.springframework.beans.BeanUtils;
-import org.springframework.core.ResolvableType;
 import org.springframework.util.Assert;
 
 /**
@@ -39,18 +36,6 @@ public abstract class AbstractCompositeHealthContributorConfiguration indicatorFactory;
 
-	/**
-	 * Creates a {@code AbstractCompositeHealthContributorConfiguration} that will use
-	 * reflection to create health indicator instances.
-	 * @deprecated since 3.0.0 in favor of
-	 * {@link #AbstractCompositeHealthContributorConfiguration(Function)}
-	 */
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	protected AbstractCompositeHealthContributorConfiguration() {
-		this.indicatorFactory = new ReflectionIndicatorFactory(
-				ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class, getClass()));
-	}
-
 	/**
 	 * Creates a {@code AbstractCompositeHealthContributorConfiguration} that will use the
 	 * given {@code indicatorFactory} to create health indicator instances.
@@ -75,34 +60,4 @@ protected I createIndicator(B bean) {
 		return this.indicatorFactory.apply(bean);
 	}
 
-	private class ReflectionIndicatorFactory implements Function {
-
-		private final Class indicatorType;
-
-		private final Class beanType;
-
-		ReflectionIndicatorFactory(ResolvableType type) {
-			this.indicatorType = type.resolveGeneric(1);
-			this.beanType = type.resolveGeneric(2);
-		}
-
-		@Override
-		public I apply(B bean) {
-			try {
-				return BeanUtils.instantiateClass(getConstructor(), bean);
-			}
-			catch (Exception ex) {
-				throw new IllegalStateException("Unable to create health indicator %s for bean type %s"
-					.formatted(this.indicatorType, this.beanType), ex);
-			}
-
-		}
-
-		@SuppressWarnings("unchecked")
-		private Constructor getConstructor() throws NoSuchMethodException {
-			return (Constructor) this.indicatorType.getDeclaredConstructor(this.beanType);
-		}
-
-	}
-
 }
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java
index 7901e1307552..4b979e94d19e 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2022 the original author or authors.
+ * Copyright 2012-2023 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.
@@ -36,18 +36,6 @@
 public abstract class CompositeHealthContributorConfiguration
 		extends AbstractCompositeHealthContributorConfiguration {
 
-	/**
-	 * Creates a {@code CompositeHealthContributorConfiguration} that will use reflection
-	 * to create {@link HealthIndicator} instances.
-	 * @deprecated since 3.0.0 in favor of
-	 * {@link #CompositeHealthContributorConfiguration(Function)}
-	 */
-	@SuppressWarnings("removal")
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	public CompositeHealthContributorConfiguration() {
-		super();
-	}
-
 	/**
 	 * Creates a {@code CompositeHealthContributorConfiguration} that will use the given
 	 * {@code indicatorFactory} to create {@link HealthIndicator} instances.
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java
index 57b45ff1a10f..12c4ff22a88e 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2022 the original author or authors.
+ * Copyright 2012-2023 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.
@@ -36,18 +36,6 @@
 public abstract class CompositeReactiveHealthContributorConfiguration
 		extends AbstractCompositeHealthContributorConfiguration {
 
-	/**
-	 * Creates a {@code CompositeReactiveHealthContributorConfiguration} that will use
-	 * reflection to create {@link ReactiveHealthIndicator} instances.
-	 * @deprecated since 3.0.0 in favor of
-	 * {@link #CompositeReactiveHealthContributorConfiguration(Function)}
-	 */
-	@SuppressWarnings("removal")
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	public CompositeReactiveHealthContributorConfiguration() {
-		super();
-	}
-
 	/**
 	 * Creates a {@code CompositeReactiveHealthContributorConfiguration} that will use the
 	 * given {@code indicatorFactory} to create {@link ReactiveHealthIndicator} instances.
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java
index 771aa7ad46f4..d3994aa32893 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java
@@ -116,8 +116,6 @@ public Server getServer() {
 
 		public static class Client {
 
-			private final ClientRequest request = new ClientRequest();
-
 			/**
 			 * Maximum number of unique URI tag values allowed. After the max number of
 			 * tag values is reached, metrics with additional tag values are denied by
@@ -125,10 +123,6 @@ public static class Client {
 			 */
 			private int maxUriTags = 100;
 
-			public ClientRequest getRequest() {
-				return this.request;
-			}
-
 			public int getMaxUriTags() {
 				return this.maxUriTags;
 			}
@@ -137,32 +131,10 @@ public void setMaxUriTags(int maxUriTags) {
 				this.maxUriTags = maxUriTags;
 			}
 
-			public static class ClientRequest {
-
-				/**
-				 * Name of the metric for sent requests.
-				 */
-				private String metricName = "http.client.requests";
-
-				@Deprecated(since = "3.0.0", forRemoval = true)
-				@DeprecatedConfigurationProperty(replacement = "management.observations.http.client.requests.name")
-				public String getMetricName() {
-					return this.metricName;
-				}
-
-				@Deprecated(since = "3.0.0", forRemoval = true)
-				public void setMetricName(String metricName) {
-					this.metricName = metricName;
-				}
-
-			}
-
 		}
 
 		public static class Server {
 
-			private final ServerRequest request = new ServerRequest();
-
 			/**
 			 * Maximum number of unique URI tag values allowed. After the max number of
 			 * tag values is reached, metrics with additional tag values are denied by
@@ -170,10 +142,6 @@ public static class Server {
 			 */
 			private int maxUriTags = 100;
 
-			public ServerRequest getRequest() {
-				return this.request;
-			}
-
 			public int getMaxUriTags() {
 				return this.maxUriTags;
 			}
@@ -182,27 +150,6 @@ public void setMaxUriTags(int maxUriTags) {
 				this.maxUriTags = maxUriTags;
 			}
 
-			public static class ServerRequest {
-
-				/**
-				 * Name of the metric for received requests.
-				 */
-				private String metricName = "http.server.requests";
-
-				@Deprecated(since = "3.0.0", forRemoval = true)
-				@DeprecatedConfigurationProperty(replacement = "management.observations.http.server.requests.name")
-				public String getMetricName() {
-					return this.metricName;
-				}
-
-				@Deprecated(since = "3.0.0", forRemoval = true)
-				@DeprecatedConfigurationProperty(replacement = "management.observations.http.server.requests.name")
-				public void setMetricName(String metricName) {
-					this.metricName = metricName;
-				}
-
-			}
-
 		}
 
 	}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java
index fdb018f2276d..654a87564e46 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java
@@ -29,9 +29,9 @@
 
 import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
 import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
-import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server;
 import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
 import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -57,13 +57,12 @@
 @ConditionalOnClass({ ResourceConfig.class, MetricsApplicationEventListener.class })
 @ConditionalOnBean({ MeterRegistry.class, ResourceConfig.class })
 @EnableConfigurationProperties(MetricsProperties.class)
-@SuppressWarnings("removal")
 public class JerseyServerMetricsAutoConfiguration {
 
-	private final MetricsProperties properties;
+	private final ObservationProperties observationProperties;
 
-	public JerseyServerMetricsAutoConfiguration(MetricsProperties properties) {
-		this.properties = properties;
+	public JerseyServerMetricsAutoConfiguration(ObservationProperties observationProperties) {
+		this.observationProperties = observationProperties;
 	}
 
 	@Bean
@@ -75,19 +74,19 @@ public DefaultJerseyTagsProvider jerseyTagsProvider() {
 	@Bean
 	public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(MeterRegistry meterRegistry,
 			JerseyTagsProvider tagsProvider) {
-		Server server = this.properties.getWeb().getServer();
-		return (config) -> config.register(new MetricsApplicationEventListener(meterRegistry, tagsProvider,
-				server.getRequest().getMetricName(), true, new AnnotationUtilsAnnotationFinder()));
+		String metricName = this.observationProperties.getHttp().getServer().getRequests().getName();
+		return (config) -> config.register(new MetricsApplicationEventListener(meterRegistry, tagsProvider, metricName,
+				true, new AnnotationUtilsAnnotationFinder()));
 	}
 
 	@Bean
 	@Order(0)
-	public MeterFilter jerseyMetricsUriTagFilter() {
-		String metricName = this.properties.getWeb().getServer().getRequest().getMetricName();
+	public MeterFilter jerseyMetricsUriTagFilter(MetricsProperties metricsProperties) {
+		String metricName = this.observationProperties.getHttp().getServer().getRequests().getName();
 		MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
 				() -> String.format("Reached the maximum number of URI tags for '%s'.", metricName));
-		return MeterFilter.maximumAllowableTags(metricName, "uri", this.properties.getWeb().getServer().getMaxUriTags(),
-				filter);
+		return MeterFilter.maximumAllowableTags(metricName, "uri",
+				metricsProperties.getWeb().getServer().getMaxUriTags(), filter);
 	}
 
 	/**
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java
index b42ecf3e2122..24d81daa4b1a 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java
@@ -91,10 +91,9 @@ public ClientRequests getRequests() {
 			public static class ClientRequests {
 
 				/**
-				 * Name of the observation for client requests. If empty, will use the
-				 * default "http.client.requests".
+				 * Name of the observation for client requests.
 				 */
-				private String name;
+				private String name = "http.client.requests";
 
 				public String getName() {
 					return this.name;
@@ -125,10 +124,9 @@ public Filter getFilter() {
 			public static class ServerRequests {
 
 				/**
-				 * Name of the observation for server requests. If empty, will use the
-				 * default "http.server.requests".
+				 * Name of the observation for server requests.
 				 */
-				private String name;
+				private String name = "http.server.requests";
 
 				public String getName() {
 					return this.name;
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java
index 5c447db00fba..86b5ed0aee35 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/graphql/GraphQlObservationAutoConfiguration.java
@@ -43,7 +43,6 @@
 @AutoConfiguration(after = ObservationAutoConfiguration.class)
 @ConditionalOnBean(ObservationRegistry.class)
 @ConditionalOnClass({ GraphQL.class, GraphQlSource.class, Observation.class })
-@SuppressWarnings("removal")
 public class GraphQlObservationAutoConfiguration {
 
 	@Bean
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java
deleted file mode 100644
index 87205527f5e2..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapter.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.observation.web.client;
-
-import io.micrometer.common.KeyValues;
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
-import org.springframework.http.client.observation.ClientRequestObservationContext;
-import org.springframework.http.client.observation.ClientRequestObservationConvention;
-
-/**
- * Adapter class that applies {@link RestTemplateExchangeTagsProvider} tags as a
- * {@link ClientRequestObservationConvention}.
- *
- * @author Brian Clozel
- */
-@SuppressWarnings({ "removal" })
-class ClientHttpObservationConventionAdapter implements ClientRequestObservationConvention {
-
-	private final String metricName;
-
-	private final RestTemplateExchangeTagsProvider tagsProvider;
-
-	ClientHttpObservationConventionAdapter(String metricName, RestTemplateExchangeTagsProvider tagsProvider) {
-		this.metricName = metricName;
-		this.tagsProvider = tagsProvider;
-	}
-
-	@Override
-	@SuppressWarnings("deprecation")
-	public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
-		Iterable tags = this.tagsProvider.getTags(context.getUriTemplate(), context.getCarrier(),
-				context.getResponse());
-		return KeyValues.of(tags, Tag::getKey, Tag::getValue);
-	}
-
-	@Override
-	public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) {
-		return KeyValues.empty();
-	}
-
-	@Override
-	public String getName() {
-		return this.metricName;
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java
deleted file mode 100644
index 0f3230a5bf8a..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.observation.web.client;
-
-import io.micrometer.common.KeyValues;
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.observation.Observation;
-
-import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
-import org.springframework.core.Conventions;
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ClientRequestObservationContext;
-import org.springframework.web.reactive.function.client.ClientRequestObservationConvention;
-import org.springframework.web.reactive.function.client.WebClient;
-
-/**
- * Adapter class that applies {@link WebClientExchangeTagsProvider} tags as a
- * {@link ClientRequestObservationConvention}.
- *
- * @author Brian Clozel
- */
-@SuppressWarnings("removal")
-class ClientObservationConventionAdapter implements ClientRequestObservationConvention {
-
-	private static final String URI_TEMPLATE_ATTRIBUTE = Conventions.getQualifiedAttributeName(WebClient.class,
-			"uriTemplate");
-
-	private final String metricName;
-
-	private final WebClientExchangeTagsProvider tagsProvider;
-
-	ClientObservationConventionAdapter(String metricName, WebClientExchangeTagsProvider tagsProvider) {
-		this.metricName = metricName;
-		this.tagsProvider = tagsProvider;
-	}
-
-	@Override
-	public boolean supportsContext(Observation.Context context) {
-		return context instanceof ClientRequestObservationContext;
-	}
-
-	@Override
-	public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
-		ClientRequest request = context.getRequest();
-		if (request == null) {
-			request = context.getCarrier().attribute(URI_TEMPLATE_ATTRIBUTE, context.getUriTemplate()).build();
-		}
-		Iterable tags = this.tagsProvider.tags(request, context.getResponse(), context.getError());
-		return KeyValues.of(tags, Tag::getKey, Tag::getValue);
-	}
-
-	@Override
-	public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) {
-		return KeyValues.empty();
-	}
-
-	@Override
-	public String getName() {
-		return this.metricName;
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java
index 23323d25238c..563a805e654c 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/HttpClientObservationsAutoConfiguration.java
@@ -65,13 +65,10 @@ static class MeterFilterConfiguration {
 
 		@Bean
 		@Order(0)
-		@SuppressWarnings("removal")
 		MeterFilter metricsHttpClientUriTagFilter(ObservationProperties observationProperties,
 				MetricsProperties metricsProperties) {
 			Client clientProperties = metricsProperties.getWeb().getClient();
-			String metricName = clientProperties.getRequest().getMetricName();
-			String observationName = observationProperties.getHttp().getClient().getRequests().getName();
-			String name = (observationName != null) ? observationName : metricName;
+			String name = observationProperties.getHttp().getClient().getRequests().getName();
 			MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(
 					() -> "Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?"
 						.formatted(name));
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java
index 0f62f2849b19..a1d2a152de56 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfiguration.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2022 the original author or authors.
+ * Copyright 2012-2023 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.
@@ -22,7 +22,6 @@
 import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
 import org.springframework.boot.actuate.metrics.web.client.ObservationRestTemplateCustomizer;
-import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.web.client.RestTemplateBuilder;
@@ -40,39 +39,16 @@
 @Configuration(proxyBeanMethods = false)
 @ConditionalOnClass(RestTemplate.class)
 @ConditionalOnBean(RestTemplateBuilder.class)
-@SuppressWarnings("removal")
 class RestTemplateObservationConfiguration {
 
 	@Bean
 	ObservationRestTemplateCustomizer observationRestTemplateCustomizer(ObservationRegistry observationRegistry,
 			ObjectProvider customConvention,
-			ObservationProperties observationProperties, MetricsProperties metricsProperties,
-			ObjectProvider optionalTagsProvider) {
-		String name = observationName(observationProperties, metricsProperties);
-		ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(),
-				name, optionalTagsProvider.getIfAvailable());
+			ObservationProperties observationProperties, MetricsProperties metricsProperties) {
+		String name = observationProperties.getHttp().getClient().getRequests().getName();
+		ClientRequestObservationConvention observationConvention = customConvention
+			.getIfAvailable(() -> new DefaultClientRequestObservationConvention(name));
 		return new ObservationRestTemplateCustomizer(observationRegistry, observationConvention);
 	}
 
-	private static String observationName(ObservationProperties observationProperties,
-			MetricsProperties metricsProperties) {
-		String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
-		String observationName = observationProperties.getHttp().getClient().getRequests().getName();
-		return (observationName != null) ? observationName : metricName;
-	}
-
-	private static ClientRequestObservationConvention createConvention(
-			ClientRequestObservationConvention customConvention, String name,
-			RestTemplateExchangeTagsProvider tagsProvider) {
-		if (customConvention != null) {
-			return customConvention;
-		}
-		else if (tagsProvider != null) {
-			return new ClientHttpObservationConventionAdapter(name, tagsProvider);
-		}
-		else {
-			return new DefaultClientRequestObservationConvention(name);
-		}
-	}
-
 }
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java
index ce531912e1dc..2df9c4bf9104 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfiguration.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2022 the original author or authors.
+ * Copyright 2012-2023 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.
@@ -22,7 +22,6 @@
 import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
 import org.springframework.boot.actuate.metrics.web.reactive.client.ObservationWebClientCustomizer;
-import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -37,39 +36,16 @@
  */
 @Configuration(proxyBeanMethods = false)
 @ConditionalOnClass(WebClient.class)
-@SuppressWarnings("removal")
 class WebClientObservationConfiguration {
 
 	@Bean
 	ObservationWebClientCustomizer observationWebClientCustomizer(ObservationRegistry observationRegistry,
 			ObjectProvider customConvention,
-			ObservationProperties observationProperties, ObjectProvider tagsProvider,
-			MetricsProperties metricsProperties) {
-		String name = observationName(observationProperties, metricsProperties);
-		ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(),
-				tagsProvider.getIfAvailable(), name);
+			ObservationProperties observationProperties, MetricsProperties metricsProperties) {
+		String name = observationProperties.getHttp().getClient().getRequests().getName();
+		ClientRequestObservationConvention observationConvention = customConvention
+			.getIfAvailable(() -> new DefaultClientRequestObservationConvention(name));
 		return new ObservationWebClientCustomizer(observationRegistry, observationConvention);
 	}
 
-	private static ClientRequestObservationConvention createConvention(
-			ClientRequestObservationConvention customConvention, WebClientExchangeTagsProvider tagsProvider,
-			String name) {
-		if (customConvention != null) {
-			return customConvention;
-		}
-		else if (tagsProvider != null) {
-			return new ClientObservationConventionAdapter(name, tagsProvider);
-		}
-		else {
-			return new DefaultClientRequestObservationConvention(name);
-		}
-	}
-
-	private static String observationName(ObservationProperties observationProperties,
-			MetricsProperties metricsProperties) {
-		String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
-		String observationName = observationProperties.getHttp().getClient().getRequests().getName();
-		return (observationName != null) ? observationName : metricName;
-	}
-
 }
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java
deleted file mode 100644
index 43689a962a05..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapter.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.observation.web.reactive;
-
-import java.util.List;
-
-import io.micrometer.common.KeyValues;
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider;
-import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor;
-import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider;
-import org.springframework.http.codec.ServerCodecConfigurer;
-import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
-import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention;
-import org.springframework.web.server.adapter.DefaultServerWebExchange;
-import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
-import org.springframework.web.server.i18n.LocaleContextResolver;
-import org.springframework.web.server.session.DefaultWebSessionManager;
-import org.springframework.web.server.session.WebSessionManager;
-
-/**
- * Adapter class that applies {@link WebFluxTagsProvider} tags as a
- * {@link ServerRequestObservationConvention}.
- *
- * @author Brian Clozel
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class ServerRequestObservationConventionAdapter implements ServerRequestObservationConvention {
-
-	private final WebSessionManager webSessionManager = new DefaultWebSessionManager();
-
-	private final ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create();
-
-	private final LocaleContextResolver localeContextResolver = new AcceptHeaderLocaleContextResolver();
-
-	private final String name;
-
-	private final WebFluxTagsProvider tagsProvider;
-
-	ServerRequestObservationConventionAdapter(String name, WebFluxTagsProvider tagsProvider) {
-		this.name = name;
-		this.tagsProvider = tagsProvider;
-	}
-
-	ServerRequestObservationConventionAdapter(String name, List contributors) {
-		this(name, new DefaultWebFluxTagsProvider(contributors));
-	}
-
-	@Override
-	public String getName() {
-		return this.name;
-	}
-
-	@Override
-	public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
-		DefaultServerWebExchange serverWebExchange = new DefaultServerWebExchange(context.getCarrier(),
-				context.getResponse(), this.webSessionManager, this.serverCodecConfigurer, this.localeContextResolver);
-		serverWebExchange.getAttributes().putAll(context.getAttributes());
-		Iterable tags = this.tagsProvider.httpRequestTags(serverWebExchange, context.getError());
-		return KeyValues.of(tags, Tag::getKey, Tag::getValue);
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java
index 17c2a9d7f274..457d2cdf3937 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java
@@ -16,8 +16,6 @@
 
 package org.springframework.boot.actuate.autoconfigure.observation.web.reactive;
 
-import java.util.List;
-
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.config.MeterFilter;
 import io.micrometer.observation.Observation;
@@ -31,8 +29,6 @@
 import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
-import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor;
-import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -66,48 +62,23 @@
 @SuppressWarnings("removal")
 public class WebFluxObservationAutoConfiguration {
 
-	private final MetricsProperties metricsProperties;
-
 	private final ObservationProperties observationProperties;
 
-	public WebFluxObservationAutoConfiguration(MetricsProperties metricsProperties,
-			ObservationProperties observationProperties) {
-		this.metricsProperties = metricsProperties;
+	public WebFluxObservationAutoConfiguration(ObservationProperties observationProperties) {
 		this.observationProperties = observationProperties;
 	}
 
 	@Bean
 	@ConditionalOnMissingBean(ServerHttpObservationFilter.class)
 	public OrderedServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry,
-			ObjectProvider customConvention,
-			ObjectProvider tagConfigurer,
-			ObjectProvider contributorsProvider) {
-		String observationName = this.observationProperties.getHttp().getServer().getRequests().getName();
-		String metricName = this.metricsProperties.getWeb().getServer().getRequest().getMetricName();
-		String name = (observationName != null) ? observationName : metricName;
-		WebFluxTagsProvider tagsProvider = tagConfigurer.getIfAvailable();
-		List tagsContributors = contributorsProvider.orderedStream().toList();
-		ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name,
-				tagsProvider, tagsContributors);
+			ObjectProvider customConvention) {
+		String name = this.observationProperties.getHttp().getServer().getRequests().getName();
+		ServerRequestObservationConvention convention = customConvention
+			.getIfAvailable(() -> new DefaultServerRequestObservationConvention(name));
 		int order = this.observationProperties.getHttp().getServer().getFilter().getOrder();
 		return new OrderedServerHttpObservationFilter(registry, convention, order);
 	}
 
-	private static ServerRequestObservationConvention createConvention(
-			ServerRequestObservationConvention customConvention, String name, WebFluxTagsProvider tagsProvider,
-			List tagsContributors) {
-		if (customConvention != null) {
-			return customConvention;
-		}
-		if (tagsProvider != null) {
-			return new ServerRequestObservationConventionAdapter(name, tagsProvider);
-		}
-		if (!tagsContributors.isEmpty()) {
-			return new ServerRequestObservationConventionAdapter(name, tagsContributors);
-		}
-		return new DefaultServerRequestObservationConvention(name);
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	@ConditionalOnClass(MeterRegistry.class)
 	@ConditionalOnBean(MeterRegistry.class)
@@ -117,9 +88,7 @@ static class MeterFilterConfiguration {
 		@Order(0)
 		MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties,
 				ObservationProperties observationProperties) {
-			String observationName = observationProperties.getHttp().getServer().getRequests().getName();
-			String name = (observationName != null) ? observationName
-					: metricsProperties.getWeb().getServer().getRequest().getMetricName();
+			String name = observationProperties.getHttp().getServer().getRequests().getName();
 			MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
 					() -> "Reached the maximum number of URI tags for '%s'.".formatted(name));
 			return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapter.java
deleted file mode 100644
index df52cbca7407..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapter.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.observation.web.servlet;
-
-import java.util.List;
-
-import io.micrometer.common.KeyValues;
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.observation.Observation;
-
-import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
-import org.springframework.http.server.observation.ServerRequestObservationContext;
-import org.springframework.http.server.observation.ServerRequestObservationConvention;
-import org.springframework.util.Assert;
-import org.springframework.web.servlet.HandlerMapping;
-
-/**
- * Adapter class that applies {@link WebMvcTagsProvider} tags as a
- * {@link ServerRequestObservationConvention}.
- *
- * @author Brian Clozel
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class ServerRequestObservationConventionAdapter implements ServerRequestObservationConvention {
-
-	private final String observationName;
-
-	private final WebMvcTagsProvider tagsProvider;
-
-	ServerRequestObservationConventionAdapter(String observationName, WebMvcTagsProvider tagsProvider,
-			List contributors) {
-		Assert.state((tagsProvider != null) || (contributors != null),
-				"adapter should adapt to a WebMvcTagsProvider or a list of contributors");
-		this.observationName = observationName;
-		this.tagsProvider = (tagsProvider != null) ? tagsProvider : new DefaultWebMvcTagsProvider(contributors);
-	}
-
-	@Override
-	public String getName() {
-		return this.observationName;
-	}
-
-	@Override
-	public boolean supportsContext(Observation.Context context) {
-		return context instanceof ServerRequestObservationContext;
-	}
-
-	@Override
-	public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
-		Iterable tags = this.tagsProvider.getTags(context.getCarrier(), context.getResponse(), getHandler(context),
-				context.getError());
-		return KeyValues.of(tags, Tag::getKey, Tag::getValue);
-	}
-
-	private Object getHandler(ServerRequestObservationContext context) {
-		return context.getCarrier().getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java
index dba50c22f3d6..e6f1c487def1 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java
@@ -16,8 +16,6 @@
 
 package org.springframework.boot.actuate.autoconfigure.observation.web.servlet;
 
-import java.util.List;
-
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.config.MeterFilter;
 import io.micrometer.observation.Observation;
@@ -32,8 +30,6 @@
 import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -66,56 +62,23 @@
 @ConditionalOnClass({ DispatcherServlet.class, Observation.class })
 @ConditionalOnBean(ObservationRegistry.class)
 @EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class })
-@SuppressWarnings("removal")
 public class WebMvcObservationAutoConfiguration {
 
-	private final MetricsProperties metricsProperties;
-
-	private final ObservationProperties observationProperties;
-
-	public WebMvcObservationAutoConfiguration(ObservationProperties observationProperties,
-			MetricsProperties metricsProperties) {
-		this.observationProperties = observationProperties;
-		this.metricsProperties = metricsProperties;
-	}
-
 	@Bean
 	@ConditionalOnMissingFilterBean
 	public FilterRegistrationBean webMvcObservationFilter(ObservationRegistry registry,
 			ObjectProvider customConvention,
-			ObjectProvider customTagsProvider,
-			ObjectProvider contributorsProvider) {
-		String name = httpRequestsMetricName(this.observationProperties, this.metricsProperties);
-		ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name,
-				customTagsProvider.getIfAvailable(), contributorsProvider.orderedStream().toList());
+			ObservationProperties observationProperties) {
+		String name = observationProperties.getHttp().getServer().getRequests().getName();
+		ServerRequestObservationConvention convention = customConvention
+			.getIfAvailable(() -> new DefaultServerRequestObservationConvention(name));
 		ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention);
 		FilterRegistrationBean registration = new FilterRegistrationBean<>(filter);
-		registration.setOrder(this.observationProperties.getHttp().getServer().getFilter().getOrder());
+		registration.setOrder(observationProperties.getHttp().getServer().getFilter().getOrder());
 		registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
 		return registration;
 	}
 
-	private static ServerRequestObservationConvention createConvention(
-			ServerRequestObservationConvention customConvention, String name, WebMvcTagsProvider tagsProvider,
-			List contributors) {
-		if (customConvention != null) {
-			return customConvention;
-		}
-		else if (tagsProvider != null || contributors.size() > 0) {
-			return new ServerRequestObservationConventionAdapter(name, tagsProvider, contributors);
-		}
-		else {
-			return new DefaultServerRequestObservationConvention(name);
-		}
-	}
-
-	private static String httpRequestsMetricName(ObservationProperties observationProperties,
-			MetricsProperties metricsProperties) {
-		String observationName = observationProperties.getHttp().getServer().getRequests().getName();
-		return (observationName != null) ? observationName
-				: metricsProperties.getWeb().getServer().getRequest().getMetricName();
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	@ConditionalOnClass(MeterRegistry.class)
 	@ConditionalOnBean(MeterRegistry.class)
@@ -123,9 +86,9 @@ static class MeterFilterConfiguration {
 
 		@Bean
 		@Order(0)
-		MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties,
-				ObservationProperties observationProperties) {
-			String name = httpRequestsMetricName(observationProperties, metricsProperties);
+		MeterFilter metricsHttpServerUriTagFilter(ObservationProperties observationProperties,
+				MetricsProperties metricsProperties) {
+			String name = observationProperties.getHttp().getServer().getRequests().getName();
 			MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
 					() -> String.format("Reached the maximum number of URI tags for '%s'.", name));
 			return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index 9fb2160d5b2a..bd717a474b56 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -1980,11 +1980,19 @@
         "reason": "Should be applied at the ObservationRegistry level."
       }
     },
+    {
+      "name": "management.metrics.web.client.request.metric-name",
+      "type": "java.lang.String",
+      "deprecation": {
+        "replacement": "management.observations.http.client.requests.name",
+        "level": "error"
+      }
+    },
     {
       "name": "management.metrics.web.client.requests-metric-name",
       "type": "java.lang.String",
       "deprecation": {
-        "replacement": "management.metrics.web.client.request.metric-name",
+        "replacement": "management.observations.http.client.requests.name",
         "level": "error"
       }
     },
@@ -2030,11 +2038,19 @@
         "reason": "Not needed anymore, direct instrumentation in Spring MVC."
       }
     },
+    {
+      "name": "management.metrics.web.server.request.metric-name",
+      "type": "java.lang.String",
+      "deprecation": {
+        "replacement": "management.observations.http.server.requests.name",
+        "level": "error"
+      }
+    },
     {
       "name": "management.metrics.web.server.requests-metric-name",
       "type": "java.lang.String",
       "deprecation": {
-        "replacement": "management.metrics.web.server.request.metric-name",
+        "replacement": "management.observations.http.server.requests.name",
         "level": "error"
       }
     },
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationReflectionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationReflectionTests.java
deleted file mode 100644
index c6100f971b7b..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationReflectionTests.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.health;
-
-import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfigurationReflectionTests.TestHealthIndicator;
-import org.springframework.boot.actuate.health.AbstractHealthIndicator;
-import org.springframework.boot.actuate.health.Health.Builder;
-import org.springframework.boot.actuate.health.HealthContributor;
-
-/**
- * Tests for {@link CompositeHealthContributorConfiguration} using reflection to create
- * indicator instances.
- *
- * @author Phillip Webb
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class CompositeHealthContributorConfigurationReflectionTests
-		extends AbstractCompositeHealthContributorConfigurationTests {
-
-	@Override
-	protected AbstractCompositeHealthContributorConfiguration newComposite() {
-		return new ReflectiveTestCompositeHealthContributorConfiguration();
-	}
-
-	static class ReflectiveTestCompositeHealthContributorConfiguration
-			extends CompositeHealthContributorConfiguration {
-
-	}
-
-	static class TestHealthIndicator extends AbstractHealthIndicator {
-
-		TestHealthIndicator(TestBean testBean) {
-		}
-
-		@Override
-		protected void doHealthCheck(Builder builder) throws Exception {
-			builder.up();
-		}
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationReflectionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationReflectionTests.java
deleted file mode 100644
index 183a3c7bd3a7..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationReflectionTests.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.health;
-
-import reactor.core.publisher.Mono;
-
-import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfigurationReflectionTests.TestReactiveHealthIndicator;
-import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
-import org.springframework.boot.actuate.health.Health;
-import org.springframework.boot.actuate.health.Health.Builder;
-import org.springframework.boot.actuate.health.ReactiveHealthContributor;
-
-/**
- * Tests for {@link CompositeReactiveHealthContributorConfiguration} using reflection to
- * create indicator instances.
- *
- * @author Phillip Webb
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class CompositeReactiveHealthContributorConfigurationReflectionTests extends
-		AbstractCompositeHealthContributorConfigurationTests {
-
-	@Override
-	protected AbstractCompositeHealthContributorConfiguration newComposite() {
-		return new TestCompositeReactiveHealthContributorConfiguration();
-	}
-
-	static class TestCompositeReactiveHealthContributorConfiguration
-			extends CompositeReactiveHealthContributorConfiguration {
-
-	}
-
-	static class TestReactiveHealthIndicator extends AbstractReactiveHealthIndicator {
-
-		TestReactiveHealthIndicator(TestBean testBean) {
-		}
-
-		@Override
-		protected Mono doHealthCheck(Builder builder) {
-			return Mono.just(builder.up().build());
-		}
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapterTests.java
deleted file mode 100644
index 814e71a4c720..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientHttpObservationConventionAdapterTests.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.observation.web.client;
-
-import java.net.URI;
-
-import io.micrometer.common.KeyValue;
-import io.micrometer.observation.Observation;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpRequest;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.http.client.observation.ClientRequestObservationContext;
-import org.springframework.mock.http.client.MockClientHttpRequest;
-import org.springframework.mock.http.client.MockClientHttpResponse;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link ClientHttpObservationConventionAdapter}.
- *
- * @author Brian Clozel
- */
-@SuppressWarnings({ "deprecation", "removal" })
-class ClientHttpObservationConventionAdapterTests {
-
-	private static final String TEST_METRIC_NAME = "test.metric.name";
-
-	private final ClientHttpObservationConventionAdapter convention = new ClientHttpObservationConventionAdapter(
-			TEST_METRIC_NAME, new DefaultRestTemplateExchangeTagsProvider());
-
-	private final ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/resource/test"));
-
-	private final ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.OK);
-
-	private ClientRequestObservationContext context;
-
-	@BeforeEach
-	void setup() {
-		this.context = new ClientRequestObservationContext(this.request);
-		this.context.setResponse(this.response);
-		this.context.setUriTemplate("/resource/{name}");
-	}
-
-	@Test
-	void shouldUseConfiguredName() {
-		assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME);
-	}
-
-	@Test
-	void shouldOnlySupportClientHttpObservationContext() {
-		assertThat(this.convention.supportsContext(this.context)).isTrue();
-		assertThat(this.convention.supportsContext(new OtherContext())).isFalse();
-	}
-
-	@Test
-	void shouldPushTagsAsLowCardinalityKeyValues() {
-		assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"),
-				KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"),
-				KeyValue.of("method", "GET"));
-	}
-
-	@Test
-	void shouldNotPushAnyHighCardinalityKeyValue() {
-		assertThat(this.convention.getHighCardinalityKeyValues(this.context)).isEmpty();
-	}
-
-	static class OtherContext extends Observation.Context {
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java
deleted file mode 100644
index cd73e29efba0..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2012-2023 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.actuate.autoconfigure.observation.web.client;
-
-import java.net.URI;
-
-import io.micrometer.common.KeyValue;
-import io.micrometer.observation.Observation;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.boot.actuate.metrics.web.reactive.client.DefaultWebClientExchangeTagsProvider;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ClientRequestObservationContext;
-import org.springframework.web.reactive.function.client.ClientResponse;
-import org.springframework.web.reactive.function.client.WebClient;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link ClientObservationConventionAdapter}.
- *
- * @author Brian Clozel
- */
-@SuppressWarnings({ "deprecation", "removal" })
-class ClientObservationConventionAdapterTests {
-
-	private static final String TEST_METRIC_NAME = "test.metric.name";
-
-	private final ClientObservationConventionAdapter convention = new ClientObservationConventionAdapter(
-			TEST_METRIC_NAME, new DefaultWebClientExchangeTagsProvider());
-
-	private final ClientRequest.Builder requestBuilder = ClientRequest
-		.create(HttpMethod.GET, URI.create("/resource/test"))
-		.attribute(WebClient.class.getName() + ".uriTemplate", "/resource/{name}");
-
-	private final ClientResponse response = ClientResponse.create(HttpStatus.OK).body("foo").build();
-
-	private ClientRequestObservationContext context;
-
-	@BeforeEach
-	void setup() {
-		this.context = new ClientRequestObservationContext();
-		this.context.setCarrier(this.requestBuilder);
-		this.context.setResponse(this.response);
-		this.context.setUriTemplate("/resource/{name}");
-	}
-
-	@Test
-	void shouldUseConfiguredName() {
-		assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME);
-	}
-
-	@Test
-	void shouldOnlySupportClientObservationContext() {
-		assertThat(this.convention.supportsContext(this.context)).isTrue();
-		assertThat(this.convention.supportsContext(new OtherContext())).isFalse();
-	}
-
-	@Test
-	void shouldPushTagsAsLowCardinalityKeyValues() {
-		this.context.setRequest(this.requestBuilder.build());
-		assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"),
-				KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"),
-				KeyValue.of("method", "GET"));
-	}
-
-	@Test
-	void doesNotFailWithEmptyRequest() {
-		assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"),
-				KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"),
-				KeyValue.of("method", "GET"));
-	}
-
-	@Test
-	void shouldNotPushAnyHighCardinalityKeyValue() {
-		assertThat(this.convention.getHighCardinalityKeyValues(this.context)).isEmpty();
-	}
-
-	static class OtherContext extends Observation.Context {
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java
index 6116649a14dc..92b4367cad0c 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/RestTemplateObservationConfigurationTests.java
@@ -18,8 +18,6 @@
 
 import io.micrometer.common.KeyValues;
 import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.core.instrument.Tags;
 import io.micrometer.observation.ObservationRegistry;
 import io.micrometer.observation.tck.TestObservationRegistry;
 import io.micrometer.observation.tck.TestObservationRegistryAssert;
@@ -28,9 +26,7 @@
 
 import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
-import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider;
 import org.springframework.boot.actuate.metrics.web.client.ObservationRestTemplateCustomizer;
-import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
 import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
@@ -40,9 +36,7 @@
 import org.springframework.boot.web.client.RestTemplateBuilder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpRequest;
 import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpResponse;
 import org.springframework.http.client.observation.ClientRequestObservationContext;
 import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
 import org.springframework.test.web.client.MockRestServiceServer;
@@ -58,7 +52,6 @@
  * @author Brian Clozel
  */
 @ExtendWith(OutputCaptureExtension.class)
-@SuppressWarnings("removal")
 class RestTemplateObservationConfigurationTests {
 
 	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@@ -68,8 +61,7 @@ class RestTemplateObservationConfigurationTests {
 
 	@Test
 	void contributesCustomizerBean() {
-		this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationRestTemplateCustomizer.class)
-			.doesNotHaveBean(DefaultRestTemplateExchangeTagsProvider.class));
+		this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationRestTemplateCustomizer.class));
 	}
 
 	@Test
@@ -96,32 +88,6 @@ void restTemplateCreatedWithBuilderUsesCustomConventionName() {
 			});
 	}
 
-	@Test
-	void restTemplateCreatedWithBuilderUsesCustomMetricName() {
-		final String metricName = "test.metric.name";
-		this.contextRunner.withPropertyValues("management.metrics.web.client.request.metric-name=" + metricName)
-			.run((context) -> {
-				RestTemplate restTemplate = buildRestTemplate(context);
-				restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot");
-				TestObservationRegistry registry = context.getBean(TestObservationRegistry.class);
-				TestObservationRegistryAssert.assertThat(registry)
-					.hasObservationWithNameEqualToIgnoringCase(metricName);
-			});
-	}
-
-	@Test
-	void restTemplateCreatedWithBuilderUsesCustomTagsProvider() {
-		this.contextRunner.withUserConfiguration(CustomTagsConfiguration.class).run((context) -> {
-			RestTemplate restTemplate = buildRestTemplate(context);
-			restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot");
-			TestObservationRegistry registry = context.getBean(TestObservationRegistry.class);
-			TestObservationRegistryAssert.assertThat(registry)
-				.hasObservationWithNameEqualTo("http.client.requests")
-				.that()
-				.hasLowCardinalityKeyValue("project", "spring-boot");
-		});
-	}
-
 	@Test
 	void restTemplateCreatedWithBuilderUsesCustomConvention() {
 		this.contextRunner.withUserConfiguration(CustomConvention.class).run((context) -> {
@@ -163,8 +129,7 @@ void backsOffWhenRestTemplateBuilderIsMissing() {
 		new ApplicationContextRunner().with(MetricsRun.simple())
 			.withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class,
 					HttpClientObservationsAutoConfiguration.class))
-			.run((context) -> assertThat(context).doesNotHaveBean(DefaultRestTemplateExchangeTagsProvider.class)
-				.doesNotHaveBean(ObservationRestTemplateCustomizer.class));
+			.run((context) -> assertThat(context).doesNotHaveBean(ObservationRestTemplateCustomizer.class));
 	}
 
 	private RestTemplate buildRestTemplate(AssertableApplicationContext context) {
@@ -174,26 +139,6 @@ private RestTemplate buildRestTemplate(AssertableApplicationContext context) {
 		return restTemplate;
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class CustomTagsConfiguration {
-
-		@Bean
-		CustomTagsProvider customTagsProvider() {
-			return new CustomTagsProvider();
-		}
-
-	}
-
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	static class CustomTagsProvider implements RestTemplateExchangeTagsProvider {
-
-		@Override
-		public Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response) {
-			return Tags.of("project", "spring-boot");
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class CustomConventionConfiguration {
 
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java
index 9a94712edc78..8a917298b0e0 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/WebClientObservationConfigurationTests.java
@@ -29,9 +29,7 @@
 
 import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
-import org.springframework.boot.actuate.metrics.web.reactive.client.DefaultWebClientExchangeTagsProvider;
 import org.springframework.boot.actuate.metrics.web.reactive.client.ObservationWebClientCustomizer;
-import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
 import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
@@ -59,7 +57,6 @@
  * @author Stephane Nicoll
  */
 @ExtendWith(OutputCaptureExtension.class)
-@SuppressWarnings("removal")
 class WebClientObservationConfigurationTests {
 
 	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
@@ -69,8 +66,7 @@ class WebClientObservationConfigurationTests {
 
 	@Test
 	void contributesCustomizerBean() {
-		this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationWebClientCustomizer.class)
-			.doesNotHaveBean(DefaultWebClientExchangeTagsProvider.class));
+		this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ObservationWebClientCustomizer.class));
 	}
 
 	@Test
@@ -82,14 +78,6 @@ void webClientCreatedWithBuilderIsInstrumented() {
 		});
 	}
 
-	@Test
-	void shouldNotOverrideCustomTagsProvider() {
-		this.contextRunner.withUserConfiguration(CustomTagsProviderConfig.class)
-			.run((context) -> assertThat(context).getBeans(WebClientExchangeTagsProvider.class)
-				.hasSize(1)
-				.containsKey("customTagsProvider"));
-	}
-
 	@Test
 	void shouldUseCustomConventionIfAvailable() {
 		this.contextRunner.withUserConfiguration(CustomConvention.class).run((context) -> {
@@ -170,16 +158,6 @@ private WebClient mockWebClient(WebClient.Builder builder) {
 		return builder.clientConnector(connector).build();
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class CustomTagsProviderConfig {
-
-		@Bean
-		WebClientExchangeTagsProvider customTagsProvider() {
-			return mock(WebClientExchangeTagsProvider.class);
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class CustomConventionConfig {
 
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java
deleted file mode 100644
index 9d32817dc80c..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/ServerRequestObservationConventionAdapterTests.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.observation.web.reactive;
-
-import java.util.Map;
-
-import io.micrometer.common.KeyValue;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider;
-import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
-import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
-import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
-import org.springframework.web.reactive.HandlerMapping;
-import org.springframework.web.util.pattern.PathPatternParser;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link ServerRequestObservationConventionAdapter}.
- *
- * @author Brian Clozel
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class ServerRequestObservationConventionAdapterTests {
-
-	private static final String TEST_METRIC_NAME = "test.metric.name";
-
-	private final ServerRequestObservationConventionAdapter convention = new ServerRequestObservationConventionAdapter(
-			TEST_METRIC_NAME, new DefaultWebFluxTagsProvider());
-
-	@Test
-	void shouldUseConfiguredName() {
-		assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME);
-	}
-
-	@Test
-	void shouldPushTagsAsLowCardinalityKeyValues() {
-		MockServerHttpRequest request = MockServerHttpRequest.get("/resource/test").build();
-		MockServerHttpResponse response = new MockServerHttpResponse();
-		ServerRequestObservationContext context = new ServerRequestObservationContext(request, response,
-				Map.of(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE,
-						PathPatternParser.defaultInstance.parse("/resource/{name}")));
-		assertThat(this.convention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("status", "200"),
-				KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"),
-				KeyValue.of("method", "GET"));
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java
index d8197198eb47..939b754424a8 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java
@@ -19,8 +19,6 @@
 import java.util.List;
 
 import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.core.instrument.Tags;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import reactor.core.publisher.Mono;
@@ -29,9 +27,6 @@
 import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
 import org.springframework.boot.actuate.autoconfigure.metrics.web.TestController;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
-import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider;
-import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor;
-import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
 import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
@@ -83,28 +78,6 @@ void shouldProvideWebFluxObservationFilterOrdered() {
 		});
 	}
 
-	@Test
-	void shouldUseConventionAdapterWhenCustomTagsProvider() {
-		this.contextRunner.withUserConfiguration(CustomTagsProviderConfiguration.class).run((context) -> {
-			assertThat(context).hasSingleBean(ServerHttpObservationFilter.class);
-			assertThat(context).hasSingleBean(WebFluxTagsProvider.class);
-			assertThat(context).getBean(ServerHttpObservationFilter.class)
-				.extracting("observationConvention")
-				.isInstanceOf(ServerRequestObservationConventionAdapter.class);
-		});
-	}
-
-	@Test
-	void shouldUseConventionAdapterWhenCustomTagsContributor() {
-		this.contextRunner.withUserConfiguration(CustomTagsContributorConfiguration.class).run((context) -> {
-			assertThat(context).hasSingleBean(ServerHttpObservationFilter.class);
-			assertThat(context).hasSingleBean(WebFluxTagsContributor.class);
-			assertThat(context).getBean(ServerHttpObservationFilter.class)
-				.extracting("observationConvention")
-				.isInstanceOf(ServerRequestObservationConventionAdapter.class);
-		});
-	}
-
 	@Test
 	void shouldUseCustomConventionWhenAvailable() {
 		this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class).run((context) -> {
@@ -128,21 +101,6 @@ void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
 			});
 	}
 
-	@Test
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomMetricName(CapturedOutput output) {
-		this.contextRunner.withUserConfiguration(TestController.class)
-			.withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class, ObservationAutoConfiguration.class,
-					WebFluxAutoConfiguration.class))
-			.withPropertyValues("management.metrics.web.server.max-uri-tags=2",
-					"management.metrics.web.server.request.metric-name=my.http.server.requests")
-			.run((context) -> {
-				MeterRegistry registry = getInitializedMeterRegistry(context);
-				assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2);
-				assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'");
-			});
-	}
-
 	@Test
 	void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomObservationName(CapturedOutput output) {
 		this.contextRunner.withUserConfiguration(TestController.class)
@@ -194,37 +152,6 @@ private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicati
 		return context.getBean(MeterRegistry.class);
 	}
 
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@Configuration(proxyBeanMethods = false)
-	static class CustomTagsProviderConfiguration {
-
-		@Bean
-		WebFluxTagsProvider tagsProvider() {
-			return new DefaultWebFluxTagsProvider();
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class CustomTagsContributorConfiguration {
-
-		@Bean
-		WebFluxTagsContributor tagsContributor() {
-			return new CustomTagsContributor();
-		}
-
-	}
-
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	static class CustomTagsContributor implements WebFluxTagsContributor {
-
-		@Override
-		public Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex) {
-			return Tags.of("custom", "testvalue");
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class CustomConventionConfiguration {
 
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapterTests.java
deleted file mode 100644
index 9705829af336..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/ServerRequestObservationConventionAdapterTests.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.autoconfigure.observation.web.servlet;
-
-import java.util.Collections;
-import java.util.List;
-
-import io.micrometer.common.KeyValue;
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.core.instrument.Tags;
-import io.micrometer.observation.Observation;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor;
-import org.springframework.http.server.observation.ServerRequestObservationContext;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.web.servlet.HandlerMapping;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link ServerRequestObservationConventionAdapter}
- *
- * @author Brian Clozel
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class ServerRequestObservationConventionAdapterTests {
-
-	private static final String TEST_METRIC_NAME = "test.metric.name";
-
-	private final ServerRequestObservationConventionAdapter convention = new ServerRequestObservationConventionAdapter(
-			TEST_METRIC_NAME, new DefaultWebMvcTagsProvider(), Collections.emptyList());
-
-	private final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/resource/test");
-
-	private final MockHttpServletResponse response = new MockHttpServletResponse();
-
-	private final ServerRequestObservationContext context = new ServerRequestObservationContext(this.request,
-			this.response);
-
-	@Test
-	void customNameIsUsed() {
-		assertThat(this.convention.getName()).isEqualTo(TEST_METRIC_NAME);
-	}
-
-	@Test
-	void onlySupportServerRequestObservationContext() {
-		assertThat(this.convention.supportsContext(this.context)).isTrue();
-		assertThat(this.convention.supportsContext(new OtherContext())).isFalse();
-	}
-
-	@Test
-	void pushTagsAsLowCardinalityKeyValues() {
-		this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/resource/{name}");
-		this.context.setPathPattern("/resource/{name}");
-		assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"),
-				KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"),
-				KeyValue.of("method", "GET"));
-	}
-
-	@Test
-	void doesNotPushAnyHighCardinalityKeyValue() {
-		assertThat(this.convention.getHighCardinalityKeyValues(this.context)).isEmpty();
-	}
-
-	@Test
-	void pushTagsFromContributors() {
-		ServerRequestObservationConventionAdapter convention = new ServerRequestObservationConventionAdapter(
-				TEST_METRIC_NAME, null, List.of(new CustomWebMvcContributor()));
-		assertThat(convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("custom", "value"));
-	}
-
-	static class OtherContext extends Observation.Context {
-
-	}
-
-	static class CustomWebMvcContributor implements WebMvcTagsContributor {
-
-		@Override
-		public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler,
-				Throwable exception) {
-			return Tags.of("custom", "value");
-		}
-
-		@Override
-		public Iterable getLongRequestTags(HttpServletRequest request, Object handler) {
-			return Collections.emptyList();
-		}
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java
index 7328d40b2b53..b996ba206430 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java
@@ -16,16 +16,12 @@
 
 package org.springframework.boot.actuate.autoconfigure.observation.web.servlet;
 
-import java.util.Collections;
 import java.util.EnumSet;
 
 import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Tag;
 import io.micrometer.observation.tck.TestObservationRegistry;
 import jakarta.servlet.DispatcherType;
 import jakarta.servlet.Filter;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -33,9 +29,6 @@
 import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
 import org.springframework.boot.actuate.autoconfigure.metrics.web.TestController;
 import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
-import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
 import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
@@ -67,7 +60,6 @@
  * @author Moritz Halbritter
  */
 @ExtendWith(OutputCaptureExtension.class)
-@SuppressWarnings("removal")
 class WebMvcObservationAutoConfigurationTests {
 
 	private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
@@ -85,21 +77,12 @@ void backsOffWhenMeterRegistryIsMissing() {
 	@Test
 	void definesFilterWhenRegistryIsPresent() {
 		this.contextRunner.run((context) -> {
-			assertThat(context).doesNotHaveBean(DefaultWebMvcTagsProvider.class);
 			assertThat(context).hasSingleBean(FilterRegistrationBean.class);
 			assertThat(context.getBean(FilterRegistrationBean.class).getFilter())
 				.isInstanceOf(ServerHttpObservationFilter.class);
 		});
 	}
 
-	@Test
-	void adapterConventionWhenTagsProviderPresent() {
-		this.contextRunner.withUserConfiguration(TagsProviderConfiguration.class)
-			.run((context) -> assertThat(context.getBean(FilterRegistrationBean.class).getFilter())
-				.extracting("observationConvention")
-				.isInstanceOf(ServerRequestObservationConventionAdapter.class));
-	}
-
 	@Test
 	void customConventionWhenPresent() {
 		this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class)
@@ -169,21 +152,6 @@ void afterMaxUrisReachedFurtherUrisAreDenied(CapturedOutput output) {
 			});
 	}
 
-	@Test
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomMetricName(CapturedOutput output) {
-		this.contextRunner.withUserConfiguration(TestController.class)
-			.withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class, ObservationAutoConfiguration.class,
-					WebMvcAutoConfiguration.class))
-			.withPropertyValues("management.metrics.web.server.max-uri-tags=2",
-					"management.metrics.web.server.request.metric-name=my.http.server.requests")
-			.run((context) -> {
-				MeterRegistry registry = getInitializedMeterRegistry(context);
-				assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2);
-				assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'");
-			});
-	}
-
 	@Test
 	void afterMaxUrisReachedFurtherUrisAreDeniedWhenUsingCustomObservationName(CapturedOutput output) {
 		this.contextRunner.withUserConfiguration(TestController.class)
@@ -211,14 +179,6 @@ void shouldNotDenyNorLogIfMaxUrisIsNotReached(CapturedOutput output) {
 			});
 	}
 
-	@Test
-	void whenTagContributorsAreDefinedThenTagsProviderUsesThem() {
-		this.contextRunner.withUserConfiguration(TagsContributorsConfiguration.class)
-			.run((context) -> assertThat(context.getBean(FilterRegistrationBean.class).getFilter())
-				.extracting("observationConvention")
-				.isInstanceOf(ServerRequestObservationConventionAdapter.class));
-	}
-
 	private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContext context) throws Exception {
 		return getInitializedMeterRegistry(context, "/test0", "/test1", "/test2");
 	}
@@ -235,47 +195,6 @@ private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContex
 		return context.getBean(MeterRegistry.class);
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class TagsProviderConfiguration {
-
-		@Bean
-		TestWebMvcTagsProvider tagsProvider() {
-			return new TestWebMvcTagsProvider();
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class TagsContributorsConfiguration {
-
-		@Bean
-		WebMvcTagsContributor tagContributorOne() {
-			return mock(WebMvcTagsContributor.class);
-		}
-
-		@Bean
-		WebMvcTagsContributor tagContributorTwo() {
-			return mock(WebMvcTagsContributor.class);
-		}
-
-	}
-
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	private static final class TestWebMvcTagsProvider implements WebMvcTagsProvider {
-
-		@Override
-		public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler,
-				Throwable exception) {
-			return Collections.emptyList();
-		}
-
-		@Override
-		public Iterable getLongRequestTags(HttpServletRequest request, Object handler) {
-			return Collections.emptyList();
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class TestServerHttpObservationFilterRegistrationConfiguration {
 
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java
index f0a3453e70e1..da459eb0c28b 100644
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java
@@ -141,7 +141,7 @@ private void shutdown(ShutdownOperation shutdownOperation) {
 		}
 		this.scheduled.cancel(false);
 		switch (shutdownOperation) {
-			case PUSH, POST -> post();
+			case POST -> post();
 			case PUT -> put();
 			case DELETE -> delete();
 		}
@@ -162,13 +162,6 @@ public enum ShutdownOperation {
 		 */
 		POST,
 
-		/**
-		 * Perform a POST before shutdown.
-		 * @deprecated since 3.0.0 for removal in 3.2.0 in favor of {@link #POST}.
-		 */
-		@Deprecated(since = "3.0.0", forRemoval = true)
-		PUSH,
-
 		/**
 		 * Perform a PUT before shutdown.
 		 */
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java
deleted file mode 100644
index ace3689e6862..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.client;
-
-import java.util.Arrays;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.http.HttpRequest;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.util.StringUtils;
-
-/**
- * Default implementation of {@link RestTemplateExchangeTagsProvider}.
- *
- * @author Jon Schneider
- * @author Nishant Raut
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.client.observation.DefaultClientRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-@SuppressWarnings("removal")
-public class DefaultRestTemplateExchangeTagsProvider implements RestTemplateExchangeTagsProvider {
-
-	@Override
-	public Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response) {
-		Tag uriTag = (StringUtils.hasText(urlTemplate) ? RestTemplateExchangeTags.uri(urlTemplate)
-				: RestTemplateExchangeTags.uri(request));
-		return Arrays.asList(RestTemplateExchangeTags.method(request), uriTag,
-				RestTemplateExchangeTags.status(response), RestTemplateExchangeTags.clientName(request),
-				RestTemplateExchangeTags.outcome(response));
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java
deleted file mode 100644
index 5f17ee3d4f44..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.client;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.regex.Pattern;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.boot.actuate.metrics.http.Outcome;
-import org.springframework.http.HttpRequest;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
-import org.springframework.util.StringUtils;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * Factory methods for creating {@link Tag Tags} related to a request-response exchange
- * performed by a {@link RestTemplate}.
- *
- * @author Andy Wilkinson
- * @author Jon Schneider
- * @author Nishant Raut
- * @author Brian Clozel
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link DefaultClientRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-public final class RestTemplateExchangeTags {
-
-	private static final Pattern STRIP_URI_PATTERN = Pattern.compile("^https?://[^/]+/");
-
-	private RestTemplateExchangeTags() {
-	}
-
-	/**
-	 * Creates a {@code method} {@code Tag} for the {@link HttpRequest#getMethod() method}
-	 * of the given {@code request}.
-	 * @param request the request
-	 * @return the method tag
-	 */
-	public static Tag method(HttpRequest request) {
-		return Tag.of("method", request.getMethod().name());
-	}
-
-	/**
-	 * Creates a {@code uri} {@code Tag} for the URI of the given {@code request}.
-	 * @param request the request
-	 * @return the uri tag
-	 */
-	public static Tag uri(HttpRequest request) {
-		return Tag.of("uri", ensureLeadingSlash(stripUri(request.getURI().toString())));
-	}
-
-	/**
-	 * Creates a {@code uri} {@code Tag} from the given {@code uriTemplate}.
-	 * @param uriTemplate the template
-	 * @return the uri tag
-	 */
-	public static Tag uri(String uriTemplate) {
-		String uri = (StringUtils.hasText(uriTemplate) ? uriTemplate : "none");
-		return Tag.of("uri", ensureLeadingSlash(stripUri(uri)));
-	}
-
-	private static String stripUri(String uri) {
-		return STRIP_URI_PATTERN.matcher(uri).replaceAll("");
-	}
-
-	private static String ensureLeadingSlash(String url) {
-		return (url == null || url.startsWith("/")) ? url : "/" + url;
-	}
-
-	/**
-	 * Creates a {@code status} {@code Tag} derived from the
-	 * {@link ClientHttpResponse#getStatusCode() status} of the given {@code response}.
-	 * @param response the response
-	 * @return the status tag
-	 */
-	public static Tag status(ClientHttpResponse response) {
-		return Tag.of("status", getStatusMessage(response));
-	}
-
-	private static String getStatusMessage(ClientHttpResponse response) {
-		try {
-			if (response == null) {
-				return "CLIENT_ERROR";
-			}
-			return String.valueOf(response.getStatusCode().value());
-		}
-		catch (IOException ex) {
-			return "IO_ERROR";
-		}
-	}
-
-	/**
-	 * Create a {@code client.name} {@code Tag} derived from the {@link URI#getHost host}
-	 * of the {@link HttpRequest#getURI() URI} of the given {@code request}.
-	 * @param request the request
-	 * @return the client.name tag
-	 */
-	public static Tag clientName(HttpRequest request) {
-		String host = request.getURI().getHost();
-		if (host == null) {
-			host = "none";
-		}
-		return Tag.of("client.name", host);
-	}
-
-	/**
-	 * Creates an {@code outcome} {@code Tag} derived from the
-	 * {@link ClientHttpResponse#getStatusCode() status} of the given {@code response}.
-	 * @param response the response
-	 * @return the outcome tag
-	 * @since 2.2.0
-	 */
-	public static Tag outcome(ClientHttpResponse response) {
-		try {
-			if (response != null) {
-				return Outcome.forStatus(response.getStatusCode().value()).asTag();
-			}
-		}
-		catch (IOException ex) {
-			// Continue
-		}
-		return Outcome.UNKNOWN.asTag();
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsProvider.java
deleted file mode 100644
index ea3c05360ce3..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsProvider.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.client;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.http.HttpRequest;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.http.client.observation.ClientRequestObservationConvention;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * Provides {@link Tag Tags} for an exchange performed by a {@link RestTemplate}.
- *
- * @author Jon Schneider
- * @author Andy Wilkinson
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link ClientRequestObservationConvention}
- */
-@FunctionalInterface
-@Deprecated(since = "3.0.0", forRemoval = true)
-public interface RestTemplateExchangeTagsProvider {
-
-	/**
-	 * Provides the tags to be associated with metrics that are recorded for the given
-	 * {@code request} and {@code response} exchange.
-	 * @param urlTemplate the source URl template, if available
-	 * @param request the request
-	 * @param response the response (may be {@code null} if the exchange failed)
-	 * @return the tags
-	 */
-	Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response);
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java
deleted file mode 100644
index aeae3222d5ab..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.reactive.client;
-
-import java.util.Arrays;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ClientResponse;
-
-/**
- * Default implementation of {@link WebClientExchangeTagsProvider}.
- *
- * @author Brian Clozel
- * @author Nishant Raut
- * @since 2.1.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.web.reactive.function.client.ClientRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-@SuppressWarnings("removal")
-public class DefaultWebClientExchangeTagsProvider implements WebClientExchangeTagsProvider {
-
-	@Override
-	public Iterable tags(ClientRequest request, ClientResponse response, Throwable throwable) {
-		Tag method = WebClientExchangeTags.method(request);
-		Tag uri = WebClientExchangeTags.uri(request);
-		Tag clientName = WebClientExchangeTags.clientName(request);
-		Tag status = WebClientExchangeTags.status(response, throwable);
-		Tag outcome = WebClientExchangeTags.outcome(response);
-		return Arrays.asList(method, uri, clientName, status, outcome);
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java
deleted file mode 100644
index c916188b6846..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.reactive.client;
-
-import java.io.IOException;
-import java.util.regex.Pattern;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.boot.actuate.metrics.http.Outcome;
-import org.springframework.http.client.reactive.ClientHttpRequest;
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ClientResponse;
-import org.springframework.web.reactive.function.client.WebClient;
-
-/**
- * Factory methods for creating {@link Tag Tags} related to a request-response exchange
- * performed by a {@link WebClient}.
- *
- * @author Brian Clozel
- * @author Nishant Raut
- * @since 2.1.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.web.reactive.function.client.DefaultClientRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-public final class WebClientExchangeTags {
-
-	private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate";
-
-	private static final Tag IO_ERROR = Tag.of("status", "IO_ERROR");
-
-	private static final Tag CLIENT_ERROR = Tag.of("status", "CLIENT_ERROR");
-
-	private static final Pattern PATTERN_BEFORE_PATH = Pattern.compile("^https?://[^/]+/");
-
-	private static final Tag CLIENT_NAME_NONE = Tag.of("client.name", "none");
-
-	private WebClientExchangeTags() {
-	}
-
-	/**
-	 * Creates a {@code method} {@code Tag} for the {@link ClientHttpRequest#getMethod()
-	 * method} of the given {@code request}.
-	 * @param request the request
-	 * @return the method tag
-	 */
-	public static Tag method(ClientRequest request) {
-		return Tag.of("method", request.method().name());
-	}
-
-	/**
-	 * Creates a {@code uri} {@code Tag} for the URI path of the given {@code request}.
-	 * @param request the request
-	 * @return the uri tag
-	 */
-	public static Tag uri(ClientRequest request) {
-		String uri = (String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElseGet(() -> request.url().toString());
-		return Tag.of("uri", extractPath(uri));
-	}
-
-	private static String extractPath(String url) {
-		String path = PATTERN_BEFORE_PATH.matcher(url).replaceFirst("");
-		return (path.startsWith("/") ? path : "/" + path);
-	}
-
-	/**
-	 * Creates a {@code status} {@code Tag} derived from the
-	 * {@link ClientResponse#statusCode()} of the given {@code response} if available, the
-	 * thrown exception otherwise, or considers the request as Cancelled as a last resort.
-	 * @param response the response
-	 * @param throwable the exception
-	 * @return the status tag
-	 * @since 2.3.0
-	 */
-	public static Tag status(ClientResponse response, Throwable throwable) {
-		if (response != null) {
-			return Tag.of("status", String.valueOf(response.statusCode().value()));
-		}
-		if (throwable != null) {
-			return (throwable instanceof IOException) ? IO_ERROR : CLIENT_ERROR;
-		}
-		return CLIENT_ERROR;
-	}
-
-	/**
-	 * Create a {@code client.name} {@code Tag} derived from the
-	 * {@link java.net.URI#getHost host} of the {@link ClientRequest#url() URL} of the
-	 * given {@code request}.
-	 * @param request the request
-	 * @return the client.name tag
-	 */
-	public static Tag clientName(ClientRequest request) {
-		String host = request.url().getHost();
-		if (host == null) {
-			return CLIENT_NAME_NONE;
-		}
-		return Tag.of("client.name", host);
-	}
-
-	/**
-	 * Creates an {@code outcome} {@code Tag} derived from the
-	 * {@link ClientResponse#statusCode() status} of the given {@code response}.
-	 * @param response the response
-	 * @return the outcome tag
-	 * @since 2.2.0
-	 */
-	public static Tag outcome(ClientResponse response) {
-		Outcome outcome = (response != null) ? Outcome.forStatus(response.statusCode().value()) : Outcome.UNKNOWN;
-		return outcome.asTag();
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsProvider.java
deleted file mode 100644
index 7d522e48d2b3..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsProvider.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.reactive.client;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ClientResponse;
-
-/**
- * {@link Tag Tags} provider for an exchange performed by a
- * {@link org.springframework.web.reactive.function.client.WebClient}.
- *
- * @author Brian Clozel
- * @since 2.1.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.web.reactive.function.client.ClientRequestObservationConvention}
- */
-@FunctionalInterface
-@Deprecated(since = "3.0.0", forRemoval = true)
-public interface WebClientExchangeTagsProvider {
-
-	/**
-	 * Provide tags to be associated with metrics for the client exchange.
-	 * @param request the client request
-	 * @param response the server response (may be {@code null})
-	 * @param throwable the exception (may be {@code null})
-	 * @return tags to associate with metrics for the request and response exchange
-	 */
-	Iterable tags(ClientRequest request, ClientResponse response, Throwable throwable);
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java
deleted file mode 100644
index ef319dc065ad..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.reactive.server;
-
-import java.util.Collections;
-import java.util.List;
-
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.core.instrument.Tags;
-
-import org.springframework.web.server.ServerWebExchange;
-
-/**
- * Default implementation of {@link WebFluxTagsProvider}.
- *
- * @author Jon Schneider
- * @author Andy Wilkinson
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-@SuppressWarnings("removal")
-public class DefaultWebFluxTagsProvider implements WebFluxTagsProvider {
-
-	private final boolean ignoreTrailingSlash;
-
-	private final List contributors;
-
-	public DefaultWebFluxTagsProvider() {
-		this(false);
-	}
-
-	/**
-	 * Creates a new {@link DefaultWebFluxTagsProvider} that will provide tags from the
-	 * given {@code contributors} in addition to its own.
-	 * @param contributors the contributors that will provide additional tags
-	 * @since 2.3.0
-	 */
-	public DefaultWebFluxTagsProvider(List contributors) {
-		this(false, contributors);
-	}
-
-	public DefaultWebFluxTagsProvider(boolean ignoreTrailingSlash) {
-		this(ignoreTrailingSlash, Collections.emptyList());
-	}
-
-	/**
-	 * Creates a new {@link DefaultWebFluxTagsProvider} that will provide tags from the
-	 * given {@code contributors} in addition to its own.
-	 * @param ignoreTrailingSlash whether trailing slashes should be ignored when
-	 * determining the {@code uri} tag.
-	 * @param contributors the contributors that will provide additional tags
-	 * @since 2.3.0
-	 */
-	public DefaultWebFluxTagsProvider(boolean ignoreTrailingSlash, List contributors) {
-		this.ignoreTrailingSlash = ignoreTrailingSlash;
-		this.contributors = contributors;
-	}
-
-	@Override
-	public Iterable httpRequestTags(ServerWebExchange exchange, Throwable exception) {
-		Tags tags = Tags.empty();
-		tags = tags.and(WebFluxTags.method(exchange));
-		tags = tags.and(WebFluxTags.uri(exchange, this.ignoreTrailingSlash));
-		tags = tags.and(WebFluxTags.exception(exception));
-		tags = tags.and(WebFluxTags.status(exchange));
-		tags = tags.and(WebFluxTags.outcome(exchange, exception));
-		for (WebFluxTagsContributor contributor : this.contributors) {
-			tags = tags.and(contributor.httpRequestTags(exchange, exception));
-		}
-		return tags;
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java
deleted file mode 100644
index ecada43258a4..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.reactive.server;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.boot.actuate.metrics.http.Outcome;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.HttpStatusCode;
-import org.springframework.util.StringUtils;
-import org.springframework.web.reactive.HandlerMapping;
-import org.springframework.web.server.ServerWebExchange;
-import org.springframework.web.util.pattern.PathPattern;
-
-/**
- * Factory methods for {@link Tag Tags} associated with a request-response exchange that
- * is handled by WebFlux.
- *
- * @author Jon Schneider
- * @author Andy Wilkinson
- * @author Michael McFadyen
- * @author Brian Clozel
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-public final class WebFluxTags {
-
-	private static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND");
-
-	private static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION");
-
-	private static final Tag URI_ROOT = Tag.of("uri", "root");
-
-	private static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN");
-
-	private static final Tag EXCEPTION_NONE = Tag.of("exception", "None");
-
-	private static final Pattern FORWARD_SLASHES_PATTERN = Pattern.compile("//+");
-
-	private static final Set DISCONNECTED_CLIENT_EXCEPTIONS = new HashSet<>(
-			Arrays.asList("AbortedException", "ClientAbortException", "EOFException", "EofException"));
-
-	private WebFluxTags() {
-	}
-
-	/**
-	 * Creates a {@code method} tag based on the
-	 * {@link org.springframework.http.server.reactive.ServerHttpRequest#getMethod()
-	 * method} of the {@link ServerWebExchange#getRequest()} request of the given
-	 * {@code exchange}.
-	 * @param exchange the exchange
-	 * @return the method tag whose value is a capitalized method (e.g. GET).
-	 */
-	public static Tag method(ServerWebExchange exchange) {
-		return Tag.of("method", exchange.getRequest().getMethod().name());
-	}
-
-	/**
-	 * Creates a {@code status} tag based on the response status of the given
-	 * {@code exchange}.
-	 * @param exchange the exchange
-	 * @return the status tag derived from the response status
-	 */
-	public static Tag status(ServerWebExchange exchange) {
-		HttpStatusCode status = exchange.getResponse().getStatusCode();
-		if (status == null) {
-			status = HttpStatus.OK;
-		}
-		return Tag.of("status", String.valueOf(status.value()));
-	}
-
-	/**
-	 * Creates a {@code uri} tag based on the URI of the given {@code exchange}. Uses the
-	 * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if
-	 * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND}
-	 * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN}
-	 * for all other requests.
-	 * @param exchange the exchange
-	 * @return the uri tag derived from the exchange
-	 */
-	public static Tag uri(ServerWebExchange exchange) {
-		return uri(exchange, false);
-	}
-
-	/**
-	 * Creates a {@code uri} tag based on the URI of the given {@code exchange}. Uses the
-	 * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if
-	 * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND}
-	 * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN}
-	 * for all other requests.
-	 * @param exchange the exchange
-	 * @param ignoreTrailingSlash whether to ignore the trailing slash
-	 * @return the uri tag derived from the exchange
-	 */
-	public static Tag uri(ServerWebExchange exchange, boolean ignoreTrailingSlash) {
-		PathPattern pathPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
-		if (pathPattern != null) {
-			String patternString = pathPattern.getPatternString();
-			if (ignoreTrailingSlash && patternString.length() > 1) {
-				patternString = removeTrailingSlash(patternString);
-			}
-			if (patternString.isEmpty()) {
-				return URI_ROOT;
-			}
-			return Tag.of("uri", patternString);
-		}
-		HttpStatusCode status = exchange.getResponse().getStatusCode();
-		if (status != null) {
-			if (status.is3xxRedirection()) {
-				return URI_REDIRECTION;
-			}
-			if (status == HttpStatus.NOT_FOUND) {
-				return URI_NOT_FOUND;
-			}
-		}
-		String path = getPathInfo(exchange);
-		if (path.isEmpty()) {
-			return URI_ROOT;
-		}
-		return URI_UNKNOWN;
-	}
-
-	private static String getPathInfo(ServerWebExchange exchange) {
-		String path = exchange.getRequest().getPath().value();
-		String uri = StringUtils.hasText(path) ? path : "/";
-		String singleSlashes = FORWARD_SLASHES_PATTERN.matcher(uri).replaceAll("/");
-		return removeTrailingSlash(singleSlashes);
-	}
-
-	private static String removeTrailingSlash(String text) {
-		if (!StringUtils.hasLength(text)) {
-			return text;
-		}
-		return text.endsWith("/") ? text.substring(0, text.length() - 1) : text;
-	}
-
-	/**
-	 * Creates an {@code exception} tag based on the {@link Class#getSimpleName() simple
-	 * name} of the class of the given {@code exception}.
-	 * @param exception the exception, may be {@code null}
-	 * @return the exception tag derived from the exception
-	 */
-	public static Tag exception(Throwable exception) {
-		if (exception != null) {
-			String simpleName = exception.getClass().getSimpleName();
-			return Tag.of("exception", StringUtils.hasText(simpleName) ? simpleName : exception.getClass().getName());
-		}
-		return EXCEPTION_NONE;
-	}
-
-	/**
-	 * Creates an {@code outcome} tag based on the response status of the given
-	 * {@code exchange} and the exception thrown during request processing.
-	 * @param exchange the exchange
-	 * @param exception the termination signal sent by the publisher
-	 * @return the outcome tag derived from the response status
-	 * @since 2.5.0
-	 */
-	public static Tag outcome(ServerWebExchange exchange, Throwable exception) {
-		if (exception != null) {
-			if (DISCONNECTED_CLIENT_EXCEPTIONS.contains(exception.getClass().getSimpleName())) {
-				return Outcome.UNKNOWN.asTag();
-			}
-		}
-		HttpStatusCode statusCode = exchange.getResponse().getStatusCode();
-		Outcome outcome = (statusCode != null) ? Outcome.forStatus(statusCode.value()) : Outcome.SUCCESS;
-		return outcome.asTag();
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java
deleted file mode 100644
index 6bd9958dfca3..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsContributor.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.reactive.server;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.web.server.ServerWebExchange;
-
-/**
- * A contributor of {@link Tag Tags} for WebFlux-based request handling. Typically used by
- * a {@link WebFluxTagsProvider} to provide tags in addition to its defaults.
- *
- * @author Andy Wilkinson
- * @since 2.3.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention}
- */
-@FunctionalInterface
-@Deprecated(since = "3.0.0", forRemoval = true)
-public interface WebFluxTagsContributor {
-
-	/**
-	 * Provides tags to be associated with metrics for the given {@code exchange}.
-	 * @param exchange the exchange
-	 * @param ex the current exception (may be {@code null})
-	 * @return tags to associate with metrics for the request and response exchange
-	 */
-	Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex);
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java
deleted file mode 100644
index 081c9598cc89..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.reactive.server;
-
-import io.micrometer.core.instrument.Tag;
-
-import org.springframework.web.server.ServerWebExchange;
-
-/**
- * Provides {@link Tag Tags} for WebFlux-based request handling.
- *
- * @author Jon Schneider
- * @author Andy Wilkinson
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.reactive.observation.ServerRequestObservationConvention}
- */
-@FunctionalInterface
-@Deprecated(since = "3.0.0", forRemoval = true)
-public interface WebFluxTagsProvider {
-
-	/**
-	 * Provides tags to be associated with metrics for the given {@code exchange}.
-	 * @param exchange the exchange
-	 * @param ex the current exception (may be {@code null})
-	 * @return tags to associate with metrics for the request and response exchange
-	 */
-	Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex);
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/package-info.java
deleted file mode 100644
index 1167af5ca302..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2012-2019 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.
- */
-
-/**
- * Actuator support for WebFlux metrics.
- */
-package org.springframework.boot.actuate.metrics.web.reactive.server;
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java
deleted file mode 100644
index ac50670ca326..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/DefaultWebMvcTagsProvider.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.servlet;
-
-import java.util.Collections;
-import java.util.List;
-
-import io.micrometer.core.instrument.Tag;
-import io.micrometer.core.instrument.Tags;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-/**
- * Default implementation of {@link WebMvcTagsProvider}.
- *
- * @author Jon Schneider
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.observation.ServerRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-@SuppressWarnings("removal")
-public class DefaultWebMvcTagsProvider implements WebMvcTagsProvider {
-
-	private final boolean ignoreTrailingSlash;
-
-	private final List contributors;
-
-	public DefaultWebMvcTagsProvider() {
-		this(false);
-	}
-
-	/**
-	 * Creates a new {@link DefaultWebMvcTagsProvider} that will provide tags from the
-	 * given {@code contributors} in addition to its own.
-	 * @param contributors the contributors that will provide additional tags
-	 * @since 2.3.0
-	 */
-	public DefaultWebMvcTagsProvider(List contributors) {
-		this(false, contributors);
-	}
-
-	public DefaultWebMvcTagsProvider(boolean ignoreTrailingSlash) {
-		this(ignoreTrailingSlash, Collections.emptyList());
-	}
-
-	/**
-	 * Creates a new {@link DefaultWebMvcTagsProvider} that will provide tags from the
-	 * given {@code contributors} in addition to its own.
-	 * @param ignoreTrailingSlash whether trailing slashes should be ignored when
-	 * determining the {@code uri} tag.
-	 * @param contributors the contributors that will provide additional tags
-	 * @since 2.3.0
-	 */
-	public DefaultWebMvcTagsProvider(boolean ignoreTrailingSlash, List contributors) {
-		this.ignoreTrailingSlash = ignoreTrailingSlash;
-		this.contributors = contributors;
-	}
-
-	@Override
-	public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler,
-			Throwable exception) {
-		Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response, this.ignoreTrailingSlash),
-				WebMvcTags.exception(exception), WebMvcTags.status(response), WebMvcTags.outcome(response));
-		for (WebMvcTagsContributor contributor : this.contributors) {
-			tags = tags.and(contributor.getTags(request, response, handler, exception));
-		}
-		return tags;
-	}
-
-	@Override
-	public Iterable getLongRequestTags(HttpServletRequest request, Object handler) {
-		Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, null, this.ignoreTrailingSlash));
-		for (WebMvcTagsContributor contributor : this.contributors) {
-			tags = tags.and(contributor.getLongRequestTags(request, handler));
-		}
-		return tags;
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTags.java
deleted file mode 100644
index db5c1f0e49c5..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTags.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.servlet;
-
-import java.util.regex.Pattern;
-
-import io.micrometer.core.instrument.Tag;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-import org.springframework.boot.actuate.metrics.http.Outcome;
-import org.springframework.http.HttpStatus;
-import org.springframework.util.StringUtils;
-import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.util.pattern.PathPattern;
-
-/**
- * Factory methods for {@link Tag Tags} associated with a request-response exchange that
- * is handled by Spring MVC.
- *
- * @author Jon Schneider
- * @author Andy Wilkinson
- * @author Brian Clozel
- * @author Michael McFadyen
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.observation.ServerRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-public final class WebMvcTags {
-
-	private static final String DATA_REST_PATH_PATTERN_ATTRIBUTE = "org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.EFFECTIVE_REPOSITORY_RESOURCE_LOOKUP_PATH";
-
-	private static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND");
-
-	private static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION");
-
-	private static final Tag URI_ROOT = Tag.of("uri", "root");
-
-	private static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN");
-
-	private static final Tag EXCEPTION_NONE = Tag.of("exception", "None");
-
-	private static final Tag STATUS_UNKNOWN = Tag.of("status", "UNKNOWN");
-
-	private static final Tag METHOD_UNKNOWN = Tag.of("method", "UNKNOWN");
-
-	private static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/$");
-
-	private static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+");
-
-	private WebMvcTags() {
-	}
-
-	/**
-	 * Creates a {@code method} tag based on the {@link HttpServletRequest#getMethod()
-	 * method} of the given {@code request}.
-	 * @param request the request
-	 * @return the method tag whose value is a capitalized method (e.g. GET).
-	 */
-	public static Tag method(HttpServletRequest request) {
-		return (request != null) ? Tag.of("method", request.getMethod()) : METHOD_UNKNOWN;
-	}
-
-	/**
-	 * Creates a {@code status} tag based on the status of the given {@code response}.
-	 * @param response the HTTP response
-	 * @return the status tag derived from the status of the response
-	 */
-	public static Tag status(HttpServletResponse response) {
-		return (response != null) ? Tag.of("status", Integer.toString(response.getStatus())) : STATUS_UNKNOWN;
-	}
-
-	/**
-	 * Creates a {@code uri} tag based on the URI of the given {@code request}. Uses the
-	 * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if
-	 * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND}
-	 * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN}
-	 * for all other requests.
-	 * @param request the request
-	 * @param response the response
-	 * @return the uri tag derived from the request
-	 */
-	public static Tag uri(HttpServletRequest request, HttpServletResponse response) {
-		return uri(request, response, false);
-	}
-
-	/**
-	 * Creates a {@code uri} tag based on the URI of the given {@code request}. Uses the
-	 * {@link HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE} best matching pattern if
-	 * available. Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND}
-	 * for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN}
-	 * for all other requests.
-	 * @param request the request
-	 * @param response the response
-	 * @param ignoreTrailingSlash whether to ignore the trailing slash
-	 * @return the uri tag derived from the request
-	 */
-	public static Tag uri(HttpServletRequest request, HttpServletResponse response, boolean ignoreTrailingSlash) {
-		if (request != null) {
-			String pattern = getMatchingPattern(request);
-			if (pattern != null) {
-				if (ignoreTrailingSlash && pattern.length() > 1) {
-					pattern = TRAILING_SLASH_PATTERN.matcher(pattern).replaceAll("");
-				}
-				if (pattern.isEmpty()) {
-					return URI_ROOT;
-				}
-				return Tag.of("uri", pattern);
-			}
-			if (response != null) {
-				HttpStatus status = extractStatus(response);
-				if (status != null) {
-					if (status.is3xxRedirection()) {
-						return URI_REDIRECTION;
-					}
-					if (status == HttpStatus.NOT_FOUND) {
-						return URI_NOT_FOUND;
-					}
-				}
-			}
-			String pathInfo = getPathInfo(request);
-			if (pathInfo.isEmpty()) {
-				return URI_ROOT;
-			}
-		}
-		return URI_UNKNOWN;
-	}
-
-	private static HttpStatus extractStatus(HttpServletResponse response) {
-		try {
-			return HttpStatus.valueOf(response.getStatus());
-		}
-		catch (IllegalArgumentException ex) {
-			return null;
-		}
-	}
-
-	private static String getMatchingPattern(HttpServletRequest request) {
-		PathPattern dataRestPathPattern = (PathPattern) request.getAttribute(DATA_REST_PATH_PATTERN_ATTRIBUTE);
-		if (dataRestPathPattern != null) {
-			return dataRestPathPattern.getPatternString();
-		}
-		return (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
-	}
-
-	private static String getPathInfo(HttpServletRequest request) {
-		String pathInfo = request.getPathInfo();
-		String uri = StringUtils.hasText(pathInfo) ? pathInfo : "/";
-		uri = MULTIPLE_SLASH_PATTERN.matcher(uri).replaceAll("/");
-		return TRAILING_SLASH_PATTERN.matcher(uri).replaceAll("");
-	}
-
-	/**
-	 * Creates an {@code exception} tag based on the {@link Class#getSimpleName() simple
-	 * name} of the class of the given {@code exception}.
-	 * @param exception the exception, may be {@code null}
-	 * @return the exception tag derived from the exception
-	 */
-	public static Tag exception(Throwable exception) {
-		if (exception != null) {
-			String simpleName = exception.getClass().getSimpleName();
-			return Tag.of("exception", StringUtils.hasText(simpleName) ? simpleName : exception.getClass().getName());
-		}
-		return EXCEPTION_NONE;
-	}
-
-	/**
-	 * Creates an {@code outcome} tag based on the status of the given {@code response}.
-	 * @param response the HTTP response
-	 * @return the outcome tag derived from the status of the response
-	 * @since 2.1.0
-	 */
-	public static Tag outcome(HttpServletResponse response) {
-		Outcome outcome = (response != null) ? Outcome.forStatus(response.getStatus()) : Outcome.UNKNOWN;
-		return outcome.asTag();
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java
deleted file mode 100644
index f27b2115af67..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsContributor.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.servlet;
-
-import io.micrometer.core.instrument.LongTaskTimer;
-import io.micrometer.core.instrument.Tag;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-/**
- * A contributor of {@link Tag Tags} for Spring MVC-based request handling. Typically used
- * by a {@link WebMvcTagsProvider} to provide tags in addition to its defaults.
- *
- * @author Andy Wilkinson
- * @since 2.3.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.observation.ServerRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-public interface WebMvcTagsContributor {
-
-	/**
-	 * Provides tags to be associated with metrics for the given {@code request} and
-	 * {@code response} exchange.
-	 * @param request the request
-	 * @param response the response
-	 * @param handler the handler for the request or {@code null} if the handler is
-	 * unknown
-	 * @param exception the current exception, if any
-	 * @return tags to associate with metrics for the request and response exchange
-	 */
-	Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler,
-			Throwable exception);
-
-	/**
-	 * Provides tags to be used by {@link LongTaskTimer long task timers}.
-	 * @param request the HTTP request
-	 * @param handler the handler for the request or {@code null} if the handler is
-	 * unknown
-	 * @return tags to associate with metrics recorded for the request
-	 */
-	Iterable getLongRequestTags(HttpServletRequest request, Object handler);
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsProvider.java
deleted file mode 100644
index 09206f727b1b..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcTagsProvider.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.servlet;
-
-import io.micrometer.core.instrument.LongTaskTimer;
-import io.micrometer.core.instrument.Tag;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-/**
- * Provides {@link Tag Tags} for Spring MVC-based request handling.
- *
- * @author Jon Schneider
- * @author Andy Wilkinson
- * @since 2.0.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link org.springframework.http.server.observation.ServerRequestObservationConvention}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-public interface WebMvcTagsProvider {
-
-	/**
-	 * Provides tags to be associated with metrics for the given {@code request} and
-	 * {@code response} exchange.
-	 * @param request the request
-	 * @param response the response
-	 * @param handler the handler for the request or {@code null} if the handler is
-	 * unknown
-	 * @param exception the current exception, if any
-	 * @return tags to associate with metrics for the request and response exchange
-	 */
-	Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler,
-			Throwable exception);
-
-	/**
-	 * Provides tags to be used by {@link LongTaskTimer long task timers}.
-	 * @param request the HTTP request
-	 * @param handler the handler for the request or {@code null} if the handler is
-	 * unknown
-	 * @return tags to associate with metrics recorded for the request
-	 */
-	Iterable getLongRequestTags(HttpServletRequest request, Object handler);
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/package-info.java
deleted file mode 100644
index 22bbf429f87a..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2012-2019 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.
- */
-
-/**
- * Actuator support for Spring MVC metrics.
- */
-package org.springframework.boot.actuate.metrics.web.servlet;
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java
deleted file mode 100644
index 4bdcb94ce427..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/DefaultWebMvcTagsProviderTests.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2012-2023 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.actuate.endpoint.web.servlet;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import io.micrometer.core.instrument.Tag;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.web.servlet.HandlerMapping;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link DefaultWebMvcTagsProvider}.
- *
- * @author Andy Wilkinson
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class DefaultWebMvcTagsProviderTests {
-
-	@Test
-	void whenTagsAreProvidedThenDefaultTagsArePresent() {
-		Map tags = asMap(new DefaultWebMvcTagsProvider().getTags(null, null, null, null));
-		assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri");
-	}
-
-	@Test
-	void givenSomeContributorsWhenTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() {
-		Map tags = asMap(
-				new DefaultWebMvcTagsProvider(Arrays.asList(new TestWebMvcTagsContributor("alpha"),
-						new TestWebMvcTagsContributor("bravo", "charlie")))
-					.getTags(null, null, null, null));
-		assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri", "alpha", "bravo",
-				"charlie");
-	}
-
-	@Test
-	void whenLongRequestTagsAreProvidedThenDefaultTagsArePresent() {
-		Map tags = asMap(new DefaultWebMvcTagsProvider().getLongRequestTags(null, null));
-		assertThat(tags).containsOnlyKeys("method", "uri");
-	}
-
-	@Test
-	void givenSomeContributorsWhenLongRequestTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() {
-		Map tags = asMap(
-				new DefaultWebMvcTagsProvider(Arrays.asList(new TestWebMvcTagsContributor("alpha"),
-						new TestWebMvcTagsContributor("bravo", "charlie")))
-					.getLongRequestTags(null, null));
-		assertThat(tags).containsOnlyKeys("method", "uri", "alpha", "bravo", "charlie");
-	}
-
-	@Test
-	void trailingSlashIsIncludedByDefault() {
-		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/the/uri/");
-		request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "{one}/{two}/");
-		Map tags = asMap(new DefaultWebMvcTagsProvider().getTags(request, null, null, null));
-		assertThat(tags.get("uri").getValue()).isEqualTo("{one}/{two}/");
-	}
-
-	@Test
-	void trailingSlashCanBeIgnored() {
-		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/the/uri/");
-		request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "{one}/{two}/");
-		Map tags = asMap(new DefaultWebMvcTagsProvider(true).getTags(request, null, null, null));
-		assertThat(tags.get("uri").getValue()).isEqualTo("{one}/{two}");
-	}
-
-	private Map asMap(Iterable tags) {
-		return StreamSupport.stream(tags.spliterator(), false)
-			.collect(Collectors.toMap(Tag::getKey, Function.identity()));
-	}
-
-	private static final class TestWebMvcTagsContributor implements WebMvcTagsContributor {
-
-		private final List tagNames;
-
-		private TestWebMvcTagsContributor(String... tagNames) {
-			this.tagNames = Arrays.asList(tagNames);
-		}
-
-		@Override
-		public Iterable getTags(HttpServletRequest request, HttpServletResponse response, Object handler,
-				Throwable exception) {
-			return this.tagNames.stream().map((name) -> Tag.of(name, "value")).toList();
-		}
-
-		@Override
-		public Iterable getLongRequestTags(HttpServletRequest request, Object handler) {
-			return this.tagNames.stream().map((name) -> Tag.of(name, "value")).toList();
-		}
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcTagsTests.java
deleted file mode 100644
index 7097ee9dfd79..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/WebMvcTagsTests.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.endpoint.web.servlet;
-
-import io.micrometer.core.instrument.Tag;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTags;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.util.pattern.PathPatternParser;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link WebMvcTags}.
- *
- * @author Andy Wilkinson
- * @author Brian Clozel
- * @author Michael McFadyen
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class WebMvcTagsTests {
-
-	private final MockHttpServletRequest request = new MockHttpServletRequest();
-
-	private final MockHttpServletResponse response = new MockHttpServletResponse();
-
-	@Test
-	void uriTagIsDataRestsEffectiveRepositoryLookupPathWhenAvailable() {
-		this.request.setAttribute(
-				"org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.EFFECTIVE_REPOSITORY_RESOURCE_LOOKUP_PATH",
-				new PathPatternParser().parse("/api/cities"));
-		this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/api/{repository}");
-		Tag tag = WebMvcTags.uri(this.request, this.response);
-		assertThat(tag.getValue()).isEqualTo("/api/cities");
-	}
-
-	@Test
-	void uriTagValueIsBestMatchingPatternWhenAvailable() {
-		this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/spring/");
-		this.response.setStatus(301);
-		Tag tag = WebMvcTags.uri(this.request, this.response);
-		assertThat(tag.getValue()).isEqualTo("/spring/");
-	}
-
-	@Test
-	void uriTagValueIsRootWhenBestMatchingPatternIsEmpty() {
-		this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "");
-		this.response.setStatus(301);
-		Tag tag = WebMvcTags.uri(this.request, this.response);
-		assertThat(tag.getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashRemoveTrailingSlash() {
-		this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/spring/");
-		Tag tag = WebMvcTags.uri(this.request, this.response, true);
-		assertThat(tag.getValue()).isEqualTo("/spring");
-	}
-
-	@Test
-	void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashKeepSingleSlash() {
-		this.request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/");
-		Tag tag = WebMvcTags.uri(this.request, this.response, true);
-		assertThat(tag.getValue()).isEqualTo("/");
-	}
-
-	@Test
-	void uriTagValueIsRootWhenRequestHasNoPatternOrPathInfo() {
-		assertThat(WebMvcTags.uri(this.request, null).getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagValueIsRootWhenRequestHasNoPatternAndSlashPathInfo() {
-		this.request.setPathInfo("/");
-		assertThat(WebMvcTags.uri(this.request, null).getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagValueIsUnknownWhenRequestHasNoPatternAndNonRootPathInfo() {
-		this.request.setPathInfo("/example");
-		assertThat(WebMvcTags.uri(this.request, null).getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void uriTagValueIsRedirectionWhenResponseStatusIs3xx() {
-		this.response.setStatus(301);
-		Tag tag = WebMvcTags.uri(this.request, this.response);
-		assertThat(tag.getValue()).isEqualTo("REDIRECTION");
-	}
-
-	@Test
-	void uriTagValueIsNotFoundWhenResponseStatusIs404() {
-		this.response.setStatus(404);
-		Tag tag = WebMvcTags.uri(this.request, this.response);
-		assertThat(tag.getValue()).isEqualTo("NOT_FOUND");
-	}
-
-	@Test
-	void uriTagToleratesCustomResponseStatus() {
-		this.response.setStatus(601);
-		Tag tag = WebMvcTags.uri(this.request, this.response);
-		assertThat(tag.getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagIsUnknownWhenRequestIsNull() {
-		Tag tag = WebMvcTags.uri(null, null);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseIsNull() {
-		Tag tag = WebMvcTags.outcome(null);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void outcomeTagIsInformationalWhenResponseIs1xx() {
-		this.response.setStatus(100);
-		Tag tag = WebMvcTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("INFORMATIONAL");
-	}
-
-	@Test
-	void outcomeTagIsSuccessWhenResponseIs2xx() {
-		this.response.setStatus(200);
-		Tag tag = WebMvcTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("SUCCESS");
-	}
-
-	@Test
-	void outcomeTagIsRedirectionWhenResponseIs3xx() {
-		this.response.setStatus(301);
-		Tag tag = WebMvcTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("REDIRECTION");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIs4xx() {
-		this.response.setStatus(400);
-		Tag tag = WebMvcTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() {
-		this.response.setStatus(490);
-		Tag tag = WebMvcTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsServerErrorWhenResponseIs5xx() {
-		this.response.setStatus(500);
-		Tag tag = WebMvcTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("SERVER_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() {
-		this.response.setStatus(701);
-		Tag tag = WebMvcTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java
index ca8d61d3108c..6960ea722752 100644
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java
@@ -140,18 +140,6 @@ void shutdownWhenDoesNotOwnSchedulerDoesNotShutdownScheduler() {
 		then(otherScheduler).should(never()).shutdown();
 	}
 
-	@Test
-	@SuppressWarnings("removal")
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	void shutdownWhenShutdownOperationIsPushPerformsPushAddOnShutdown() throws Exception {
-		givenScheduleAtFixedRateWithReturnFuture();
-		PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry,
-				this.scheduler, this.pushRate, "job", this.groupingKey, ShutdownOperation.PUSH);
-		manager.shutdown();
-		then(this.future).should().cancel(false);
-		then(this.pushGateway).should().pushAdd(this.registry, "job", this.groupingKey);
-	}
-
 	@Test
 	void shutdownWhenShutdownOperationIsPostPerformsPushAddOnShutdown() throws Exception {
 		givenScheduleAtFixedRateWithReturnFuture();
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java
deleted file mode 100644
index 7dd9a2322b37..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.actuate.metrics.web.client;
-
-import java.io.IOException;
-import java.net.URI;
-
-import io.micrometer.core.instrument.Tag;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.http.HttpStatusCode;
-import org.springframework.http.client.ClientHttpRequest;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.mock.http.client.MockClientHttpResponse;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-
-/**
- * Tests for {@link RestTemplateExchangeTags}.
- *
- * @author Nishant Raut
- * @author Brian Clozel
- */
-@SuppressWarnings({ "removal" })
-@Deprecated(since = "3.0.0", forRemoval = true)
-class RestTemplateExchangeTagsTests {
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseIsNull() {
-		Tag tag = RestTemplateExchangeTags.outcome(null);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void outcomeTagIsInformationalWhenResponseIs1xx() {
-		ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.CONTINUE);
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("INFORMATIONAL");
-	}
-
-	@Test
-	void outcomeTagIsSuccessWhenResponseIs2xx() {
-		ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.OK);
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("SUCCESS");
-	}
-
-	@Test
-	void outcomeTagIsRedirectionWhenResponseIs3xx() {
-		ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.MOVED_PERMANENTLY);
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("REDIRECTION");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIs4xx() {
-		ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.BAD_REQUEST);
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsServerErrorWhenResponseIs5xx() {
-		ClientHttpResponse response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.BAD_GATEWAY);
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("SERVER_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseThrowsIOException() throws Exception {
-		ClientHttpResponse response = mock(ClientHttpResponse.class);
-		given(response.getStatusCode()).willThrow(IOException.class);
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() throws IOException {
-		ClientHttpResponse response = mock(ClientHttpResponse.class);
-		given(response.getStatusCode()).willReturn(HttpStatusCode.valueOf(490));
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() throws IOException {
-		ClientHttpResponse response = mock(ClientHttpResponse.class);
-		given(response.getStatusCode()).willReturn(HttpStatusCode.valueOf(701));
-		Tag tag = RestTemplateExchangeTags.outcome(response);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void clientNameTagIsHostOfRequestUri() {
-		ClientHttpRequest request = mock(ClientHttpRequest.class);
-		given(request.getURI()).willReturn(URI.create("https://example.org"));
-		Tag tag = RestTemplateExchangeTags.clientName(request);
-		assertThat(tag).isEqualTo(Tag.of("client.name", "example.org"));
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java
deleted file mode 100644
index c04846e71696..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2012-2023 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.actuate.metrics.web.reactive.client;
-
-import java.io.IOException;
-import java.net.URI;
-
-import io.micrometer.core.instrument.Tag;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ClientResponse;
-import org.springframework.web.reactive.function.client.WebClient;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-
-/**
- * Tests for {@link DefaultWebClientExchangeTagsProvider}
- *
- * @author Brian Clozel
- * @author Nishant Raut
- */
-@SuppressWarnings({ "deprecation", "removal" })
-class DefaultWebClientExchangeTagsProviderTests {
-
-	private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate";
-
-	private final WebClientExchangeTagsProvider tagsProvider = new DefaultWebClientExchangeTagsProvider();
-
-	private ClientRequest request;
-
-	private ClientResponse response;
-
-	@BeforeEach
-	void setup() {
-		this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot"))
-			.attribute(URI_TEMPLATE_ATTRIBUTE, "https://example.org/projects/{project}")
-			.build();
-		this.response = mock(ClientResponse.class);
-		given(this.response.statusCode()).willReturn(HttpStatus.OK);
-	}
-
-	@Test
-	void tagsShouldBePopulated() {
-		Iterable tags = this.tagsProvider.tags(this.request, this.response, null);
-		assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"),
-				Tag.of("client.name", "example.org"), Tag.of("status", "200"), Tag.of("outcome", "SUCCESS"));
-	}
-
-	@Test
-	void tagsWhenNoUriTemplateShouldProvideUriPath() {
-		ClientRequest request = ClientRequest
-			.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot"))
-			.build();
-		Iterable tags = this.tagsProvider.tags(request, this.response, null);
-		assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/spring-boot"),
-				Tag.of("client.name", "example.org"), Tag.of("status", "200"), Tag.of("outcome", "SUCCESS"));
-	}
-
-	@Test
-	void tagsWhenIoExceptionShouldReturnIoErrorStatus() {
-		Iterable tags = this.tagsProvider.tags(this.request, null, new IOException());
-		assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"),
-				Tag.of("client.name", "example.org"), Tag.of("status", "IO_ERROR"), Tag.of("outcome", "UNKNOWN"));
-	}
-
-	@Test
-	void tagsWhenExceptionShouldReturnClientErrorStatus() {
-		Iterable tags = this.tagsProvider.tags(this.request, null, new IllegalArgumentException());
-		assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"),
-				Tag.of("client.name", "example.org"), Tag.of("status", "CLIENT_ERROR"), Tag.of("outcome", "UNKNOWN"));
-	}
-
-	@Test
-	void tagsWhenCancelledRequestShouldReturnClientErrorStatus() {
-		Iterable tags = this.tagsProvider.tags(this.request, null, null);
-		assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"),
-				Tag.of("client.name", "example.org"), Tag.of("status", "CLIENT_ERROR"), Tag.of("outcome", "UNKNOWN"));
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java
deleted file mode 100644
index fbc3860fb4bc..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2012-2023 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.actuate.metrics.web.reactive.client;
-
-import java.io.IOException;
-import java.net.URI;
-
-import io.micrometer.core.instrument.Tag;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.HttpStatusCode;
-import org.springframework.web.reactive.function.client.ClientRequest;
-import org.springframework.web.reactive.function.client.ClientResponse;
-import org.springframework.web.reactive.function.client.WebClient;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-
-/**
- * Tests for {@link WebClientExchangeTags}.
- *
- * @author Brian Clozel
- * @author Nishant Raut
- */
-@SuppressWarnings({ "deprecation", "removal" })
-class WebClientExchangeTagsTests {
-
-	private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate";
-
-	private ClientRequest request;
-
-	private ClientResponse response;
-
-	@BeforeEach
-	void setup() {
-		this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot"))
-			.attribute(URI_TEMPLATE_ATTRIBUTE, "https://example.org/projects/{project}")
-			.build();
-		this.response = mock(ClientResponse.class);
-	}
-
-	@Test
-	void method() {
-		assertThat(WebClientExchangeTags.method(this.request)).isEqualTo(Tag.of("method", "GET"));
-	}
-
-	@Test
-	void uriWhenAbsoluteTemplateIsAvailableShouldReturnTemplate() {
-		assertThat(WebClientExchangeTags.uri(this.request)).isEqualTo(Tag.of("uri", "/projects/{project}"));
-	}
-
-	@Test
-	void uriWhenRelativeTemplateIsAvailableShouldReturnTemplate() {
-		this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot"))
-			.attribute(URI_TEMPLATE_ATTRIBUTE, "/projects/{project}")
-			.build();
-		assertThat(WebClientExchangeTags.uri(this.request)).isEqualTo(Tag.of("uri", "/projects/{project}"));
-	}
-
-	@Test
-	void uriWhenTemplateIsMissingShouldReturnPath() {
-		this.request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot"))
-			.build();
-		assertThat(WebClientExchangeTags.uri(this.request)).isEqualTo(Tag.of("uri", "/projects/spring-boot"));
-	}
-
-	@Test
-	void uriWhenTemplateIsMissingShouldReturnPathWithQueryParams() {
-		this.request = ClientRequest
-			.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot?section=docs"))
-			.build();
-		assertThat(WebClientExchangeTags.uri(this.request))
-			.isEqualTo(Tag.of("uri", "/projects/spring-boot?section=docs"));
-	}
-
-	@Test
-	void clientName() {
-		assertThat(WebClientExchangeTags.clientName(this.request)).isEqualTo(Tag.of("client.name", "example.org"));
-	}
-
-	@Test
-	void status() {
-		given(this.response.statusCode()).willReturn(HttpStatus.OK);
-		assertThat(WebClientExchangeTags.status(this.response, null)).isEqualTo(Tag.of("status", "200"));
-	}
-
-	@Test
-	void statusWhenIOException() {
-		assertThat(WebClientExchangeTags.status(null, new IOException())).isEqualTo(Tag.of("status", "IO_ERROR"));
-	}
-
-	@Test
-	void statusWhenClientException() {
-		assertThat(WebClientExchangeTags.status(null, new IllegalArgumentException()))
-			.isEqualTo(Tag.of("status", "CLIENT_ERROR"));
-	}
-
-	@Test
-	void statusWhenNonStandard() {
-		given(this.response.statusCode()).willReturn(HttpStatusCode.valueOf(490));
-		assertThat(WebClientExchangeTags.status(this.response, null)).isEqualTo(Tag.of("status", "490"));
-	}
-
-	@Test
-	void statusWhenCancelled() {
-		assertThat(WebClientExchangeTags.status(null, null)).isEqualTo(Tag.of("status", "CLIENT_ERROR"));
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseIsNull() {
-		Tag tag = WebClientExchangeTags.outcome(null);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void outcomeTagIsInformationalWhenResponseIs1xx() {
-		given(this.response.statusCode()).willReturn(HttpStatus.CONTINUE);
-		Tag tag = WebClientExchangeTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("INFORMATIONAL");
-	}
-
-	@Test
-	void outcomeTagIsSuccessWhenResponseIs2xx() {
-		given(this.response.statusCode()).willReturn(HttpStatus.OK);
-		Tag tag = WebClientExchangeTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("SUCCESS");
-	}
-
-	@Test
-	void outcomeTagIsRedirectionWhenResponseIs3xx() {
-		given(this.response.statusCode()).willReturn(HttpStatus.MOVED_PERMANENTLY);
-		Tag tag = WebClientExchangeTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("REDIRECTION");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIs4xx() {
-		given(this.response.statusCode()).willReturn(HttpStatus.BAD_REQUEST);
-		Tag tag = WebClientExchangeTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsServerErrorWhenResponseIs5xx() {
-		given(this.response.statusCode()).willReturn(HttpStatus.BAD_GATEWAY);
-		Tag tag = WebClientExchangeTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("SERVER_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() {
-		given(this.response.statusCode()).willReturn(HttpStatusCode.valueOf(490));
-		Tag tag = WebClientExchangeTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() {
-		given(this.response.statusCode()).willReturn(HttpStatusCode.valueOf(701));
-		Tag tag = WebClientExchangeTags.outcome(this.response);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java
deleted file mode 100644
index 39240b2fd341..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProviderTests.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012-2023 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.actuate.metrics.web.reactive.server;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import io.micrometer.core.instrument.Tag;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
-import org.springframework.mock.web.server.MockServerWebExchange;
-import org.springframework.web.server.ServerWebExchange;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link DefaultWebFluxTagsProvider}.
- *
- * @author Andy Wilkinson
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class DefaultWebFluxTagsProviderTests {
-
-	@Test
-	void whenTagsAreProvidedThenDefaultTagsArePresent() {
-		ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test"));
-		Map tags = asMap(new DefaultWebFluxTagsProvider().httpRequestTags(exchange, null));
-		assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri");
-	}
-
-	@Test
-	void givenSomeContributorsWhenTagsAreProvidedThenDefaultTagsAndContributedTagsArePresent() {
-		ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test"));
-		Map tags = asMap(
-				new DefaultWebFluxTagsProvider(Arrays.asList(new TestWebFluxTagsContributor("alpha"),
-						new TestWebFluxTagsContributor("bravo", "charlie")))
-					.httpRequestTags(exchange, null));
-		assertThat(tags).containsOnlyKeys("exception", "method", "outcome", "status", "uri", "alpha", "bravo",
-				"charlie");
-	}
-
-	private Map asMap(Iterable tags) {
-		return StreamSupport.stream(tags.spliterator(), false)
-			.collect(Collectors.toMap(Tag::getKey, Function.identity()));
-	}
-
-	private static final class TestWebFluxTagsContributor implements WebFluxTagsContributor {
-
-		private final List tagNames;
-
-		private TestWebFluxTagsContributor(String... tagNames) {
-			this.tagNames = Arrays.asList(tagNames);
-		}
-
-		@Override
-		public Iterable httpRequestTags(ServerWebExchange exchange, Throwable ex) {
-			return this.tagNames.stream().map((name) -> Tag.of(name, "value")).toList();
-		}
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java
deleted file mode 100644
index 03d7a1030258..000000000000
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsTests.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright 2012-2023 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.actuate.metrics.web.reactive.server;
-
-import java.io.EOFException;
-
-import io.micrometer.core.instrument.Tag;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.server.reactive.ServerHttpRequest;
-import org.springframework.http.server.reactive.ServerHttpResponse;
-import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
-import org.springframework.mock.web.server.MockServerWebExchange;
-import org.springframework.web.reactive.HandlerMapping;
-import org.springframework.web.server.ServerWebExchange;
-import org.springframework.web.util.pattern.PathPatternParser;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-
-/**
- * Tests for {@link WebFluxTags}.
- *
- * @author Brian Clozel
- * @author Michael McFadyen
- * @author Madhura Bhave
- * @author Stephane Nicoll
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "3.0.0", forRemoval = true)
-class WebFluxTagsTests {
-
-	private MockServerWebExchange exchange;
-
-	private final PathPatternParser parser = new PathPatternParser();
-
-	@BeforeEach
-	void setup() {
-		this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get(""));
-	}
-
-	@Test
-	void uriTagValueIsBestMatchingPatternWhenAvailable() {
-		this.exchange.getAttributes()
-			.put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse("/spring/"));
-		this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY);
-		Tag tag = WebFluxTags.uri(this.exchange);
-		assertThat(tag.getValue()).isEqualTo("/spring/");
-	}
-
-	@Test
-	void uriTagValueIsRootWhenBestMatchingPatternIsEmpty() {
-		this.exchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse(""));
-		this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY);
-		Tag tag = WebFluxTags.uri(this.exchange);
-		assertThat(tag.getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashRemoveTrailingSlash() {
-		this.exchange.getAttributes()
-			.put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse("/spring/"));
-		Tag tag = WebFluxTags.uri(this.exchange, true);
-		assertThat(tag.getValue()).isEqualTo("/spring");
-	}
-
-	@Test
-	void uriTagValueWithBestMatchingPatternAndIgnoreTrailingSlashKeepSingleSlash() {
-		this.exchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, this.parser.parse("/"));
-		Tag tag = WebFluxTags.uri(this.exchange, true);
-		assertThat(tag.getValue()).isEqualTo("/");
-	}
-
-	@Test
-	void uriTagValueIsRedirectionWhenResponseStatusIs3xx() {
-		this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY);
-		Tag tag = WebFluxTags.uri(this.exchange);
-		assertThat(tag.getValue()).isEqualTo("REDIRECTION");
-	}
-
-	@Test
-	void uriTagValueIsNotFoundWhenResponseStatusIs404() {
-		this.exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
-		Tag tag = WebFluxTags.uri(this.exchange);
-		assertThat(tag.getValue()).isEqualTo("NOT_FOUND");
-	}
-
-	@Test
-	void uriTagToleratesCustomResponseStatus() {
-		this.exchange.getResponse().setRawStatusCode(601);
-		Tag tag = WebFluxTags.uri(this.exchange);
-		assertThat(tag.getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagValueIsRootWhenRequestHasNoPatternOrPathInfo() {
-		Tag tag = WebFluxTags.uri(this.exchange);
-		assertThat(tag.getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagValueIsRootWhenRequestHasNoPatternAndSlashPathInfo() {
-		MockServerHttpRequest request = MockServerHttpRequest.get("/").build();
-		ServerWebExchange exchange = MockServerWebExchange.from(request);
-		Tag tag = WebFluxTags.uri(exchange);
-		assertThat(tag.getValue()).isEqualTo("root");
-	}
-
-	@Test
-	void uriTagValueIsUnknownWhenRequestHasNoPatternAndNonRootPathInfo() {
-		MockServerHttpRequest request = MockServerHttpRequest.get("/example").build();
-		ServerWebExchange exchange = MockServerWebExchange.from(request);
-		Tag tag = WebFluxTags.uri(exchange);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void methodTagToleratesNonStandardHttpMethods() {
-		ServerWebExchange exchange = mock(ServerWebExchange.class);
-		ServerHttpRequest request = mock(ServerHttpRequest.class);
-		given(exchange.getRequest()).willReturn(request);
-		given(request.getMethod()).willReturn(HttpMethod.valueOf("CUSTOM"));
-		Tag tag = WebFluxTags.method(exchange);
-		assertThat(tag.getValue()).isEqualTo("CUSTOM");
-	}
-
-	@Test
-	void outcomeTagIsSuccessWhenResponseStatusIsNull() {
-		this.exchange.getResponse().setStatusCode(null);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("SUCCESS");
-	}
-
-	@Test
-	void outcomeTagIsSuccessWhenResponseStatusIsAvailableFromUnderlyingServer() {
-		ServerWebExchange exchange = mock(ServerWebExchange.class);
-		ServerHttpRequest request = mock(ServerHttpRequest.class);
-		ServerHttpResponse response = mock(ServerHttpResponse.class);
-		given(response.getStatusCode()).willReturn(HttpStatus.OK);
-		given(response.getStatusCode().value()).willReturn(null);
-		given(exchange.getRequest()).willReturn(request);
-		given(exchange.getResponse()).willReturn(response);
-		Tag tag = WebFluxTags.outcome(exchange, null);
-		assertThat(tag.getValue()).isEqualTo("SUCCESS");
-	}
-
-	@Test
-	void outcomeTagIsInformationalWhenResponseIs1xx() {
-		this.exchange.getResponse().setStatusCode(HttpStatus.CONTINUE);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("INFORMATIONAL");
-	}
-
-	@Test
-	void outcomeTagIsSuccessWhenResponseIs2xx() {
-		this.exchange.getResponse().setStatusCode(HttpStatus.OK);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("SUCCESS");
-	}
-
-	@Test
-	void outcomeTagIsRedirectionWhenResponseIs3xx() {
-		this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("REDIRECTION");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIs4xx() {
-		this.exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsServerErrorWhenResponseIs5xx() {
-		this.exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("SERVER_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() {
-		this.exchange.getResponse().setRawStatusCode(490);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() {
-		this.exchange.getResponse().setRawStatusCode(701);
-		Tag tag = WebFluxTags.outcome(this.exchange, null);
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-	@Test
-	void outcomeTagIsUnknownWhenExceptionIsDisconnectedClient() {
-		Tag tag = WebFluxTags.outcome(this.exchange, new EOFException("broken pipe"));
-		assertThat(tag.getValue()).isEqualTo("UNKNOWN");
-	}
-
-}
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 0c5928ed27c3..e65ef1fa54e5 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
@@ -127,16 +127,6 @@ PropertiesFlywayConnectionDetails flywayConnectionDetails(FlywayProperties prope
 			return new PropertiesFlywayConnectionDetails(properties);
 		}
 
-		@Deprecated(since = "3.0.0", forRemoval = true)
-		public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader,
-				ObjectProvider dataSource, ObjectProvider flywayDataSource,
-				ObjectProvider fluentConfigurationCustomizers,
-				ObjectProvider javaMigrations, ObjectProvider callbacks) {
-			return flyway(properties, new PropertiesFlywayConnectionDetails(properties), resourceLoader, dataSource,
-					flywayDataSource, fluentConfigurationCustomizers, javaMigrations, callbacks,
-					new ResourceProviderCustomizer());
-		}
-
 		@Bean
 		Flyway flyway(FlywayProperties properties, FlywayConnectionDetails connectionDetails,
 				ResourceLoader resourceLoader, ObjectProvider dataSource,
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java
index 478897d053d9..c14779f3305e 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java
@@ -22,7 +22,6 @@
 import liquibase.integration.spring.SpringLiquibase;
 
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
 import org.springframework.util.Assert;
 
 /**
@@ -257,17 +256,6 @@ public void setLabelFilter(String labelFilter) {
 		this.labelFilter = labelFilter;
 	}
 
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@DeprecatedConfigurationProperty(replacement = "spring.liquibase.label-filter")
-	public String getLabels() {
-		return getLabelFilter();
-	}
-
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	public void setLabels(String labels) {
-		setLabelFilter(labels);
-	}
-
 	public Map getParameters() {
 		return this.parameters;
 	}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java
index 4d6329462831..b5e36068a8a3 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java
@@ -41,8 +41,8 @@
 import org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.context.properties.PropertyMapper;
+import org.springframework.boot.web.server.Cookie;
 import org.springframework.boot.web.server.Cookie.SameSite;
-import org.springframework.boot.web.servlet.server.Session.Cookie;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Conditional;
 import org.springframework.context.annotation.Configuration;
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java
index 681bd7d8384a..1a053cd529b0 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java
@@ -154,17 +154,6 @@ public void setServerHeader(String serverHeader) {
 		this.serverHeader = serverHeader;
 	}
 
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@DeprecatedConfigurationProperty
-	public DataSize getMaxHttpHeaderSize() {
-		return getMaxHttpRequestHeaderSize();
-	}
-
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	public void setMaxHttpHeaderSize(DataSize maxHttpHeaderSize) {
-		setMaxHttpRequestHeaderSize(maxHttpHeaderSize);
-	}
-
 	public DataSize getMaxHttpRequestHeaderSize() {
 		return this.maxHttpRequestHeaderSize;
 	}
@@ -475,7 +464,7 @@ public static class Tomcat {
 		/**
 		 * Whether to reject requests with illegal header names or values.
 		 */
-		@Deprecated(since = "2.7.12", forRemoval = true)
+		@Deprecated(since = "2.7.12", forRemoval = true) // Remove in 3.3
 		private boolean rejectIllegalHeader = true;
 
 		/**
@@ -1409,11 +1398,6 @@ public static class Netty {
 		 */
 		private DataSize initialBufferSize = DataSize.ofBytes(128);
 
-		/**
-		 * Maximum chunk size that can be decoded for an HTTP request.
-		 */
-		private DataSize maxChunkSize = DataSize.ofKilobytes(8);
-
 		/**
 		 * Maximum length that can be decoded for an HTTP request's initial line.
 		 */
@@ -1460,17 +1444,6 @@ public void setInitialBufferSize(DataSize initialBufferSize) {
 			this.initialBufferSize = initialBufferSize;
 		}
 
-		@Deprecated(since = "3.0.0", forRemoval = true)
-		@DeprecatedConfigurationProperty(reason = "Deprecated for removal in Reactor Netty")
-		public DataSize getMaxChunkSize() {
-			return this.maxChunkSize;
-		}
-
-		@Deprecated(since = "3.0.0", forRemoval = true)
-		public void setMaxChunkSize(DataSize maxChunkSize) {
-			this.maxChunkSize = maxChunkSize;
-		}
-
 		public DataSize getMaxInitialLineLength() {
 			return this.maxInitialLineLength;
 		}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java
index c1d6ba684b8d..acfade5b98d6 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizer.java
@@ -19,7 +19,6 @@
 import java.time.Duration;
 
 import io.netty.channel.ChannelOption;
-import reactor.netty.http.server.HttpRequestDecoderSpec;
 
 import org.springframework.boot.autoconfigure.web.ServerProperties;
 import org.springframework.boot.cloud.CloudPlatform;
@@ -87,7 +86,6 @@ private void customizeRequestDecoder(NettyReactiveWebServerFactory factory, Prop
 				.to((maxHttpRequestHeader) -> httpRequestDecoderSpec
 					.maxHeaderSize((int) maxHttpRequestHeader.toBytes()));
 			ServerProperties.Netty nettyProperties = this.serverProperties.getNetty();
-			maxChunkSize(propertyMapper, httpRequestDecoderSpec, nettyProperties);
 			propertyMapper.from(nettyProperties.getMaxInitialLineLength())
 				.whenNonNull()
 				.to((maxInitialLineLength) -> httpRequestDecoderSpec
@@ -106,14 +104,6 @@ private void customizeRequestDecoder(NettyReactiveWebServerFactory factory, Prop
 		}));
 	}
 
-	@SuppressWarnings({ "deprecation", "removal" })
-	private void maxChunkSize(PropertyMapper propertyMapper, HttpRequestDecoderSpec httpRequestDecoderSpec,
-			ServerProperties.Netty nettyProperties) {
-		propertyMapper.from(nettyProperties.getMaxChunkSize())
-			.whenNonNull()
-			.to((maxChunkSize) -> httpRequestDecoderSpec.maxChunkSize((int) maxChunkSize.toBytes()));
-	}
-
 	private void customizeIdleTimeout(NettyReactiveWebServerFactory factory, Duration idleTimeout) {
 		factory.addServerCustomizers((httpServer) -> httpServer.idleTimeout(idleTimeout));
 	}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java
index e92256d6004a..f3e8097248fc 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java
@@ -31,7 +31,6 @@
 import org.springframework.beans.factory.ListableBeanFactory;
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfigureOrder;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -403,24 +402,6 @@ public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties w
 			this.beanFactory = beanFactory;
 		}
 
-		@Bean
-		@Override
-		public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
-				@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
-				@Qualifier("mvcConversionService") FormattingConversionService conversionService,
-				@Qualifier("mvcValidator") Validator validator) {
-			RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
-					conversionService, validator);
-			setIgnoreDefaultModelOnRedirect(adapter);
-			return adapter;
-		}
-
-		@SuppressWarnings({ "deprecation", "removal" })
-		private void setIgnoreDefaultModelOnRedirect(RequestMappingHandlerAdapter adapter) {
-			adapter.setIgnoreDefaultModelOnRedirect(
-					this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
-		}
-
 		@Override
 		protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
 			if (this.mvcRegistrations != null) {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java
index 6037fa974a84..0c73524c8f94 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java
@@ -21,7 +21,6 @@
 import java.util.Map;
 
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
 import org.springframework.http.MediaType;
 import org.springframework.util.Assert;
 import org.springframework.validation.DefaultMessageCodesResolver;
@@ -57,12 +56,6 @@ public class WebMvcProperties {
 	 */
 	private boolean dispatchOptionsRequest = true;
 
-	/**
-	 * Whether the content of the "default" model should be ignored during redirect
-	 * scenarios.
-	 */
-	private boolean ignoreDefaultModelOnRedirect = true;
-
 	/**
 	 * Whether to publish a ServletRequestHandledEvent at the end of each request.
 	 */
@@ -120,17 +113,6 @@ public Format getFormat() {
 		return this.format;
 	}
 
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@DeprecatedConfigurationProperty(reason = "Deprecated for removal in Spring MVC")
-	public boolean isIgnoreDefaultModelOnRedirect() {
-		return this.ignoreDefaultModelOnRedirect;
-	}
-
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
-		this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
-	}
-
 	public boolean isPublishRequestHandledEvents() {
 		return this.publishRequestHandledEvents;
 	}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index dae3a6e6cdf0..be821293afaa 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -115,6 +115,13 @@
         "level": "error"
       }
     },
+    {
+      "name": "server.max-http-header-size",
+      "deprecation": {
+        "replacement": "server.max-http-request-header-size",
+        "level": "error"
+      }
+    },
     {
       "name": "server.max-http-post-size",
       "type": "java.lang.Integer",
@@ -125,6 +132,13 @@
         "level": "error"
       }
     },
+    {
+      "name": "server.netty.max-chunk-size",
+      "deprecation": {
+        "reason": "Deprecated for removal in Reactor Netty.",
+        "level": "error"
+      }
+    },
     {
       "name": "server.port",
       "defaultValue": 8080
@@ -210,7 +224,10 @@
     },
     {
       "name": "server.servlet.session.cookie.comment",
-      "description": "Comment for the cookie."
+      "description": "Comment for the cookie.",
+      "deprecation": {
+        "level": "error"
+      }
     },
     {
       "name": "server.servlet.session.cookie.domain",
@@ -2043,6 +2060,13 @@
         "level": "error"
       }
     },
+    {
+      "name": "spring.liquibase.labels",
+      "deprecation": {
+        "replacement": "spring.liquibase.label-filter",
+        "level": "error"
+      }
+    },
     {
       "name": "spring.mail.test-connection",
       "description": "Whether to test that the mail server is available on startup.",
@@ -2105,6 +2129,13 @@
       "description": "Whether to enable Spring's HiddenHttpMethodFilter.",
       "defaultValue": false
     },
+    {
+      "name": "spring.mvc.ignore-default-model-on-redirect",
+      "deprecation": {
+        "reason": "Deprecated for removal in Spring MVC.",
+        "level": "error"
+      }
+    },
     {
       "name": "spring.mvc.locale",
       "type": "java.util.Locale",
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java
index b05e9b0f010d..3c66a6009707 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java
@@ -379,14 +379,6 @@ void overrideLabelFilter() {
 			.run(assertLiquibase((liquibase) -> assertThat(liquibase.getLabelFilter()).isEqualTo("test, production")));
 	}
 
-	@Test
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	void overrideLabelFilterWithDeprecatedLabelsProperty() {
-		this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
-			.withPropertyValues("spring.liquibase.labels:test, production")
-			.run(assertLiquibase((liquibase) -> assertThat(liquibase.getLabelFilter()).isEqualTo("test, production")));
-	}
-
 	@Test
 	@SuppressWarnings("unchecked")
 	void testOverrideParameters() {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java
index eaf0b45c2b0a..379aa6e32ce4 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java
@@ -205,24 +205,6 @@ void testCustomizeUriEncoding() {
 		assertThat(this.properties.getTomcat().getUriEncoding()).isEqualTo(StandardCharsets.US_ASCII);
 	}
 
-	@Test
-	@SuppressWarnings("removal")
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	void testCustomizeHeaderSize() {
-		bind("server.max-http-header-size", "1MB");
-		assertThat(this.properties.getMaxHttpHeaderSize()).isEqualTo(DataSize.ofMegabytes(1));
-		assertThat(this.properties.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofMegabytes(1));
-	}
-
-	@Test
-	@SuppressWarnings("removal")
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	void testCustomizeHeaderSizeUseBytesByDefault() {
-		bind("server.max-http-header-size", "1024");
-		assertThat(this.properties.getMaxHttpHeaderSize()).isEqualTo(DataSize.ofKilobytes(1));
-		assertThat(this.properties.getMaxHttpRequestHeaderSize()).isEqualTo(DataSize.ofKilobytes(1));
-	}
-
 	@Test
 	void testCustomizeMaxHttpRequestHeaderSize() {
 		bind("server.max-http-request-header-size", "1MB");
@@ -538,14 +520,6 @@ void undertowMaxHttpPostSizeMatchesDefault() {
 			.isEqualTo(UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
 	}
 
-	@Test
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@SuppressWarnings("removal")
-	void nettyMaxChunkSizeMatchesHttpDecoderSpecDefault() {
-		assertThat(this.properties.getNetty().getMaxChunkSize().toBytes())
-			.isEqualTo(HttpDecoderSpec.DEFAULT_MAX_CHUNK_SIZE);
-	}
-
 	@Test
 	void nettyMaxInitialLineLengthMatchesHttpDecoderSpecDefault() {
 		assertThat(this.properties.getNetty().getMaxInitialLineLength().toBytes())
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java
index c024fc15c6cf..a4367e150642 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java
@@ -263,30 +263,6 @@ void setUseForwardHeaders() {
 		then(factory).should().setUseForwardHeaders(true);
 	}
 
-	@Test
-	void customizeMaxHttpHeaderSize() {
-		bind("server.max-http-header-size=2048");
-		JettyWebServer server = customizeAndGetServer();
-		List requestHeaderSizes = getRequestHeaderSizes(server);
-		assertThat(requestHeaderSizes).containsOnly(2048);
-	}
-
-	@Test
-	void customMaxHttpHeaderSizeIgnoredIfNegative() {
-		bind("server.max-http-header-size=-1");
-		JettyWebServer server = customizeAndGetServer();
-		List requestHeaderSizes = getRequestHeaderSizes(server);
-		assertThat(requestHeaderSizes).containsOnly(8192);
-	}
-
-	@Test
-	void customMaxHttpHeaderSizeIgnoredIfZero() {
-		bind("server.max-http-header-size=0");
-		JettyWebServer server = customizeAndGetServer();
-		List requestHeaderSizes = getRequestHeaderSizes(server);
-		assertThat(requestHeaderSizes).containsOnly(8192);
-	}
-
 	@Test
 	void customizeMaxRequestHttpHeaderSize() {
 		bind("server.max-http-request-header-size=2048");
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java
index e2267a005a74..51945e666a5a 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/NettyWebServerFactoryCustomizerTests.java
@@ -132,7 +132,6 @@ void configureHttpRequestDecoder() {
 		nettyProperties.setValidateHeaders(false);
 		nettyProperties.setInitialBufferSize(DataSize.ofBytes(512));
 		nettyProperties.setH2cMaxContentLength(DataSize.ofKilobytes(1));
-		setMaxChunkSize(nettyProperties);
 		nettyProperties.setMaxInitialLineLength(DataSize.ofKilobytes(32));
 		NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
 		this.customizer.customize(factory);
@@ -143,20 +142,9 @@ void configureHttpRequestDecoder() {
 		assertThat(decoder.validateHeaders()).isFalse();
 		assertThat(decoder.initialBufferSize()).isEqualTo(nettyProperties.getInitialBufferSize().toBytes());
 		assertThat(decoder.h2cMaxContentLength()).isEqualTo(nettyProperties.getH2cMaxContentLength().toBytes());
-		assertMaxChunkSize(nettyProperties, decoder);
 		assertThat(decoder.maxInitialLineLength()).isEqualTo(nettyProperties.getMaxInitialLineLength().toBytes());
 	}
 
-	@SuppressWarnings("removal")
-	private void setMaxChunkSize(ServerProperties.Netty nettyProperties) {
-		nettyProperties.setMaxChunkSize(DataSize.ofKilobytes(16));
-	}
-
-	@SuppressWarnings({ "deprecation", "removal" })
-	private void assertMaxChunkSize(ServerProperties.Netty nettyProperties, HttpRequestDecoderSpec decoder) {
-		assertThat(decoder.maxChunkSize()).isEqualTo(nettyProperties.getMaxChunkSize().toBytes());
-	}
-
 	private void verifyConnectionTimeout(NettyReactiveWebServerFactory factory, Integer expected) {
 		if (expected == null) {
 			then(factory).should(never()).addServerCustomizers(any(NettyServerCustomizer.class));
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java
index c24b4f179cde..f885699393ad 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java
@@ -176,47 +176,6 @@ void customMaxHttpFormPostSize() {
 				(server) -> assertThat(server.getTomcat().getConnector().getMaxPostSize()).isEqualTo(10000));
 	}
 
-	@Test
-	void customMaxHttpHeaderSize() {
-		bind("server.max-http-header-size=1KB");
-		customizeAndRunServer((server) -> assertThat(
-				((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler())
-					.getMaxHttpRequestHeaderSize())
-			.isEqualTo(DataSize.ofKilobytes(1).toBytes()));
-	}
-
-	@Test
-	void customMaxHttpHeaderSizeWithHttp2() {
-		bind("server.max-http-header-size=1KB", "server.http2.enabled=true");
-		customizeAndRunServer((server) -> {
-			AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) server.getTomcat()
-				.getConnector()
-				.getProtocolHandler();
-			long expectedSize = DataSize.ofKilobytes(1).toBytes();
-			assertThat(protocolHandler.getMaxHttpRequestHeaderSize()).isEqualTo(expectedSize);
-			assertThat(((Http2Protocol) protocolHandler.getUpgradeProtocol("h2c")).getMaxHeaderSize())
-				.isEqualTo(expectedSize);
-		});
-	}
-
-	@Test
-	void customMaxHttpHeaderSizeIgnoredIfNegative() {
-		bind("server.max-http-header-size=-1");
-		customizeAndRunServer((server) -> assertThat(
-				((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler())
-					.getMaxHttpRequestHeaderSize())
-			.isEqualTo(DataSize.ofKilobytes(8).toBytes()));
-	}
-
-	@Test
-	void customMaxHttpHeaderSizeIgnoredIfZero() {
-		bind("server.max-http-header-size=0");
-		customizeAndRunServer((server) -> assertThat(
-				((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler())
-					.getMaxHttpRequestHeaderSize())
-			.isEqualTo(DataSize.ofKilobytes(8).toBytes()));
-	}
-
 	@Test
 	void defaultMaxHttpRequestHeaderSize() {
 		customizeAndRunServer((server) -> assertThat(
@@ -436,16 +395,6 @@ void disableRemoteIpValve() {
 		assertThat(factory.getEngineValves()).isEmpty();
 	}
 
-	@Test
-	@Deprecated(since = "2.7.12", forRemoval = true)
-	void testCustomizeRejectIllegalHeader() {
-		bind("server.tomcat.reject-illegal-header=false");
-		customizeAndRunServer((server) -> assertThat(
-				((AbstractHttp11Protocol) server.getTomcat().getConnector().getProtocolHandler())
-					.getRejectIllegalHeader())
-			.isFalse());
-	}
-
 	@Test
 	void errorReportValveIsConfiguredToNotReportStackTraces() {
 		TomcatWebServer server = customizeAndGetServer();
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java
index 15289d60e41b..e5a2c95e8271 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java
@@ -86,20 +86,20 @@ void customizeUndertowAccessLog() {
 	}
 
 	@Test
-	void customMaxHttpHeaderSize() {
-		bind("server.max-http-header-size=2048");
+	void customMaxHttpRequestHeaderSize() {
+		bind("server.max-http-request-header-size=2048");
 		assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isEqualTo(2048);
 	}
 
 	@Test
-	void customMaxHttpHeaderSizeIgnoredIfNegative() {
-		bind("server.max-http-header-size=-1");
+	void customMaxHttpRequestHeaderSizeIgnoredIfNegative() {
+		bind("server.max-http-request-header-size=-1");
 		assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull();
 	}
 
 	@Test
-	void customMaxHttpHeaderSizeIgnoredIfZero() {
-		bind("server.max-http-header-size=0");
+	void customMaxHttpRequestHeaderSizeIgnoredIfZero() {
+		bind("server.max-http-request-header-size=0");
 		assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull();
 	}
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java
index 7cff13798441..c428ff17819f 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizerTests.java
@@ -28,11 +28,11 @@
 import org.springframework.boot.context.properties.bind.Binder;
 import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
 import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
+import org.springframework.boot.web.server.Cookie;
 import org.springframework.boot.web.server.Shutdown;
 import org.springframework.boot.web.server.Ssl;
 import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
 import org.springframework.boot.web.servlet.server.Jsp;
-import org.springframework.boot.web.servlet.server.Session.Cookie;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
@@ -97,7 +97,6 @@ void testCustomizeJsp() {
 	}
 
 	@Test
-	@SuppressWarnings("removal")
 	void customizeSessionProperties() {
 		Map map = new HashMap<>();
 		map.put("server.servlet.session.timeout", "123");
@@ -105,7 +104,6 @@ void customizeSessionProperties() {
 		map.put("server.servlet.session.cookie.name", "testname");
 		map.put("server.servlet.session.cookie.domain", "testdomain");
 		map.put("server.servlet.session.cookie.path", "/testpath");
-		map.put("server.servlet.session.cookie.comment", "testcomment");
 		map.put("server.servlet.session.cookie.http-only", "true");
 		map.put("server.servlet.session.cookie.secure", "true");
 		map.put("server.servlet.session.cookie.max-age", "60");
@@ -118,7 +116,6 @@ void customizeSessionProperties() {
 			assertThat(cookie.getName()).isEqualTo("testname");
 			assertThat(cookie.getDomain()).isEqualTo("testdomain");
 			assertThat(cookie.getPath()).isEqualTo("/testpath");
-			assertThat(cookie.getComment()).isEqualTo("testcomment");
 			assertThat(cookie.getHttpOnly()).isTrue();
 			assertThat(cookie.getMaxAge()).hasSeconds(60);
 		}));
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java
index 4b8737d0424c..c2ebbb6d1588 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java
@@ -380,29 +380,6 @@ void customLocaleResolverWithDifferentNameDoesNotReplaceAutoConfiguredLocaleReso
 			});
 	}
 
-	@Test
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@SuppressWarnings("deprecation")
-	void customThemeResolverWithMatchingNameReplacesDefaultThemeResolver() {
-		this.contextRunner.withBean("themeResolver", CustomThemeResolver.class, CustomThemeResolver::new)
-			.run((context) -> {
-				assertThat(context).hasSingleBean(org.springframework.web.servlet.ThemeResolver.class);
-				assertThat(context.getBean("themeResolver")).isInstanceOf(CustomThemeResolver.class);
-			});
-	}
-
-	@Test
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@SuppressWarnings("deprecation")
-	void customThemeResolverWithDifferentNameDoesNotReplaceDefaultThemeResolver() {
-		this.contextRunner.withBean("customThemeResolver", CustomThemeResolver.class, CustomThemeResolver::new)
-			.run((context) -> {
-				assertThat(context.getBean("customThemeResolver")).isInstanceOf(CustomThemeResolver.class);
-				assertThat(context.getBean("themeResolver"))
-					.isInstanceOf(org.springframework.web.servlet.theme.FixedThemeResolver.class);
-			});
-	}
-
 	@Test
 	void customFlashMapManagerWithMatchingNameReplacesDefaultFlashMapManager() {
 		this.contextRunner.withBean("flashMapManager", CustomFlashMapManager.class, CustomFlashMapManager::new)
@@ -493,21 +470,6 @@ void overrideMessageCodesFormat() {
 				.isNotNull());
 	}
 
-	@Test
-	void ignoreDefaultModelOnRedirectIsTrue() {
-		this.contextRunner.run((context) -> assertThat(context.getBean(RequestMappingHandlerAdapter.class))
-			.extracting("ignoreDefaultModelOnRedirect")
-			.isEqualTo(true));
-	}
-
-	@Test
-	void overrideIgnoreDefaultModelOnRedirect() {
-		this.contextRunner.withPropertyValues("spring.mvc.ignore-default-model-on-redirect:false")
-			.run((context) -> assertThat(context.getBean(RequestMappingHandlerAdapter.class))
-				.extracting("ignoreDefaultModelOnRedirect")
-				.isEqualTo(false));
-	}
-
 	@Test
 	void customViewResolver() {
 		this.contextRunner.withUserConfiguration(CustomViewResolver.class)
@@ -1464,20 +1426,6 @@ public void setLocale(HttpServletRequest request, HttpServletResponse response,
 
 	}
 
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	static class CustomThemeResolver implements org.springframework.web.servlet.ThemeResolver {
-
-		@Override
-		public String resolveThemeName(HttpServletRequest request) {
-			return "custom";
-		}
-
-		@Override
-		public void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName) {
-		}
-
-	}
-
 	static class CustomFlashMapManager extends AbstractFlashMapManager {
 
 		@Override
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/SpringBootDependencyInjectionTestExecutionListener.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/SpringBootDependencyInjectionTestExecutionListener.java
deleted file mode 100644
index e3fbb4d61b96..000000000000
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/SpringBootDependencyInjectionTestExecutionListener.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2012-2023 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.test.autoconfigure;
-
-import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
-import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.test.context.ApplicationContextFailureProcessor;
-import org.springframework.test.context.TestContext;
-import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
-
-/**
- * Since 3.0.0 this class has been replaced by
- * {@link ConditionReportApplicationContextFailureProcessor} and is not used internally.
- *
- * @author Phillip Webb
- * @since 1.4.1
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link ApplicationContextFailureProcessor}
- */
-@Deprecated(since = "3.0.0", forRemoval = true)
-public class SpringBootDependencyInjectionTestExecutionListener extends DependencyInjectionTestExecutionListener {
-
-	@Override
-	public void prepareTestInstance(TestContext testContext) throws Exception {
-		try {
-			super.prepareTestInstance(testContext);
-		}
-		catch (Exception ex) {
-			outputConditionEvaluationReport(testContext);
-			throw ex;
-		}
-	}
-
-	private void outputConditionEvaluationReport(TestContext testContext) {
-		try {
-			ApplicationContext context = testContext.getApplicationContext();
-			if (context instanceof ConfigurableApplicationContext configurableContext) {
-				ConditionEvaluationReport report = ConditionEvaluationReport.get(configurableContext.getBeanFactory());
-				System.err.println(new ConditionEvaluationReportMessage(report));
-			}
-		}
-		catch (Exception ex) {
-			// Allow original failure to be reported
-		}
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetrics.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetrics.java
deleted file mode 100644
index 44ec896236d4..000000000000
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetrics.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.test.autoconfigure.actuate.metrics;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability;
-
-/**
- * Annotation that can be applied to a test class to enable auto-configuration for metrics
- * exporters.
- *
- * @author Chris Bono
- * @since 2.4.0
- * @deprecated since 3.0.0 for removal in 3.2.0 in favor of
- * {@link AutoConfigureObservability @AutoConfigureObservability}
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Inherited
-@Deprecated(since = "3.0.0", forRemoval = true)
-@AutoConfigureObservability(tracing = false)
-public @interface AutoConfigureMetrics {
-
-}
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/package-info.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/package-info.java
deleted file mode 100644
index 6dfcc180e669..000000000000
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2012-2020 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.
- */
-
-/**
- * Auto-configuration for handling metrics in tests.
- */
-package org.springframework.boot.test.autoconfigure.actuate.metrics;
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsMissingIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsMissingIntegrationTests.java
deleted file mode 100644
index ff4e924a993a..000000000000
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsMissingIntegrationTests.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.test.autoconfigure.actuate.metrics;
-
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
-import io.micrometer.prometheus.PrometheusMeterRegistry;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.env.Environment;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Integration test to verify behaviour when
- * {@link AutoConfigureMetrics @AutoConfigureMetrics} is not present on the test class.
- *
- * @author Chris Bono
- */
-@SpringBootTest
-class AutoConfigureMetricsMissingIntegrationTests {
-
-	@Test
-	void customizerRunsAndOnlyEnablesSimpleMeterRegistryWhenNoAnnotationPresent(
-			@Autowired ApplicationContext applicationContext) {
-		assertThat(applicationContext.getBean(MeterRegistry.class)).isInstanceOf(SimpleMeterRegistry.class);
-		assertThat(applicationContext.getBeansOfType(PrometheusMeterRegistry.class)).isEmpty();
-	}
-
-	@Test
-	void customizerRunsAndSetsExclusionPropertiesWhenNoAnnotationPresent(@Autowired Environment environment) {
-		assertThat(environment.getProperty("management.defaults.metrics.export.enabled")).isEqualTo("false");
-		assertThat(environment.getProperty("management.simple.metrics.export.enabled")).isEqualTo("true");
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsPresentIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsPresentIntegrationTests.java
deleted file mode 100644
index dfdef02bdb2c..000000000000
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsPresentIntegrationTests.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.test.autoconfigure.actuate.metrics;
-
-import io.micrometer.prometheus.PrometheusMeterRegistry;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.env.Environment;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Integration test to verify behaviour when
- * {@link AutoConfigureMetrics @AutoConfigureMetrics} is present on the test class.
- *
- * @author Chris Bono
- */
-@SuppressWarnings("removal")
-@SpringBootTest
-@AutoConfigureMetrics
-@Deprecated(since = "3.0.0", forRemoval = true)
-class AutoConfigureMetricsPresentIntegrationTests {
-
-	@Test
-	void customizerDoesNotDisableAvailableMeterRegistriesWhenAnnotationPresent(
-			@Autowired ApplicationContext applicationContext) {
-		assertThat(applicationContext.getBeansOfType(PrometheusMeterRegistry.class)).hasSize(1);
-	}
-
-	@Test
-	void customizerDoesNotSetExclusionPropertiesWhenAnnotationPresent(@Autowired Environment environment) {
-		assertThat(environment.containsProperty("management.defaults.metrics.export.enabled")).isFalse();
-		assertThat(environment.containsProperty("management.simple.metrics.export.enabled")).isFalse();
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsSpringBootApplication.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsSpringBootApplication.java
deleted file mode 100644
index 31c699a44b8d..000000000000
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/actuate/metrics/AutoConfigureMetricsSpringBootApplication.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.test.autoconfigure.actuate.metrics;
-
-import org.springframework.boot.SpringBootConfiguration;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
-import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
-import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration;
-
-/**
- * Example {@link SpringBootApplication @SpringBootApplication} for use with
- * {@link AutoConfigureMetrics @AutoConfigureMetrics} tests.
- *
- * @author Chris Bono
- */
-@SpringBootConfiguration
-@EnableAutoConfiguration(exclude = { CassandraAutoConfiguration.class, MongoAutoConfiguration.class,
-		MongoReactiveAutoConfiguration.class })
-class AutoConfigureMetricsSpringBootApplication {
-
-}
diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/DefaultTestExecutionListenersPostProcessor.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/DefaultTestExecutionListenersPostProcessor.java
deleted file mode 100644
index 695c3f460f6b..000000000000
--- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/DefaultTestExecutionListenersPostProcessor.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.test.context;
-
-import java.util.List;
-
-import org.springframework.test.context.ApplicationContextFailureProcessor;
-import org.springframework.test.context.TestExecutionListener;
-
-/**
- * Callback interface trigger from {@link SpringBootTestContextBootstrapper} that can be
- * used to post-process the list of default {@link TestExecutionListener
- * TestExecutionListeners} to be used by a test. Can be used to add or remove existing
- * listeners.
- *
- * @author Phillip Webb
- * @since 1.4.1
- * @deprecated since 3.0.0 removal in 3.2.0 in favor of
- * {@link ApplicationContextFailureProcessor}
- */
-@FunctionalInterface
-@Deprecated(since = "3.0.0", forRemoval = true)
-public interface DefaultTestExecutionListenersPostProcessor {
-
-	/**
-	 * Post process the list of default {@link TestExecutionListener listeners} to be
-	 * used.
-	 * @param listeners the source listeners
-	 * @return the actual listeners that should be used
-	 * @since 3.0.0
-	 */
-	List postProcessDefaultTestExecutionListeners(List listeners);
-
-}
diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
index 0f812a30ebba..ce07386b1a76 100644
--- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
+++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
@@ -38,7 +38,6 @@
 import org.springframework.core.annotation.MergedAnnotations;
 import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
 import org.springframework.core.env.Environment;
-import org.springframework.core.io.support.SpringFactoriesLoader;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.ContextConfigurationAttributes;
 import org.springframework.test.context.ContextCustomizer;
@@ -48,7 +47,6 @@
 import org.springframework.test.context.TestContext;
 import org.springframework.test.context.TestContextAnnotationUtils;
 import org.springframework.test.context.TestContextBootstrapper;
-import org.springframework.test.context.TestExecutionListener;
 import org.springframework.test.context.aot.AotTestAttributes;
 import org.springframework.test.context.support.DefaultTestContextBootstrapper;
 import org.springframework.test.context.support.TestPropertySourceUtils;
@@ -122,18 +120,6 @@ else if (webEnvironment != null && webEnvironment.isEmbedded()) {
 		return context;
 	}
 
-	@Override
-	@SuppressWarnings("removal")
-	protected List getDefaultTestExecutionListeners() {
-		List listeners = new ArrayList<>(super.getDefaultTestExecutionListeners());
-		List postProcessors = SpringFactoriesLoader
-			.loadFactories(DefaultTestExecutionListenersPostProcessor.class, getClass().getClassLoader());
-		for (DefaultTestExecutionListenersPostProcessor postProcessor : postProcessors) {
-			listeners = postProcessor.postProcessDefaultTestExecutionListeners(listeners);
-		}
-		return listeners;
-	}
-
 	@Override
 	protected ContextLoader resolveContextLoader(Class testClass,
 			List configAttributesList) {
diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java
index 38edac15537b..fb275a756e0c 100644
--- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java
+++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java
@@ -45,8 +45,6 @@ class SpringBootTestContextBootstrapperIntegrationTests {
 	@Autowired
 	private SpringBootTestContextBootstrapperExampleConfig config;
 
-	boolean defaultTestExecutionListenersPostProcessorCalled = false;
-
 	@Test
 	void findConfigAutomatically() {
 		assertThat(this.config).isNotNull();
@@ -62,11 +60,6 @@ void testConfigurationWasApplied() {
 		assertThat(this.context.getBean(ExampleBean.class)).isNotNull();
 	}
 
-	@Test
-	void defaultTestExecutionListenersPostProcessorShouldBeCalled() {
-		assertThat(this.defaultTestExecutionListenersPostProcessorCalled).isTrue();
-	}
-
 	@TestConfiguration(proxyBeanMethods = false)
 	static class TestConfig {
 
diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/TestDefaultTestExecutionListenersPostProcessor.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/TestDefaultTestExecutionListenersPostProcessor.java
deleted file mode 100644
index 7587c23f93f5..000000000000
--- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/TestDefaultTestExecutionListenersPostProcessor.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.test.context.bootstrap;
-
-import java.util.List;
-
-import org.springframework.boot.test.context.DefaultTestExecutionListenersPostProcessor;
-import org.springframework.test.context.TestContext;
-import org.springframework.test.context.TestExecutionListener;
-import org.springframework.test.context.support.AbstractTestExecutionListener;
-
-/**
- * Test {@link DefaultTestExecutionListenersPostProcessor}.
- *
- * @author Phillip Webb
- */
-@SuppressWarnings("removal")
-public class TestDefaultTestExecutionListenersPostProcessor implements DefaultTestExecutionListenersPostProcessor {
-
-	@Override
-	public List postProcessDefaultTestExecutionListeners(List listeners) {
-		listeners.add(new ExampleTestExecutionListener());
-		return listeners;
-	}
-
-	static class ExampleTestExecutionListener extends AbstractTestExecutionListener {
-
-		@Override
-		public void prepareTestInstance(TestContext testContext) throws Exception {
-			Object testInstance = testContext.getTestInstance();
-			if (testInstance instanceof SpringBootTestContextBootstrapperIntegrationTests test) {
-				test.defaultTestExecutionListenersPostProcessorCalled = true;
-			}
-		}
-
-	}
-
-}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java
index 9f85011de406..4f45a734cb1c 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java
@@ -146,17 +146,6 @@ void propertiesWithMultiConstructor() {
 			.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
 	}
 
-	@Test
-	@Deprecated(since = "3.0.0", forRemoval = true)
-	@SuppressWarnings("removal")
-	void propertiesWithMultiConstructorAndDeprecatedAnnotation() {
-		process(org.springframework.boot.configurationsample.immutable.DeprecatedImmutableMultiConstructorProperties.class,
-				propertyNames((stream) -> assertThat(stream).containsExactly("name", "description")));
-		process(org.springframework.boot.configurationsample.immutable.DeprecatedImmutableMultiConstructorProperties.class,
-				properties((stream) -> assertThat(stream)
-					.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
-	}
-
 	@Test
 	void propertiesWithMultiConstructorNoDirective() {
 		process(TwoConstructorsExample.class, propertyNames((stream) -> assertThat(stream).containsExactly("name")));
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java
deleted file mode 100644
index ee1ae440f81c..000000000000
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2012-2023 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.configurationsample;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Alternative to Spring Boot's deprecated
- * {@code @org.springframework.boot.context.properties.ConstructorBinding} for testing
- * (removes the need for a dependency on the real annotation).
- *
- * @author Stephane Nicoll
- */
-@Target(ElementType.CONSTRUCTOR)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@ConstructorBinding
-@Deprecated(since = "3.0.0", forRemoval = true)
-public @interface DeprecatedConstructorBinding {
-
-}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java
deleted file mode 100644
index d2e0305fc149..000000000000
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.configurationsample.immutable;
-
-/**
- * Simple immutable properties with several constructors.
- *
- * @author Stephane Nicoll
- */
-@SuppressWarnings("unused")
-@Deprecated(since = "3.0.0", forRemoval = true)
-public class DeprecatedImmutableMultiConstructorProperties {
-
-	private final String name;
-
-	/**
-	 * Test description.
-	 */
-	private final String description;
-
-	public DeprecatedImmutableMultiConstructorProperties(String name) {
-		this(name, null);
-	}
-
-	@SuppressWarnings("removal")
-	@org.springframework.boot.configurationsample.DeprecatedConstructorBinding
-	public DeprecatedImmutableMultiConstructorProperties(String name, String description) {
-		this.name = name;
-		this.description = description;
-	}
-
-}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java
index 86510c2b53a9..7c8c41e05ad3 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2021 the original author or authors.
+ * Copyright 2012-2023 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.
@@ -22,6 +22,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+import org.springframework.boot.context.properties.bind.ConstructorBinding;
 import org.springframework.core.annotation.AliasFor;
 import org.springframework.stereotype.Indexed;
 
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java
deleted file mode 100644
index 49c95249ee97..000000000000
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2012-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.context.properties;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation that can be used to indicate which constructor to use when binding
- * configuration properties using constructor arguments rather than by calling setters. A
- * single parameterized constructor implicitly indicates that constructor binding should
- * be used unless the constructor is annotated with `@Autowired`.
- * 

- * Note: To use constructor binding the class must be enabled using - * {@link EnableConfigurationProperties @EnableConfigurationProperties} or configuration - * property scanning. Constructor binding cannot be used with beans that are created by - * the regular Spring mechanisms (e.g. - * {@link org.springframework.stereotype.Component @Component} beans, beans created via - * {@link org.springframework.context.annotation.Bean @Bean} methods or beans loaded using - * {@link org.springframework.context.annotation.Import @Import}). - * - * @author Phillip Webb - * @since 2.2.0 - * @see ConfigurationProperties - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link org.springframework.boot.context.properties.bind.ConstructorBinding} - */ -@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Deprecated(since = "3.0.0", forRemoval = true) -@org.springframework.boot.context.properties.bind.ConstructorBinding -public @interface ConstructorBinding { - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBound.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBound.java index 4b1a4fea9e47..bd5fe6d92bd4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBound.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBound.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 2019-2023 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. @@ -17,6 +17,7 @@ package org.springframework.boot.context.properties; import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.context.properties.bind.ConstructorBinding; /** * Helper class to programmatically bind configuration properties that use constructor diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java index 09ed12dd40b7..9364c73bf70a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java @@ -16,14 +16,9 @@ package org.springframework.boot.jackson; -import java.util.Collection; - import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.module.SimpleModule; -import org.springframework.context.ApplicationContext; -import org.springframework.util.Assert; - /** * Spring Bean and Jackson {@link Module} to find and * {@link SimpleModule#setMixInAnnotation(Class, Class) register} @@ -36,22 +31,6 @@ */ public class JsonMixinModule extends SimpleModule { - public JsonMixinModule() { - } - - /** - * Create a new {@link JsonMixinModule} instance. - * @param context the source application context - * @param basePackages the packages to check for annotated classes - * @deprecated since 3.0.0 in favor of - * {@link #registerEntries(JsonMixinModuleEntries, ClassLoader)} - */ - @Deprecated(since = "3.0.0", forRemoval = true) - public JsonMixinModule(ApplicationContext context, Collection basePackages) { - Assert.notNull(context, "Context must not be null"); - registerEntries(JsonMixinModuleEntries.scan(context, basePackages), context.getClassLoader()); - } - /** * Register the specified {@link JsonMixinModuleEntries entries}. * @param entries the entries to register to this instance diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySupplier.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySupplier.java deleted file mode 100644 index 7fce4e31cf0f..000000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactorySupplier.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.web.client; - -import java.util.function.Supplier; - -import org.springframework.http.client.ClientHttpRequestFactory; - -/** - * A supplier for {@link ClientHttpRequestFactory} that detects the preferred candidate - * based on the available implementations on the classpath. - * - * @author Stephane Nicoll - * @author Moritz Halbritter - * @since 2.1.0 - * @deprecated since 3.0.0 for removal in 3.2.0 in favor of - * {@link ClientHttpRequestFactories} - */ -@Deprecated(since = "3.0.0", forRemoval = true) -public class ClientHttpRequestFactorySupplier implements Supplier { - - @Override - public ClientHttpRequestFactory get() { - return ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS); - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java index 93e4e1b91840..7f61703632b4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java @@ -325,8 +325,9 @@ public RestTemplateBuilder requestFactory(Supplier req } /** - * Set the {@link ClientHttpRequestFactorySupplier} that should be called each time we - * {@link #build()} a new {@link RestTemplate} instance. + * Set the request factory function that should be called to provide a + * {@link ClientHttpRequestFactory} each time we {@link #build()} a new + * {@link RestTemplate} instance. * @param requestFactoryFunction the settings to request factory function * @return a new builder instance * @since 3.0.0 diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactory.java index 349212d7e6c0..04f622135008 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactory.java @@ -39,6 +39,7 @@ import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory; +import org.springframework.boot.web.server.Cookie; import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.util.Assert; @@ -335,14 +336,12 @@ public void onStartup(ServletContext servletContext) throws ServletException { configureSessionCookie(servletContext.getSessionCookieConfig()); } - @SuppressWarnings("removal") private void configureSessionCookie(SessionCookieConfig config) { - Session.Cookie cookie = this.session.getCookie(); + Cookie cookie = this.session.getCookie(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(cookie::getName).to(config::setName); map.from(cookie::getDomain).to(config::setDomain); map.from(cookie::getPath).to(config::setPath); - map.from(cookie::getComment).to(config::setComment); map.from(cookie::getHttpOnly).to(config::setHttpOnly); map.from(cookie::getSecure).to(config::setSecure); map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(config::setMaxAge); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/Session.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/Session.java index 336c03ced541..df95dc474795 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/Session.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/Session.java @@ -21,8 +21,9 @@ import java.time.temporal.ChronoUnit; import java.util.Set; -import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.web.server.Cookie; /** * Session properties. @@ -44,6 +45,7 @@ public class Session { */ private File storeDir; + @NestedConfigurationProperty private final Cookie cookie = new Cookie(); private final SessionStoreDirectory sessionStoreDirectory = new SessionStoreDirectory(); @@ -101,34 +103,6 @@ SessionStoreDirectory getSessionStoreDirectory() { return this.sessionStoreDirectory; } - /** - * Session cookie properties. - */ - public static class Cookie extends org.springframework.boot.web.server.Cookie { - - /** - * Comment for the session cookie. - */ - private String comment; - - /** - * Return the comment for the session cookie. - * @return the session cookie comment - * @deprecated since 3.0.0 without replacement - */ - @Deprecated(since = "3.0.0", forRemoval = true) - @DeprecatedConfigurationProperty - public String getComment() { - return this.comment; - } - - @Deprecated(since = "3.0.0", forRemoval = true) - public void setComment(String comment) { - this.comment = comment; - } - - } - /** * Available session tracking modes (mirrors * {@link jakarta.servlet.SessionTrackingMode}. diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java index f48d5be9527b..7e361c87471b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java @@ -233,24 +233,6 @@ void forValueObjectWithConstructorBindingAnnotatedClassReturnsBean() { .isNotNull(); } - @Test - void forValueObjectWithDeprecatedConstructorBindingAnnotatedClassReturnsBean() { - ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean - .forValueObject(DeprecatedConstructorBindingOnConstructor.class, "valueObjectBean"); - assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean"); - assertThat(propertiesBean.getInstance()).isNull(); - assertThat(propertiesBean.getType()).isEqualTo(DeprecatedConstructorBindingOnConstructor.class); - assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT); - assertThat(propertiesBean.getAnnotation()).isNotNull(); - Bindable target = propertiesBean.asBindTarget(); - assertThat(target.getType()) - .isEqualTo(ResolvableType.forClass(DeprecatedConstructorBindingOnConstructor.class)); - assertThat(target.getValue()).isNull(); - assertThat(BindConstructorProvider.DEFAULT.getBindConstructor(DeprecatedConstructorBindingOnConstructor.class, - false)) - .isNotNull(); - } - @Test void forValueObjectWithRecordReturnsBean() { Class implicitConstructorBinding = new ByteBuddy(ClassFileVersion.JAVA_V16).makeRecord() @@ -558,20 +540,6 @@ static class ConstructorBindingOnConstructor { } - @ConfigurationProperties - @SuppressWarnings("removal") - static class DeprecatedConstructorBindingOnConstructor { - - DeprecatedConstructorBindingOnConstructor(String name) { - this(name, -1); - } - - @org.springframework.boot.context.properties.ConstructorBinding - DeprecatedConstructorBindingOnConstructor(String name, int age) { - } - - } - @ConfigurationProperties static class ConstructorBindingOnMultipleConstructors { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java index 42bf7a8f8181..768a1c0b8da4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.jackson; import java.util.Arrays; -import java.util.Collections; import java.util.List; import com.fasterxml.jackson.databind.Module; @@ -35,7 +34,6 @@ import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link JsonMixinModule}. @@ -53,14 +51,6 @@ void closeContext() { } } - @Test - @Deprecated(since = "3.0.0", forRemoval = true) - @SuppressWarnings("removal") - void createWhenContextIsNullShouldThrowException() { - assertThatIllegalArgumentException().isThrownBy(() -> new JsonMixinModule(null, Collections.emptyList())) - .withMessageContaining("Context must not be null"); - } - @Test void jsonWithModuleWithRenameMixInClassShouldBeMixedIn() throws Exception { load(RenameMixInClass.class); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java index 6e1851e72dc6..46e8eb59ebfe 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java @@ -120,11 +120,6 @@ void restoreTccl() { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); } - @Override - protected boolean isCookieCommentSupported() { - return false; - } - // JMX MBean names clash if you get more than one Engine with the same name... @Test void tomcatEngineNames() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java index adadee3b8a0f..12a9aea44ccc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java @@ -204,10 +204,6 @@ void tearDown() { } } - protected boolean isCookieCommentSupported() { - return true; - } - @Test void startServlet() throws Exception { AbstractServletWebServerFactory factory = getFactory(); @@ -870,13 +866,11 @@ void getValidSessionStoreWhenSessionStoreReferencesFile() throws Exception { } @Test - @SuppressWarnings("removal") void sessionCookieConfiguration() { AbstractServletWebServerFactory factory = getFactory(); factory.getSession().getCookie().setName("testname"); factory.getSession().getCookie().setDomain("testdomain"); factory.getSession().getCookie().setPath("/testpath"); - factory.getSession().getCookie().setComment("testcomment"); factory.getSession().getCookie().setHttpOnly(true); factory.getSession().getCookie().setSecure(true); factory.getSession().getCookie().setMaxAge(Duration.ofSeconds(60)); @@ -886,9 +880,6 @@ void sessionCookieConfiguration() { assertThat(sessionCookieConfig.getName()).isEqualTo("testname"); assertThat(sessionCookieConfig.getDomain()).isEqualTo("testdomain"); assertThat(sessionCookieConfig.getPath()).isEqualTo("/testpath"); - if (isCookieCommentSupported()) { - assertThat(sessionCookieConfig.getComment()).isEqualTo("testcomment"); - } assertThat(sessionCookieConfig.isHttpOnly()).isTrue(); assertThat(sessionCookieConfig.isSecure()).isTrue(); assertThat(sessionCookieConfig.getMaxAge()).isEqualTo(60); @@ -1143,7 +1134,6 @@ public void destroy() { } @Test - @SuppressWarnings("removal") void sessionConfiguration() { AbstractServletWebServerFactory factory = getFactory(); factory.getSession().setTimeout(Duration.ofSeconds(123)); @@ -1151,7 +1141,6 @@ void sessionConfiguration() { factory.getSession().getCookie().setName("testname"); factory.getSession().getCookie().setDomain("testdomain"); factory.getSession().getCookie().setPath("/testpath"); - factory.getSession().getCookie().setComment("testcomment"); factory.getSession().getCookie().setHttpOnly(true); factory.getSession().getCookie().setSecure(true); factory.getSession().getCookie().setMaxAge(Duration.ofMinutes(1)); @@ -1163,9 +1152,6 @@ void sessionConfiguration() { assertThat(servletContext.getSessionCookieConfig().getName()).isEqualTo("testname"); assertThat(servletContext.getSessionCookieConfig().getDomain()).isEqualTo("testdomain"); assertThat(servletContext.getSessionCookieConfig().getPath()).isEqualTo("/testpath"); - if (isCookieCommentSupported()) { - assertThat(servletContext.getSessionCookieConfig().getComment()).isEqualTo("testcomment"); - } assertThat(servletContext.getSessionCookieConfig().isHttpOnly()).isTrue(); assertThat(servletContext.getSessionCookieConfig().isSecure()).isTrue(); assertThat(servletContext.getSessionCookieConfig().getMaxAge()).isEqualTo(60); From 493987fc1a2dc894ecb9563c11a16e95032a39c8 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Fri, 23 Jun 2023 14:40:42 -0600 Subject: [PATCH 0067/1656] Allow key password to be set for a PemSslStoreBundle Closes gh-35983 --- .../boot/ssl/pem/PemSslStoreBundle.java | 18 ++++++++++++- .../boot/ssl/pem/PemSslStoreBundleTests.java | 26 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java index dee4651852af..251ff0b52799 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java @@ -42,6 +42,8 @@ public class PemSslStoreBundle implements SslStoreBundle { private final String keyAlias; + private final String keyPassword; + /** * Create a new {@link PemSslStoreBundle} instance. * @param keyStoreDetails the key store details @@ -59,9 +61,22 @@ public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails */ public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String keyAlias) { + this(keyStoreDetails, trustStoreDetails, keyAlias, null); + } + + /** + * Create a new {@link PemSslStoreBundle} instance. + * @param keyStoreDetails the key store details + * @param trustStoreDetails the trust store details + * @param keyAlias the key alias to use or {@code null} to use a default alias + * @param keyPassword the password to use for the key + */ + public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String keyAlias, + String keyPassword) { this.keyAlias = keyAlias; this.keyStoreDetails = keyStoreDetails; this.trustStoreDetails = trustStoreDetails; + this.keyPassword = keyPassword; } @Override @@ -104,7 +119,8 @@ private void addCertificates(KeyStore keyStore, X509Certificate[] certificates, throws KeyStoreException { String alias = (this.keyAlias != null) ? this.keyAlias : DEFAULT_KEY_ALIAS; if (privateKey != null) { - keyStore.setKeyEntry(alias, privateKey, null, certificates); + keyStore.setKeyEntry(alias, privateKey, (this.keyPassword != null) ? this.keyPassword.toCharArray() : null, + certificates); } else { for (int index = 0; index < certificates.length; index++) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java index 61f5c4983bd0..29c22a27e0ce 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java @@ -33,6 +33,8 @@ */ class PemSslStoreBundleTests { + private static final char[] EMPTY_KEY_PASSWORD = new char[] {}; + @Test void whenNullStores() { PemSslStoreDetails keyStoreDetails = null; @@ -117,6 +119,18 @@ void whenHasStoreType() { assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("PKCS12", "ssl")); } + @Test + void whenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword() { + PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem") + .withPrivateKey("classpath:test-key.pem"); + PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem") + .withPrivateKey("classpath:test-key.pem"); + PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, "test-alias", "keysecret"); + assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray())); + assertThat(bundle.getTrustStore()) + .satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray())); + } + private Consumer storeContainingCert(String keyAlias) { return storeContainingCert(KeyStore.getDefaultType(), keyAlias); } @@ -127,7 +141,7 @@ private Consumer storeContainingCert(String keyStoreType, String keyAl assertThat(keyStore.getType()).isEqualTo(keyStoreType); assertThat(keyStore.containsAlias(keyAlias)).isTrue(); assertThat(keyStore.getCertificate(keyAlias)).isNotNull(); - assertThat(keyStore.getKey(keyAlias, new char[] {})).isNull(); + assertThat(keyStore.getKey(keyAlias, EMPTY_KEY_PASSWORD)).isNull(); }); } @@ -136,12 +150,20 @@ private Consumer storeContainingCertAndKey(String keyAlias) { } private Consumer storeContainingCertAndKey(String keyStoreType, String keyAlias) { + return storeContainingCertAndKey(keyStoreType, keyAlias, EMPTY_KEY_PASSWORD); + } + + private Consumer storeContainingCertAndKey(String keyAlias, char[] keyPassword) { + return storeContainingCertAndKey(KeyStore.getDefaultType(), keyAlias, keyPassword); + } + + private Consumer storeContainingCertAndKey(String keyStoreType, String keyAlias, char[] keyPassword) { return ThrowingConsumer.of((keyStore) -> { assertThat(keyStore).isNotNull(); assertThat(keyStore.getType()).isEqualTo(keyStoreType); assertThat(keyStore.containsAlias(keyAlias)).isTrue(); assertThat(keyStore.getCertificate(keyAlias)).isNotNull(); - assertThat(keyStore.getKey(keyAlias, new char[] {})).isNotNull(); + assertThat(keyStore.getKey(keyAlias, keyPassword)).isNotNull(); }); } From dbb24286ff2a9e2b159d33029f792cdf10397924 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 31 May 2023 12:25:01 +0100 Subject: [PATCH 0068/1656] Separate stopping and destruction so web server can be restarted Previously, when a Servlet-based WebServer was stopped it would also stop the ServletContext. This led to problems as Tomcat and Undertow would then not allow a restart. Jetty would allow a restart but duplicate servlet registrations would then be attempted. This commit modifies the WebServer lifecycle to separate stopping and destruction for both servlet and reactive web servers. This allows a WebServer's stop() implementation to leave some components running so that they can be restarted. To completely shut down a WebServer destroy() must now be called. Both Tomcat and Jetty WebServers have been updated to stop their network connections when stop() is called but leave other components running. This works with both servlet and reactive web servers. Note that an Undertow-based Servlet web server does not support stop and restart. Once stopped, a Servlet Deployment cannot be restarted and it does not appear to be possible to separate the lifecycle of its network connections and a Servlet deployment. Reactor Netty and Undertow-based reactive web servers can now also be stopped and then restarted. Calling stop() stops the whole server but this does not cause a problem as there's no (application-exposed) ServletContext involved. There may be room to optimize this in the future if the need arises. Closes gh-34955 --- .../web/embedded/jetty/JettyWebServer.java | 16 +++++++- .../web/embedded/tomcat/GracefulShutdown.java | 14 +++---- .../web/embedded/tomcat/TomcatWebServer.java | 26 ++++++++----- .../embedded/undertow/UndertowWebServer.java | 6 +-- .../boot/web/server/WebServer.java | 10 ++++- .../ServletWebServerApplicationContext.java | 7 +++- .../JettyServletWebServerFactoryTests.java | 4 +- .../TomcatServletWebServerFactoryTests.java | 4 +- .../UndertowServletWebServerFactoryTests.java | 15 ++++++++ ...AbstractReactiveWebServerFactoryTests.java | 33 ++++++++++++++++- ...rvletWebServerApplicationContextTests.java | 28 +++++++++++++- .../AbstractServletWebServerFactoryTests.java | 37 +++++++++++++++++-- .../boot/loaderapp/LoaderTestApplication.java | 4 +- 13 files changed, 169 insertions(+), 35 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java index 95ae2e3c2b67..21f1052e6ed2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java @@ -238,7 +238,9 @@ public void stop() { this.gracefulShutdown.abort(); } try { - this.server.stop(); + for (Connector connector : this.server.getConnectors()) { + connector.stop(); + } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); @@ -249,6 +251,18 @@ public void stop() { } } + @Override + public void destroy() { + synchronized (this.monitor) { + try { + this.server.stop(); + } + catch (Exception ex) { + throw new WebServerException("Unable to destroy embedded Jetty server", ex); + } + } + } + @Override public int getPort() { Connector[] connectors = this.server.getConnectors(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/GracefulShutdown.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/GracefulShutdown.java index c921cf5c94aa..3215a0de8609 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/GracefulShutdown.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/GracefulShutdown.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -60,14 +60,14 @@ private void doShutdown(GracefulShutdownCallback callback) { try { for (Container host : this.tomcat.getEngine().findChildren()) { for (Container context : host.findChildren()) { - while (isActive(context)) { - if (this.aborted) { - logger.info("Graceful shutdown aborted with one or more requests still active"); - callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE); - return; - } + while (!this.aborted && isActive(context)) { Thread.sleep(50); } + if (this.aborted) { + logger.info("Graceful shutdown aborted with one or more requests still active"); + callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE); + return; + } } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java index 7c05aa77f3c2..c2dfde03e9e7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java @@ -209,6 +209,7 @@ public void start() throws WebServerException { if (this.started) { return; } + try { addPreviouslyRemovedConnectors(); Connector connector = this.tomcat.getConnector(); @@ -324,16 +325,10 @@ public void stop() throws WebServerException { boolean wasStarted = this.started; try { this.started = false; - try { - if (this.gracefulShutdown != null) { - this.gracefulShutdown.abort(); - } - stopTomcat(); - this.tomcat.destroy(); - } - catch (LifecycleException ex) { - // swallow and continue + if (this.gracefulShutdown != null) { + this.gracefulShutdown.abort(); } + removeServiceConnectors(); } catch (Exception ex) { throw new WebServerException("Unable to stop embedded Tomcat", ex); @@ -346,6 +341,19 @@ public void stop() throws WebServerException { } } + public void destroy() throws WebServerException { + try { + stopTomcat(); + this.tomcat.destroy(); + } + catch (LifecycleException ex) { + // Swallow and continue + } + catch (Exception ex) { + throw new WebServerException("Unable to destroy embedded Tomcat", ex); + } + } + private String getPortsDescription(boolean localPort) { StringBuilder ports = new StringBuilder(); for (Connector connector : this.tomcat.getService().findConnectors()) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java index dd7f887bfb69..563b2fc4b197 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java @@ -132,13 +132,13 @@ public void start() throws WebServerException { throw new WebServerException("Unable to start embedded Undertow", ex); } finally { - stopSilently(); + destroySilently(); } } } } - private void stopSilently() { + private void destroySilently() { try { if (this.undertow != null) { this.undertow.stop(); @@ -274,7 +274,7 @@ public void stop() throws WebServerException { } } catch (Exception ex) { - throw new WebServerException("Unable to stop undertow", ex); + throw new WebServerException("Unable to stop Undertow", ex); } } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServer.java index 6b70c02ca7b0..e5c02a86f801 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -61,4 +61,12 @@ default void shutDownGracefully(GracefulShutdownCallback callback) { callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE); } + /** + * Destroys the web server such that it cannot be started again. + * @since 3.2.0 + */ + default void destroy() { + stop(); + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java index 534237cc7728..d6513792d21b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -149,6 +149,7 @@ public final void refresh() throws BeansException, IllegalStateException { WebServer webServer = this.webServer; if (webServer != null) { webServer.stop(); + webServer.destroy(); } throw ex; } @@ -171,6 +172,10 @@ protected void doClose() { AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC); } super.doClose(); + WebServer webServer = this.webServer; + if (webServer != null) { + webServer.destroy(); + } } private void createWebServer() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java index f037202ca4e2..9dc3ec21c541 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java @@ -234,10 +234,10 @@ void sslCiphersConfiguration() { } @Test - void stopCalledWithoutStart() { + void destroyCalledWithoutStart() { JettyServletWebServerFactory factory = getFactory(); this.webServer = factory.getWebServer(exampleServletRegistration()); - this.webServer.stop(); + this.webServer.destroy(); Server server = ((JettyWebServer) this.webServer).getServer(); assertThat(server.isStopped()).isTrue(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java index 46e8eb59ebfe..ba8c07b6c0e9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java @@ -340,10 +340,10 @@ void startupFailureDoesNotResultInUnstoppedThreadsBeingReported(CapturedOutput o } @Test - void stopCalledWithoutStart() { + void destroyCalledWithoutStart() { TomcatServletWebServerFactory factory = getFactory(); this.webServer = factory.getWebServer(exampleServletRegistration()); - this.webServer.stop(); + this.webServer.destroy(); Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat(); assertThat(tomcat.getServer().getState()).isSameAs(LifecycleState.DESTROYED); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java index dd42e2bf9966..f0c5f7d4bd55 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java @@ -40,6 +40,7 @@ import org.apache.jasper.servlet.JspServlet; import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.InOrder; @@ -211,6 +212,20 @@ void whenServerIsShuttingDownGracefullyThenRequestsAreRejectedWithServiceUnavail this.webServer.stop(); } + @Test + @Override + @Disabled("Restart after stop is not supported with Undertow") + protected void restartAfterStop() { + + } + + @Test + @Override + @Disabled("Undertow's architecture prevents separating stop and destroy") + protected void servletContextListenerContextDestroyedIsNotCalledWhenContainerIsStopped() { + + } + private void testAccessLog(String prefix, String suffix, String expectedFile) throws IOException, URISyntaxException { UndertowServletWebServerFactory factory = getFactory(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java index 2985bf9d34d3..2fb586580812 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java @@ -77,6 +77,7 @@ import org.springframework.web.reactive.function.client.WebClientRequestException; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -95,6 +96,12 @@ void tearDown() { if (this.webServer != null) { try { this.webServer.stop(); + try { + this.webServer.destroy(); + } + catch (Exception ex) { + // Ignore + } } catch (Exception ex) { // Ignore @@ -124,13 +131,37 @@ void specificPort() throws Exception { assertThat(this.webServer.getPort()).isEqualTo(specificPort); } + @Test + protected void restartAfterStop() throws Exception { + AbstractReactiveWebServerFactory factory = getFactory(); + this.webServer = factory.getWebServer(new EchoHandler()); + this.webServer.start(); + int port = this.webServer.getPort(); + assertThat(getResponse(port, "/test")).isEqualTo("Hello World"); + this.webServer.stop(); + assertThatException().isThrownBy(() -> getResponse(port, "/test")); + this.webServer.start(); + assertThat(getResponse(this.webServer.getPort(), "/test")).isEqualTo("Hello World"); + } + + private String getResponse(int port, String uri) { + WebClient webClient = getWebClient(port).build(); + Mono result = webClient.post() + .uri(uri) + .contentType(MediaType.TEXT_PLAIN) + .body(BodyInserters.fromValue("Hello World")) + .retrieve() + .bodyToMono(String.class); + return result.block(Duration.ofSeconds(30)); + } + @Test void portIsMinusOneWhenConnectionIsClosed() { AbstractReactiveWebServerFactory factory = getFactory(); this.webServer = factory.getWebServer(new EchoHandler()); this.webServer.start(); assertThat(this.webServer.getPort()).isGreaterThan(0); - this.webServer.stop(); + this.webServer.destroy(); assertThat(this.webServer.getPort()).isEqualTo(-1); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContextTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContextTests.java index 4bde93562782..059b6224c294 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContextTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContextTests.java @@ -84,6 +84,7 @@ import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.withSettings; /** @@ -156,12 +157,35 @@ void localPortIsAvailable() { } @Test - void stopOnClose() { + void stopOnStop() { addWebServerFactoryBean(); this.context.refresh(); MockServletWebServerFactory factory = getWebServerFactory(); - this.context.close(); + then(factory.getWebServer()).should().start(); + this.context.stop(); + then(factory.getWebServer()).should().stop(); + } + + @Test + void startOnStartAfterStop() { + addWebServerFactoryBean(); + this.context.refresh(); + MockServletWebServerFactory factory = getWebServerFactory(); + then(factory.getWebServer()).should().start(); + this.context.stop(); then(factory.getWebServer()).should().stop(); + this.context.start(); + then(factory.getWebServer()).should(times(2)).start(); + } + + @Test + void stopAndDestroyOnClose() { + addWebServerFactoryBean(); + this.context.refresh(); + MockServletWebServerFactory factory = getWebServerFactory(); + this.context.close(); + then(factory.getWebServer()).should(times(2)).stop(); + then(factory.getWebServer()).should().destroy(); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java index 12a9aea44ccc..c6c53781daf6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java @@ -167,6 +167,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; /** * Base for testing classes that extends {@link AbstractServletWebServerFactory}. @@ -197,6 +198,12 @@ void tearDown() { if (this.webServer != null) { try { this.webServer.stop(); + try { + this.webServer.destroy(); + } + catch (Exception ex) { + // Ignore + } } catch (Exception ex) { // Ignore @@ -233,6 +240,19 @@ void stopCalledTwice() { this.webServer.stop(); } + @Test + protected void restartAfterStop() throws IOException, URISyntaxException { + AbstractServletWebServerFactory factory = getFactory(); + this.webServer = factory.getWebServer(exampleServletRegistration()); + this.webServer.start(); + assertThat(getResponse(getLocalUrl("/hello"))).isEqualTo("Hello World"); + int port = this.webServer.getPort(); + this.webServer.stop(); + assertThatIOException().isThrownBy(() -> getResponse(getLocalUrl(port, "/hello"))); + this.webServer.start(); + assertThat(getResponse(getLocalUrl("/hello"))).isEqualTo("Hello World"); + } + @Test void emptyServerWhenPortIsMinusOne() { AbstractServletWebServerFactory factory = getFactory(); @@ -295,7 +315,7 @@ void portIsMinusOneWhenConnectionIsClosed() { this.webServer = factory.getWebServer(); this.webServer.start(); assertThat(this.webServer.getPort()).isGreaterThan(0); - this.webServer.stop(); + this.webServer.destroy(); assertThat(this.webServer.getPort()).isEqualTo(-1); } @@ -814,7 +834,7 @@ void persistSession() throws Exception { this.webServer.start(); String s1 = getResponse(getLocalUrl("/session")); String s2 = getResponse(getLocalUrl("/session")); - this.webServer.stop(); + this.webServer.destroy(); this.webServer = factory.getWebServer(sessionServletRegistration()); this.webServer.start(); String s3 = getResponse(getLocalUrl("/session")); @@ -833,7 +853,7 @@ void persistSessionInSpecificSessionStoreDir() throws Exception { this.webServer = factory.getWebServer(sessionServletRegistration()); this.webServer.start(); getResponse(getLocalUrl("/session")); - this.webServer.stop(); + this.webServer.destroy(); File[] dirContents = sessionStoreDir.listFiles((dir, name) -> !(".".equals(name) || "..".equals(name))); assertThat(dirContents).isNotEmpty(); } @@ -1158,11 +1178,20 @@ void sessionConfiguration() { } @Test - void servletContextListenerContextDestroyedIsCalledWhenContainerIsStopped() throws Exception { + protected void servletContextListenerContextDestroyedIsNotCalledWhenContainerIsStopped() throws Exception { ServletContextListener listener = mock(ServletContextListener.class); this.webServer = getFactory().getWebServer((servletContext) -> servletContext.addListener(listener)); this.webServer.start(); this.webServer.stop(); + then(listener).should(times(0)).contextDestroyed(any(ServletContextEvent.class)); + } + + @Test + void servletContextListenerContextDestroyedIsCalledWhenContainerIsDestroyed() throws Exception { + ServletContextListener listener = mock(ServletContextListener.class); + this.webServer = getFactory().getWebServer((servletContext) -> servletContext.addListener(listener)); + this.webServer.start(); + this.webServer.destroy(); then(listener).should().contextDestroyed(any(ServletContextEvent.class)); } diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/spring-boot-loader-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/spring-boot-loader-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java index 81b7c41cbd3a..0c9d429350d8 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/spring-boot-loader-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/spring-boot-loader-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -53,7 +53,7 @@ public CommandLineRunner commandLineRunner(ServletContext servletContext) { } public static void main(String[] args) { - SpringApplication.run(LoaderTestApplication.class, args).stop(); + SpringApplication.run(LoaderTestApplication.class, args).close(); } } From 98d459d76cb887f4624133a0aa953ec8e2c477de Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 27 Jun 2023 14:16:20 +0100 Subject: [PATCH 0069/1656] Revert "Merge branch '3.1.x'" See gh-36092 --- ...ndertowWebServerFactoryCustomizerTests.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java index c9c2547b62a4..e5a2c95e8271 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java @@ -103,24 +103,6 @@ void customMaxHttpRequestHeaderSizeIgnoredIfZero() { assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull(); } - @Test - void customMaxHttpRequestHeaderSize() { - bind("server.max-http-request-header-size=2048"); - assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isEqualTo(2048); - } - - @Test - void customMaxHttpRequestHeaderSizeIgnoredIfNegative() { - bind("server.max-http-request-header-size=-1"); - assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull(); - } - - @Test - void customMaxHttpRequestHeaderSizeIgnoredIfZero() { - bind("server.max-http-request-header-size=0"); - assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull(); - } - @Test void customMaxHttpPostSize() { bind("server.undertow.max-http-post-size=256"); From 0b39429f96dca3c0a027d149e2c7d403c140a17e Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 28 Jun 2023 14:11:37 +0100 Subject: [PATCH 0070/1656] Remove containers after use in Docker Compose integration tests Closes gh-36104 --- .../connection/test/AbstractDockerComposeIntegrationTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIntegrationTests.java index f142ba86df74..d59742b86314 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIntegrationTests.java @@ -58,6 +58,7 @@ protected final T run(Class type) { properties.put("spring.docker.compose.skip.in-tests", "false"); properties.put("spring.docker.compose.file", ThrowingSupplier.of(this.composeResource::getFile).get().getAbsolutePath()); + properties.put("spring.docker.compose.stop.command", "down"); application.setDefaultProperties(properties); return application.run().getBean(type); } From b32697b3cebdf2fa8b7e229fe60e2e9df7030984 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 29 Jun 2023 16:53:36 +0100 Subject: [PATCH 0071/1656] Add support to @ClassPathExclusions for excluding packages Closes gh-36120 --- .../classpath/ClassPathExclusions.java | 27 +++++++++++++++++-- .../ModifiedClassPathClassLoader.java | 25 +++++++++++++++-- ...fiedClassPathExtensionExclusionsTests.java | 22 ++++++++++++--- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ClassPathExclusions.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ClassPathExclusions.java index f7c809f94704..8f124bcd02ee 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ClassPathExclusions.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ClassPathExclusions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -25,6 +25,8 @@ import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.core.annotation.AliasFor; + /** * Annotation used to exclude entries from the classpath. * @@ -37,13 +39,34 @@ @ExtendWith(ModifiedClassPathExtension.class) public @interface ClassPathExclusions { + /** + * Alias for {@code files}. + *

+ * One or more Ant-style patterns that identify entries to be excluded from the class + * path. Matching is performed against an entry's {@link File#getName() file name}. + * For example, to exclude Hibernate Validator from the classpath, + * {@code "hibernate-validator-*.jar"} can be used. + * @return the exclusion patterns + */ + @AliasFor("files") + String[] value() default {}; + /** * One or more Ant-style patterns that identify entries to be excluded from the class * path. Matching is performed against an entry's {@link File#getName() file name}. * For example, to exclude Hibernate Validator from the classpath, * {@code "hibernate-validator-*.jar"} can be used. * @return the exclusion patterns + * @since 3.2.0 + */ + @AliasFor("value") + String[] files() default {}; + + /** + * One or more packages that should be excluded from the classpath. + * @return the excluded packages + * @since 3.2.0 */ - String[] value(); + String[] packages() default {}; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java index fdd5f0c91594..acb91585bea2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -56,6 +57,7 @@ import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.util.AntPathMatcher; +import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -74,10 +76,14 @@ final class ModifiedClassPathClassLoader extends URLClassLoader { private static final int MAX_RESOLUTION_ATTEMPTS = 5; + private final Set excludedPackages; + private final ClassLoader junitLoader; - ModifiedClassPathClassLoader(URL[] urls, ClassLoader parent, ClassLoader junitLoader) { + ModifiedClassPathClassLoader(URL[] urls, Set excludedPackages, ClassLoader parent, + ClassLoader junitLoader) { super(urls, parent); + this.excludedPackages = excludedPackages; this.junitLoader = junitLoader; } @@ -87,6 +93,10 @@ public Class loadClass(String name) throws ClassNotFoundException { || name.startsWith("io.netty.internal.tcnative")) { return Class.forName(name, false, this.junitLoader); } + String packageName = ClassUtils.getPackageName(name); + if (this.excludedPackages.contains(packageName)) { + throw new ClassNotFoundException(); + } return super.loadClass(name); } @@ -130,7 +140,7 @@ private static ModifiedClassPathClassLoader compute(ClassLoader classLoader, .map((source) -> MergedAnnotations.from(source, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)) .toList(); return new ModifiedClassPathClassLoader(processUrls(extractUrls(classLoader), annotations), - classLoader.getParent(), classLoader); + excludedPackages(annotations), classLoader.getParent(), classLoader); } private static URL[] extractUrls(ClassLoader classLoader) { @@ -269,6 +279,17 @@ private static List createDependencies(String[] allCoordinates) { return dependencies; } + private static Set excludedPackages(List annotations) { + Set excludedPackages = new HashSet<>(); + for (MergedAnnotations candidate : annotations) { + MergedAnnotation annotation = candidate.get(ClassPathExclusions.class); + if (annotation.isPresent()) { + excludedPackages.addAll(Arrays.asList(annotation.getStringArray("packages"))); + } + } + return excludedPackages; + } + /** * Filter for class path entries. */ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtensionExclusionsTests.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtensionExclusionsTests.java index 5cfdd53c0af0..0e0741bd2ace 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtensionExclusionsTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/test/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathExtensionExclusionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -19,6 +19,8 @@ import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; +import org.springframework.util.ClassUtils; + import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.isA; @@ -26,22 +28,34 @@ * Tests for {@link ModifiedClassPathExtension} excluding entries from the class path. * * @author Christoph Dreis + * @author Andy Wilkinson */ -@ClassPathExclusions("hibernate-validator-*.jar") +@ClassPathExclusions(files = "hibernate-validator-*.jar", packages = "java.net.http") class ModifiedClassPathExtensionExclusionsTests { private static final String EXCLUDED_RESOURCE = "META-INF/services/jakarta.validation.spi.ValidationProvider"; @Test - void entriesAreFilteredFromTestClassClassLoader() { + void fileExclusionsAreFilteredFromTestClassClassLoader() { assertThat(getClass().getClassLoader().getResource(EXCLUDED_RESOURCE)).isNull(); } @Test - void entriesAreFilteredFromThreadContextClassLoader() { + void fileExclusionsAreFilteredFromThreadContextClassLoader() { assertThat(Thread.currentThread().getContextClassLoader().getResource(EXCLUDED_RESOURCE)).isNull(); } + @Test + void packageExclusionsAreFilteredFromTestClassClassLoader() { + assertThat(ClassUtils.isPresent("java.net.http.HttpClient", getClass().getClassLoader())).isFalse(); + } + + @Test + void packageExclusionsAreFilteredFromThreadContextClassLoader() { + assertThat(ClassUtils.isPresent("java.net.http.HttpClient", Thread.currentThread().getContextClassLoader())) + .isFalse(); + } + @Test void testsThatUseHamcrestWorkCorrectly() { Matcher matcher = isA(IllegalStateException.class); From 32b7b312f059e446297e6d87398bc616d20a4f65 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 16 Jun 2023 12:29:14 +0100 Subject: [PATCH 0072/1656] Add config metadata changelog generator to main build Closes gh-21486 --- settings.gradle | 1 + .../build.gradle | 58 +++++ ...nfigurationMetadataChangelogGenerator.java | 56 +++++ .../ConfigurationMetadataChangelogWriter.java | 204 ++++++++++++++++++ .../changelog/ConfigurationMetadataDiff.java | 109 ++++++++++ .../NamedConfigurationMetadataRepository.java | 80 +++++++ .../changelog/package-info.java | 20 ++ .../ConfigurationMetadataDiffTests.java | 92 ++++++++ .../src/test/resources/sample-1.0.json | 31 +++ .../src/test/resources/sample-2.0.json | 34 +++ src/checkstyle/checkstyle-suppressions.xml | 1 + 11 files changed, 686 insertions(+) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json diff --git a/settings.gradle b/settings.gradle index 101c44479d93..48b547ef1c4e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -53,6 +53,7 @@ include "spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-process include "spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform" include "spring-boot-project:spring-boot-tools:spring-boot-cli" include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata" +include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata-changelog-generator" include "spring-boot-project:spring-boot-tools:spring-boot-configuration-processor" include "spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin" include "spring-boot-project:spring-boot-tools:spring-boot-gradle-test-support" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle new file mode 100644 index 000000000000..4d5c517e9040 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle @@ -0,0 +1,58 @@ +plugins { + id "java" + id "org.springframework.boot.conventions" +} + +description = "Spring Boot Configuration Metadata Changelog Generator" + +configurations { + oldMetadata + newMetadata +} + +dependencies { + implementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) + implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata")) + + testImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) + testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter") +} + +if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { + dependencies { + ["spring-boot", + "spring-boot-actuator", + "spring-boot-actuator-autoconfigure", + "spring-boot-autoconfigure", + "spring-boot-devtools", + "spring-boot-test-autoconfigure"].each { + oldMetadata("org.springframework.boot:$it:$oldVersion") + newMetadata("org.springframework.boot:$it:$newVersion") + } + } + + def prepareOldMetadata = tasks.register("prepareOldMetadata", Sync) { + from(configurations.oldMetadata) + if (project.hasProperty("oldVersion")) { + destinationDir = project.file("build/configuration-metadata-diff/$oldVersion") + } + } + + def prepareNewMetadata = tasks.register("prepareNewMetadata", Sync) { + from(configurations.newMetadata) + if (project.hasProperty("newVersion")) { + destinationDir = project.file("build/configuration-metadata-diff/$newVersion") + } + } + + tasks.register("generate", JavaExec) { + inputs.files(prepareOldMetadata, prepareNewMetadata) + outputs.file(project.file("build/configuration-metadata-changelog.adoc")) + classpath = sourceSets.main.runtimeClasspath + mainClass = 'org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataChangelogGenerator' + if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { + args = [project.file("build/configuration-metadata-diff/$oldVersion"), project.file("build/configuration-metadata-diff/$newVersion"), project.file("build/configuration-metadata-changelog.adoc")] + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java new file mode 100644 index 000000000000..2eb5b1244ec0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Generates a configuration metadata changelog. Requires three arguments: + * + *

    + *
  1. The path of a directory containing jar files from which the old metadata will be + * extracted + *
  2. The path of a directory containing jar files from which the new metadata will be + * extracted + *
  3. The path of a file to which the changelog will be written + *
+ * + * The name of each directory will be used to name the old and new metadata in the + * generated changelog + * + * @author Andy Wilkinson + */ +final class ConfigurationMetadataChangelogGenerator { + + private ConfigurationMetadataChangelogGenerator() { + + } + + public static void main(String[] args) throws IOException { + ConfigurationMetadataDiff diff = ConfigurationMetadataDiff.of( + NamedConfigurationMetadataRepository.from(new File(args[0])), + NamedConfigurationMetadataRepository.from(new File(args[1]))); + try (ConfigurationMetadataChangelogWriter writer = new ConfigurationMetadataChangelogWriter( + new FileWriter(new File(args[2])))) { + writer.write(diff); + } + System.out.println("\nConfiguration metadata changelog written to '" + args[2] + "'"); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java new file mode 100644 index 000000000000..b08b1667d34e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java @@ -0,0 +1,204 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.PrintWriter; +import java.io.Writer; +import java.text.BreakIterator; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation; +import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference; +import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; + +/** + * Writes a configuration metadata changelog from a {@link ConfigurationMetadataDiff}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class ConfigurationMetadataChangelogWriter implements AutoCloseable { + + private final PrintWriter out; + + ConfigurationMetadataChangelogWriter(Writer out) { + this.out = new PrintWriter(out); + } + + void write(ConfigurationMetadataDiff diff) { + this.out.append(String.format("Configuration property changes between `%s` and " + "`%s`%n", diff.leftName(), + diff.rightName())); + this.out.append(System.lineSeparator()); + this.out.append(String.format("== Deprecated in `%s`%n", diff.rightName())); + Map> differencesByType = differencesByType(diff); + writeDeprecatedProperties(differencesByType.get(Type.DEPRECATED)); + this.out.append(System.lineSeparator()); + this.out.append(String.format("== New in `%s`%n", diff.rightName())); + writeAddedProperties(differencesByType.get(Type.ADDED)); + this.out.append(System.lineSeparator()); + this.out.append(String.format("== Removed in `%s`%n", diff.rightName())); + writeRemovedProperties(differencesByType.get(Type.DELETED), differencesByType.get(Type.DEPRECATED)); + } + + private Map> differencesByType(ConfigurationMetadataDiff diff) { + Map> differencesByType = new HashMap<>(); + for (Type type : Type.values()) { + differencesByType.put(type, new ArrayList<>()); + } + for (Difference difference : diff.differences()) { + differencesByType.get(difference.type()).add(difference); + } + return differencesByType; + } + + private void writeDeprecatedProperties(List differences) { + if (differences.isEmpty()) { + this.out.append(String.format("None.%n")); + } + else { + List properties = sortProperties(differences, Difference::right).stream() + .filter(this::isDeprecatedInRelease) + .collect(Collectors.toList()); + this.out.append(String.format("|======================%n")); + this.out.append(String.format("|Key |Replacement |Reason%n")); + properties.forEach((diff) -> { + ConfigurationMetadataProperty property = diff.right(); + writeDeprecatedProperty(property); + }); + this.out.append(String.format("|======================%n")); + } + this.out.append(String.format("%n%n")); + } + + private boolean isDeprecatedInRelease(Difference difference) { + return difference.right().getDeprecation() != null + && Deprecation.Level.ERROR != difference.right().getDeprecation().getLevel(); + } + + private void writeAddedProperties(List differences) { + if (differences.isEmpty()) { + this.out.append(String.format("None.%n")); + } + else { + List properties = sortProperties(differences, Difference::right); + this.out.append(String.format("|======================%n")); + this.out.append(String.format("|Key |Default value |Description%n")); + properties.forEach((diff) -> writeRegularProperty(diff.right())); + this.out.append(String.format("|======================%n")); + } + this.out.append(String.format("%n%n")); + } + + private void writeRemovedProperties(List deleted, List deprecated) { + List removed = getRemovedProperties(deleted, deprecated); + if (removed.isEmpty()) { + this.out.append(String.format("None.%n")); + } + else { + this.out.append(String.format("|======================%n")); + this.out.append(String.format("|Key |Replacement |Reason%n")); + removed.forEach((property) -> writeDeprecatedProperty( + (property.right() != null) ? property.right() : property.left())); + this.out.append(String.format("|======================%n")); + } + } + + private List getRemovedProperties(List deleted, List deprecated) { + List properties = new ArrayList<>(deleted); + properties.addAll(deprecated.stream().filter((p) -> !isDeprecatedInRelease(p)).collect(Collectors.toList())); + return sortProperties(properties, + (difference) -> (difference.left() != null) ? difference.left() : difference.right()); + } + + private void writeRegularProperty(ConfigurationMetadataProperty property) { + this.out.append("|`").append(property.getId()).append("` |"); + if (property.getDefaultValue() != null) { + this.out.append("`").append(defaultValueToString(property.getDefaultValue())).append("`"); + } + this.out.append(" |"); + if (property.getDescription() != null) { + this.out.append(property.getShortDescription()); + } + this.out.append(System.lineSeparator()); + } + + private void writeDeprecatedProperty(ConfigurationMetadataProperty property) { + Deprecation deprecation = (property.getDeprecation() != null) ? property.getDeprecation() : new Deprecation(); + this.out.append("|`").append(property.getId()).append("` |"); + if (deprecation.getReplacement() != null) { + this.out.append("`").append(deprecation.getReplacement()).append("`"); + } + this.out.append(" |"); + if (deprecation.getReason() != null) { + this.out.append(getFirstSentence(deprecation.getReason())); + } + this.out.append(System.lineSeparator()); + } + + private String getFirstSentence(String text) { + int dot = text.indexOf('.'); + if (dot != -1) { + BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US); + breakIterator.setText(text); + String sentence = text.substring(breakIterator.first(), breakIterator.next()).trim(); + return removeSpaceBetweenLine(sentence); + } + else { + String[] lines = text.split(System.lineSeparator()); + return lines[0].trim(); + } + } + + private static String removeSpaceBetweenLine(String text) { + String[] lines = text.split(System.lineSeparator()); + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line.trim()).append(" "); + } + return sb.toString().trim(); + } + + private List sortProperties(List properties, + Function property) { + List sorted = new ArrayList<>(properties); + sorted.sort((o1, o2) -> property.apply(o1).getId().compareTo(property.apply(o2).getId())); + return sorted; + } + + private static String defaultValueToString(Object defaultValue) { + if (defaultValue instanceof Object[]) { + return Stream.of((Object[]) defaultValue).map(Object::toString).collect(Collectors.joining(", ")); + } + else { + return defaultValue.toString(); + } + } + + @Override + public void close() { + this.out.close(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java new file mode 100644 index 000000000000..260c7f95ea4b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.Deprecation.Level; +import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; + +/** + * A diff of two repositories of configuration metadata. + * + * @param leftName the name of the left-hand side of the diff + * @param rightName the name of the right-hand side of the diff + * @param differences the differences + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +record ConfigurationMetadataDiff(String leftName, String rightName, List differences) { + + static ConfigurationMetadataDiff of(NamedConfigurationMetadataRepository left, + NamedConfigurationMetadataRepository right) { + return new ConfigurationMetadataDiff(left.getName(), right.getName(), differences(left, right)); + } + + private static List differences(ConfigurationMetadataRepository left, + ConfigurationMetadataRepository right) { + List differences = new ArrayList<>(); + List matches = new ArrayList<>(); + Map leftProperties = left.getAllProperties(); + Map rightProperties = right.getAllProperties(); + for (ConfigurationMetadataProperty leftProperty : leftProperties.values()) { + String id = leftProperty.getId(); + matches.add(id); + ConfigurationMetadataProperty rightProperty = rightProperties.get(id); + if (rightProperty == null) { + if (!(leftProperty.isDeprecated() && leftProperty.getDeprecation().getLevel() == Level.ERROR)) { + differences.add(new Difference(Type.DELETED, leftProperty, null)); + } + } + else if (rightProperty.isDeprecated() && !leftProperty.isDeprecated()) { + differences.add(new Difference(Type.DEPRECATED, leftProperty, rightProperty)); + } + else if (leftProperty.isDeprecated() && leftProperty.getDeprecation().getLevel() == Level.WARNING + && rightProperty.isDeprecated() && rightProperty.getDeprecation().getLevel() == Level.ERROR) { + differences.add(new Difference(Type.DELETED, leftProperty, rightProperty)); + } + } + for (ConfigurationMetadataProperty rightProperty : rightProperties.values()) { + if ((!matches.contains(rightProperty.getId())) && (!rightProperty.isDeprecated())) { + differences.add(new Difference(Type.ADDED, null, rightProperty)); + } + } + return differences; + } + + /** + * A difference in the metadata. + * + * @param type the type of the difference + * @param left the left-hand side of the difference + * @param right the right-hand side of the difference + */ + static record Difference(Type type, ConfigurationMetadataProperty left, ConfigurationMetadataProperty right) { + + /** + * The type of a difference in the metadata. + */ + enum Type { + + /** + * The entry has been added. + */ + ADDED, + + /** + * The entry has been made deprecated. It may or may not still exist in the + * previous version. + */ + DEPRECATED, + + /** + * The entry has been deleted. + */ + DELETED + + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java new file mode 100644 index 000000000000..51ec5535e33e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataGroup; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; + +/** + * A {@link ConfigurationMetadataRepository} with a name. + * + * @author Andy Wilkinson + */ +class NamedConfigurationMetadataRepository implements ConfigurationMetadataRepository { + + private final String name; + + private final ConfigurationMetadataRepository delegate; + + NamedConfigurationMetadataRepository(String name, ConfigurationMetadataRepository delegate) { + this.name = name; + this.delegate = delegate; + } + + /** + * The name of the metadata held in the repository. + * @return the name of the metadata + */ + String getName() { + return this.name; + } + + @Override + public Map getAllGroups() { + return this.delegate.getAllGroups(); + } + + @Override + public Map getAllProperties() { + return this.delegate.getAllProperties(); + } + + static NamedConfigurationMetadataRepository from(File metadataDir) { + ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create(); + for (File jar : metadataDir.listFiles()) { + try (JarFile jarFile = new JarFile(jar)) { + JarEntry jsonMetadata = jarFile.getJarEntry("META-INF/spring-configuration-metadata.json"); + if (jsonMetadata != null) { + builder.withJsonResource(jarFile.getInputStream(jsonMetadata)); + } + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + return new NamedConfigurationMetadataRepository(metadataDir.getName(), builder.build()); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java new file mode 100644 index 000000000000..96eca3173f88 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 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. + */ + +/** + * Spring Boot configuration metadata changelog generator. + */ +package org.springframework.boot.configurationmetadata.changelog; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java new file mode 100644 index 000000000000..787184a93f43 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; +import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference; +import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfigurationMetadataDiff}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class ConfigurationMetadataDiffTests { + + @Test + void diffContainsDifferencesBetweenLeftAndRightInputs() { + NamedConfigurationMetadataRepository left = new NamedConfigurationMetadataRepository("1.0", + load("sample-1.0.json")); + NamedConfigurationMetadataRepository right = new NamedConfigurationMetadataRepository("2.0", + load("sample-2.0.json")); + ConfigurationMetadataDiff diff = ConfigurationMetadataDiff.of(left, right); + assertThat(diff).isNotNull(); + assertThat(diff.leftName()).isEqualTo("1.0"); + assertThat(diff.rightName()).isEqualTo("2.0"); + assertThat(diff.differences()).hasSize(4); + List added = diff.differences() + .stream() + .filter((difference) -> difference.type() == Type.ADDED) + .collect(Collectors.toList()); + assertThat(added).hasSize(1); + assertProperty(added.get(0).right(), "test.add", String.class, "new"); + List deleted = diff.differences() + .stream() + .filter((difference) -> difference.type() == Type.DELETED) + .collect(Collectors.toList()); + assertThat(deleted).hasSize(2) + .anySatisfy((entry) -> assertProperty(entry.left(), "test.delete", String.class, "delete")) + .anySatisfy((entry) -> assertProperty(entry.right(), "test.delete.deprecated", String.class, "delete")); + List deprecated = diff.differences() + .stream() + .filter((difference) -> difference.type() == Type.DEPRECATED) + .collect(Collectors.toList()); + assertThat(deprecated).hasSize(1); + assertProperty(deprecated.get(0).left(), "test.deprecate", String.class, "wrong"); + assertProperty(deprecated.get(0).right(), "test.deprecate", String.class, "wrong"); + } + + private void assertProperty(ConfigurationMetadataProperty property, String id, Class type, Object defaultValue) { + assertThat(property).isNotNull(); + assertThat(property.getId()).isEqualTo(id); + assertThat(property.getType()).isEqualTo(type.getName()); + assertThat(property.getDefaultValue()).isEqualTo(defaultValue); + } + + private ConfigurationMetadataRepository load(String filename) { + try (InputStream inputStream = new FileInputStream("src/test/resources/" + filename)) { + return ConfigurationMetadataRepositoryJsonBuilder.create(inputStream).build(); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json new file mode 100644 index 000000000000..a0584bc5695b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json @@ -0,0 +1,31 @@ +{ + "properties": [ + { + "name": "test.equal", + "type": "java.lang.String", + "description": "Test equality.", + "defaultValue": "test" + }, + { + "name": "test.deprecate", + "type": "java.lang.String", + "description": "Test deprecate.", + "defaultValue": "wrong" + }, + { + "name": "test.delete", + "type": "java.lang.String", + "description": "Test delete.", + "defaultValue": "delete" + }, + { + "name": "test.delete.deprecated", + "type": "java.lang.String", + "description": "Test delete deprecated.", + "defaultValue": "delete", + "deprecation": { + "level": "warning" + } + } + ] +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json new file mode 100644 index 000000000000..2de71ca99e57 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json @@ -0,0 +1,34 @@ +{ + "properties": [ + { + "name": "test.add", + "type": "java.lang.String", + "description": "Test add.", + "defaultValue": "new" + }, + { + "name": "test.equal", + "type": "java.lang.String", + "description": "Test equality.", + "defaultValue": "test" + }, + { + "name": "test.deprecate", + "type": "java.lang.String", + "description": "Test deprecate.", + "defaultValue": "wrong", + "deprecation": { + "level": "error" + } + }, + { + "name": "test.delete.deprecated", + "type": "java.lang.String", + "description": "Test delete deprecated.", + "defaultValue": "delete", + "deprecation": { + "level": "error" + } + } + ] +} \ No newline at end of file diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 9062ad5e2b4f..3f7d8b5e0655 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -70,6 +70,7 @@ + From 849f65a0def0a8b5c28d4101690f0cf81ce74e4c Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 30 Jun 2023 13:47:04 +0200 Subject: [PATCH 0073/1656] Revert "Apply filter order to ServerHttpObservationFilter" This reverts commit efcc65bc5bb2870868cb43e540252cd87fa42a4a. --- .../servlet/WebMvcObservationAutoConfiguration.java | 4 ++-- .../WebMvcObservationAutoConfigurationTests.java | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java index e6f1c487def1..2b4aa96c3933 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfiguration.java @@ -40,6 +40,7 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.server.observation.DefaultServerRequestObservationConvention; import org.springframework.http.server.observation.ServerRequestObservationConvention; @@ -53,7 +54,6 @@ * @author Brian Clozel * @author Jon Schneider * @author Dmytro Nosan - * @author Moritz Halbritter * @since 3.0.0 */ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, @@ -74,7 +74,7 @@ public FilterRegistrationBean webMvcObservationFilt .getIfAvailable(() -> new DefaultServerRequestObservationConvention(name)); ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention); FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); - registration.setOrder(observationProperties.getHttp().getServer().getFilter().getOrder()); + registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC); return registration; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java index b996ba206430..3fd1a2b61bef 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/servlet/WebMvcObservationAutoConfigurationTests.java @@ -57,7 +57,6 @@ * @author Tadaya Tsuyukubo * @author Madhura Bhave * @author Chanhyeong LEE - * @author Moritz Halbritter */ @ExtendWith(OutputCaptureExtension.class) class WebMvcObservationAutoConfigurationTests { @@ -101,15 +100,6 @@ void filterRegistrationHasExpectedDispatcherTypesAndOrder() { }); } - @Test - void filterRegistrationOrderCanBeOverridden() { - this.contextRunner.withPropertyValues("management.observations.http.server.filter.order=1000") - .run((context) -> { - FilterRegistrationBean registration = context.getBean(FilterRegistrationBean.class); - assertThat(registration.getOrder()).isEqualTo(1000); - }); - } - @Test void filterRegistrationBacksOffWithAnotherServerHttpObservationFilterRegistration() { this.contextRunner.withUserConfiguration(TestServerHttpObservationFilterRegistrationConfiguration.class) From b4bc7cebbc54783ab99f9fbcc7444eda6ce7c297 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 30 Jun 2023 14:00:38 +0200 Subject: [PATCH 0074/1656] Revert "Add property to specify the order of ServerHttpObservationFilter" This reverts commit 7b90fbb0b2ef73871ca776d9f999e9cdc1cb7e6d. --- .../observation/ObservationProperties.java | 24 ---------- .../OrderedServerHttpObservationFilter.java | 47 ------------------- .../WebFluxObservationAutoConfiguration.java | 8 ++-- ...FluxObservationAutoConfigurationTests.java | 10 ---- 4 files changed, 4 insertions(+), 85 deletions(-) delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java index 24d81daa4b1a..08de1a01c104 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java @@ -20,7 +20,6 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.Ordered; /** * {@link ConfigurationProperties @ConfigurationProperties} for configuring Micrometer @@ -111,16 +110,10 @@ public static class Server { private final ServerRequests requests = new ServerRequests(); - private final Filter filter = new Filter(); - public ServerRequests getRequests() { return this.requests; } - public Filter getFilter() { - return this.filter; - } - public static class ServerRequests { /** @@ -138,23 +131,6 @@ public void setName(String name) { } - public static class Filter { - - /** - * Order of the filter that creates the observations. - */ - private int order = Ordered.HIGHEST_PRECEDENCE + 1; - - public int getOrder() { - return this.order; - } - - public void setOrder(int order) { - this.order = order; - } - - } - } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java deleted file mode 100644 index 4541f6e16521..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/OrderedServerHttpObservationFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2012-2023 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.actuate.autoconfigure.observation.web.reactive; - -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.boot.web.reactive.filter.OrderedWebFilter; -import org.springframework.core.Ordered; -import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; -import org.springframework.web.filter.reactive.ServerHttpObservationFilter; - -/** - * {@link ServerHttpObservationFilter} that also implements {@link Ordered}. - * - * @author Moritz Halbritter - */ -@SuppressWarnings({ "deprecation", "removal" }) -class OrderedServerHttpObservationFilter extends ServerHttpObservationFilter implements OrderedWebFilter { - - private final int order; - - OrderedServerHttpObservationFilter(ObservationRegistry observationRegistry, - ServerRequestObservationConvention observationConvention, int order) { - super(observationRegistry, observationConvention); - this.order = order; - } - - @Override - public int getOrder() { - return this.order; - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java index 457d2cdf3937..91b1d6fa95aa 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention; import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; @@ -50,7 +51,6 @@ * @author Brian Clozel * @author Jon Schneider * @author Dmytro Nosan - * @author Moritz Halbritter * @since 3.0.0 */ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, @@ -70,13 +70,13 @@ public WebFluxObservationAutoConfiguration(ObservationProperties observationProp @Bean @ConditionalOnMissingBean(ServerHttpObservationFilter.class) - public OrderedServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry, + @Order(Ordered.HIGHEST_PRECEDENCE + 1) + public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry, ObjectProvider customConvention) { String name = this.observationProperties.getHttp().getServer().getRequests().getName(); ServerRequestObservationConvention convention = customConvention .getIfAvailable(() -> new DefaultServerRequestObservationConvention(name)); - int order = this.observationProperties.getHttp().getServer().getFilter().getOrder(); - return new OrderedServerHttpObservationFilter(registry, convention, order); + return new ServerHttpObservationFilter(registry, convention); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java index 939b754424a8..48c065660fa0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/reactive/WebFluxObservationAutoConfigurationTests.java @@ -52,7 +52,6 @@ * @author Brian Clozel * @author Dmytro Nosan * @author Madhura Bhave - * @author Moritz Halbritter */ @ExtendWith(OutputCaptureExtension.class) @SuppressWarnings("removal") @@ -128,15 +127,6 @@ void shouldNotDenyNorLogIfMaxUrisIsNotReached(CapturedOutput output) { }); } - @Test - void shouldUsePropertyForServerHttpObservationFilterOrder() { - this.contextRunner.withPropertyValues("management.observations.http.server.filter.order=1000") - .run((context) -> { - OrderedServerHttpObservationFilter bean = context.getBean(OrderedServerHttpObservationFilter.class); - assertThat(bean.getOrder()).isEqualTo(1000); - }); - } - private MeterRegistry getInitializedMeterRegistry(AssertableReactiveWebApplicationContext context) throws Exception { return getInitializedMeterRegistry(context, "/test0", "/test1", "/test2"); From 298bfd96c2b9c14d7443570e73e7d5c4273ca6a8 Mon Sep 17 00:00:00 2001 From: Ahmed Ashour Date: Wed, 28 Jun 2023 12:39:54 +0000 Subject: [PATCH 0075/1656] Change WebServer log messages to use port or ports, not port(s) See gh-36103 --- .../boot/build/docs/ApplicationRunner.java | 4 ++-- .../endpoint/web/documentation/sample.log | 4 ++-- .../boot/rsocket/netty/NettyRSocketServer.java | 2 +- .../web/embedded/jetty/JettyWebServer.java | 14 +++++++++++--- .../web/embedded/tomcat/TomcatWebServer.java | 18 +++++++++++------- .../embedded/undertow/UndertowWebServer.java | 16 +++++++++++++--- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java b/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java index 0f95d55d3d67..785be772544b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/docs/ApplicationRunner.java @@ -105,8 +105,8 @@ public Property getApplicationJar() { } public void normalizeTomcatPort() { - this.normalizations.put("(Tomcat started on port\\(s\\): )[\\d]+( \\(http\\))", "$18080$2"); - this.normalizations.put("(Tomcat initialized with port\\(s\\): )[\\d]+( \\(http\\))", "$18080$2"); + this.normalizations.put("(Tomcat started on port )[\\d]+( \\(http\\))", "$18080$2"); + this.normalizations.put("(Tomcat initialized with port )[\\d]+( \\(http\\))", "$18080$2"); } public void normalizeLiveReloadPort() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log index 9b8f1ab7eced..b1f92c2d2c35 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log @@ -9,7 +9,7 @@ 2017-08-08 17:12:30.910 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : Starting SampleWebFreeMarkerApplication with PID 19866 2017-08-08 17:12:30.913 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : No active profile set, falling back to default profiles: default 2017-08-08 17:12:30.952 INFO 19866 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@76b10754: startup date [Tue Aug 08 17:12:30 BST 2017]; root of context hierarchy -2017-08-08 17:12:31.878 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) +2017-08-08 17:12:31.878 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2017-08-08 17:12:31.889 INFO 19866 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2017-08-08 17:12:31.890 INFO 19866 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.16 2017-08-08 17:12:31.978 INFO 19866 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext @@ -27,5 +27,5 @@ 2017-08-08 17:12:32.471 INFO 19866 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2017-08-08 17:12:32.600 INFO 19866 --- [ main] o.s.w.s.v.f.FreeMarkerConfigurer : ClassTemplateLoader for Spring macros added to FreeMarker configuration 2017-08-08 17:12:32.681 INFO 19866 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup -2017-08-08 17:12:32.744 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) +2017-08-08 17:12:32.744 INFO 19866 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) 2017-08-08 17:12:32.750 INFO 19866 --- [ main] s.f.SampleWebFreeMarkerApplication : Started SampleWebFreeMarkerApplication in 2.172 seconds (JVM running for 2.479) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/netty/NettyRSocketServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/netty/NettyRSocketServer.java index cb2df6779f62..f50f847346dd 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/netty/NettyRSocketServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/netty/NettyRSocketServer.java @@ -62,7 +62,7 @@ public InetSocketAddress address() { @Override public void start() throws RSocketServerException { this.channel = block(this.starter, this.lifecycleTimeout); - logger.info("Netty RSocket started on port(s): " + address().getPort()); + logger.info("Netty RSocket started on port " + address().getPort()); startDaemonAwaitThread(this.channel); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java index 21f1052e6ed2..cd0951ce994a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java @@ -168,7 +168,7 @@ public void start() throws WebServerException { } } this.started = true; - logger.info("Jetty started on port(s) " + getActualPortsDescription() + " with context path '" + logger.info("Jetty started on " + getActualPortsDescription() + " with context path '" + getContextPath() + "'"); } catch (WebServerException ex) { @@ -184,10 +184,18 @@ public void start() throws WebServerException { private String getActualPortsDescription() { StringBuilder ports = new StringBuilder(); - for (Connector connector : this.server.getConnectors()) { - if (ports.length() != 0) { + Connector[] connectors = this.server.getConnectors(); + ports.append("port"); + if (connectors.length != 1) { + ports.append("s"); + } + ports.append(" "); + + for (int i = 0; i < connectors.length; i++) { + if (i != 0) { ports.append(", "); } + Connector connector = connectors[i]; ports.append(getLocalPort(connector)).append(getProtocols(connector)); } return ports.toString(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java index c2dfde03e9e7..7319f620365d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java @@ -105,7 +105,7 @@ public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { } private void initialize() throws WebServerException { - logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); + logger.info("Tomcat initialized with " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); @@ -218,8 +218,8 @@ public void start() throws WebServerException { } checkThatConnectorsHaveStarted(); this.started = true; - logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '" - + getContextPath() + "'"); + logger.info("Tomcat started on " + getPortsDescription(true) + " with context path '" + getContextPath() + + "'"); } catch (ConnectorStartFailedException ex) { stopSilently(); @@ -356,10 +356,14 @@ public void destroy() throws WebServerException { private String getPortsDescription(boolean localPort) { StringBuilder ports = new StringBuilder(); - for (Connector connector : this.tomcat.getService().findConnectors()) { - if (ports.length() != 0) { - ports.append(' '); - } + Connector[] connectors = this.tomcat.getService().findConnectors(); + ports.append("port"); + if (connectors.length != 1) { + ports.append("s"); + } + + for (Connector connector : connectors) { + ports.append(' '); int port = localPort ? connector.getLocalPort() : connector.getPort(); ports.append(port).append(" (").append(connector.getScheme()).append(')'); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java index 563b2fc4b197..4ac2c5b897ee 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java @@ -182,11 +182,21 @@ protected HttpHandler createHttpHandler() { } private String getPortsDescription() { + StringBuilder portsDescription = new StringBuilder(); List ports = getActualPorts(); + portsDescription.append("port"); + if (ports.size() != 1) { + portsDescription.append("s"); + } + portsDescription.append(" "); + if (!ports.isEmpty()) { - return StringUtils.collectionToDelimitedString(ports, " "); + portsDescription.append(StringUtils.collectionToDelimitedString(ports, " ")); + } + else { + portsDescription.append("unknown"); } - return "unknown"; + return portsDescription.toString(); } private List getActualPorts() { @@ -315,7 +325,7 @@ private void notifyGracefulCallback(boolean success) { } protected String getStartLogMessage() { - return "Undertow started on port(s) " + getPortsDescription(); + return "Undertow started on " + getPortsDescription(); } /** From 318198ae5d7a1a22846ad3b00028c393b10fff26 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 30 Jun 2023 19:41:14 +0100 Subject: [PATCH 0076/1656] Polish "Change WebServer log messages to use port or ports, not port(s)" See gh-36103 --- .../web/embedded/jetty/JettyWebServer.java | 21 +++++++------ .../web/embedded/netty/NettyWebServer.java | 10 ++++-- .../web/embedded/tomcat/TomcatWebServer.java | 26 ++++++++++------ .../undertow/UndertowServletWebServer.java | 13 +++++--- .../embedded/undertow/UndertowWebServer.java | 15 +++++---- .../JettyReactiveWebServerFactoryTests.java | 15 +++++++++ .../JettyServletWebServerFactoryTests.java | 5 +++ .../NettyReactiveWebServerFactoryTests.java | 17 ++++++++++ .../TomcatReactiveWebServerFactoryTests.java | 12 +++++++ .../TomcatServletWebServerFactoryTests.java | 5 +++ ...UndertowReactiveWebServerFactoryTests.java | 12 +++++++ .../UndertowServletWebServerFactoryTests.java | 5 +++ ...AbstractReactiveWebServerFactoryTests.java | 23 ++++++++++++++ .../AbstractServletWebServerFactoryTests.java | 31 +++++++++++++++++++ 14 files changed, 174 insertions(+), 36 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java index cd0951ce994a..f85210163754 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java @@ -168,8 +168,7 @@ public void start() throws WebServerException { } } this.started = true; - logger.info("Jetty started on " + getActualPortsDescription() + " with context path '" - + getContextPath() + "'"); + logger.info(getStartedLogMessage()); } catch (WebServerException ex) { stopSilently(); @@ -182,23 +181,25 @@ public void start() throws WebServerException { } } + String getStartedLogMessage() { + return "Jetty started on " + getActualPortsDescription() + " with context path '" + getContextPath() + "'"; + } + private String getActualPortsDescription() { - StringBuilder ports = new StringBuilder(); + StringBuilder description = new StringBuilder("port"); Connector[] connectors = this.server.getConnectors(); - ports.append("port"); if (connectors.length != 1) { - ports.append("s"); + description.append("s"); } - ports.append(" "); - + description.append(" "); for (int i = 0; i < connectors.length; i++) { if (i != 0) { - ports.append(", "); + description.append(", "); } Connector connector = connectors[i]; - ports.append(getLocalPort(connector)).append(getProtocols(connector)); + description.append(getLocalPort(connector)).append(getProtocols(connector)); } - return ports.toString(); + return description.toString(); } private String getProtocols(Connector connector) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java index 8b555f350f75..f21f47a70081 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -108,7 +108,7 @@ public void start() throws WebServerException { throw new WebServerException("Unable to start Netty", ex); } if (this.disposableServer != null) { - logger.info("Netty started" + getStartedOnMessage(this.disposableServer)); + logger.info(getStartedOnMessage(this.disposableServer)); } startDaemonAwaitThread(this.disposableServer); } @@ -118,7 +118,11 @@ private String getStartedOnMessage(DisposableServer server) { StringBuilder message = new StringBuilder(); tryAppend(message, "port %s", server::port); tryAppend(message, "path %s", server::path); - return (message.length() > 0) ? " on " + message : ""; + return (message.length() > 0) ? "Netty started on " + message : "Netty started"; + } + + protected String getStartedLogMessage() { + return getStartedOnMessage(this.disposableServer); } private void tryAppend(StringBuilder message, String format, Supplier supplier) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java index 7319f620365d..86f3a0ff2c46 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java @@ -218,8 +218,7 @@ public void start() throws WebServerException { } checkThatConnectorsHaveStarted(); this.started = true; - logger.info("Tomcat started on " + getPortsDescription(true) + " with context path '" + getContextPath() - + "'"); + logger.info(getStartedLogMessage()); } catch (ConnectorStartFailedException ex) { stopSilently(); @@ -236,6 +235,10 @@ public void start() throws WebServerException { } } + String getStartedLogMessage() { + return "Tomcat started on " + getPortsDescription(true) + " with context path '" + getContextPath() + "'"; + } + private void checkThatConnectorsHaveStarted() { checkConnectorHasStarted(this.tomcat.getConnector()); for (Connector connector : this.tomcat.getService().findConnectors()) { @@ -355,19 +358,22 @@ public void destroy() throws WebServerException { } private String getPortsDescription(boolean localPort) { - StringBuilder ports = new StringBuilder(); + StringBuilder description = new StringBuilder(); Connector[] connectors = this.tomcat.getService().findConnectors(); - ports.append("port"); + description.append("port"); if (connectors.length != 1) { - ports.append("s"); + description.append("s"); } - - for (Connector connector : connectors) { - ports.append(' '); + description.append(" "); + for (int i = 0; i < connectors.length; i++) { + if (i != 0) { + description.append(", "); + } + Connector connector = connectors[i]; int port = localPort ? connector.getLocalPort() : connector.getPort(); - ports.append(port).append(" (").append(connector.getScheme()).append(')'); + description.append(port).append(" (").append(connector.getScheme()).append(')'); } - return ports.toString(); + return description.toString(); } @Override diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java index 11ce72755bae..3a0a644d2459 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -78,11 +78,14 @@ protected HttpHandler createHttpHandler() { @Override protected String getStartLogMessage() { - String message = super.getStartLogMessage(); - if (StringUtils.hasText(this.contextPath)) { - message += " with context path '" + this.contextPath + "'"; + if (!StringUtils.hasText(this.contextPath)) { + return super.getStartLogMessage(); } - return message; + StringBuilder message = new StringBuilder(super.getStartLogMessage()); + message.append(" with context path '"); + message.append(this.contextPath); + message.append("'"); + return message.toString(); } public DeploymentManager getDeploymentManager() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java index 4ac2c5b897ee..bce6152a7e68 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java @@ -182,21 +182,20 @@ protected HttpHandler createHttpHandler() { } private String getPortsDescription() { - StringBuilder portsDescription = new StringBuilder(); + StringBuilder description = new StringBuilder(); List ports = getActualPorts(); - portsDescription.append("port"); + description.append("port"); if (ports.size() != 1) { - portsDescription.append("s"); + description.append("s"); } - portsDescription.append(" "); - + description.append(" "); if (!ports.isEmpty()) { - portsDescription.append(StringUtils.collectionToDelimitedString(ports, " ")); + description.append(StringUtils.collectionToDelimitedString(ports, ", ")); } else { - portsDescription.append("unknown"); + description.append("unknown"); } - return portsDescription.toString(); + return description.toString(); } private List getActualPorts() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java index cf42ba29d7a4..f8baaa8db5b6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactoryTests.java @@ -31,6 +31,7 @@ import org.mockito.InOrder; import org.springframework.boot.testsupport.web.servlet.Servlet5ClassPathOverrides; +import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests; import org.springframework.boot.web.server.Shutdown; import org.springframework.http.client.reactive.JettyResourceFactory; @@ -161,4 +162,18 @@ void shouldApplyMaxConnections() { assertThat(connectionLimit.getMaxConnections()).isOne(); } + @Override + protected String startedLogMessage() { + return ((JettyWebServer) this.webServer).getStartedLogMessage(); + } + + @Override + protected void addConnector(int port, AbstractReactiveWebServerFactory factory) { + ((JettyReactiveWebServerFactory) factory).addServerCustomizers((server) -> { + ServerConnector connector = new ServerConnector(server); + connector.setPort(port); + server.addConnector(connector); + }); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java index 9dc3ec21c541..560269579c62 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactoryTests.java @@ -531,6 +531,11 @@ void shouldApplyMaxConnections() { assertThat(connectionLimit.getMaxConnections()).isOne(); } + @Override + protected String startedLogMessage() { + return ((JettyWebServer) this.webServer).getStartedLogMessage(); + } + private WebAppContext findWebAppContext(JettyWebServer webServer) { return findWebAppContext(webServer.getServer().getHandler()); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java index 61f123b74bc3..b1e8a318aa1b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java @@ -23,6 +23,7 @@ import io.netty.channel.Channel; import org.awaitility.Awaitility; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.InOrder; import reactor.core.CoreSubscriber; @@ -135,6 +136,12 @@ void whenServerIsShuttingDownGracefullyThenNewConnectionsCannotBeMade() { this.webServer.stop(); } + @Override + @Test + @Disabled("Reactor Netty does not support mutiple ports") + protected void startedLogMessageWithMultiplePorts() { + } + protected Mono testSslWithAlias(String alias) { String keyStore = "classpath:test.jks"; String keyPassword = "password"; @@ -164,6 +171,16 @@ protected NettyReactiveWebServerFactory getFactory() { return new NettyReactiveWebServerFactory(0); } + @Override + protected String startedLogMessage() { + return ((NettyWebServer) this.webServer).getStartedLogMessage(); + } + + @Override + protected void addConnector(int port, AbstractReactiveWebServerFactory factory) { + throw new UnsupportedOperationException("Reactor Netty does not support multiple ports"); + } + static class NoPortNettyReactiveWebServerFactory extends NettyReactiveWebServerFactory { NoPortNettyReactiveWebServerFactory(int port) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java index 4bbea8b0d41b..aaa1170c62ff 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java @@ -275,4 +275,16 @@ private void handleExceptionCausedByBlockedPortOnPrimaryConnector(RuntimeExcepti assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort); } + @Override + protected String startedLogMessage() { + return ((TomcatWebServer) this.webServer).getStartedLogMessage(); + } + + @Override + protected void addConnector(int port, AbstractReactiveWebServerFactory factory) { + Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + connector.setPort(port); + ((TomcatReactiveWebServerFactory) factory).addAdditionalTomcatConnectors(connector); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java index ba8c07b6c0e9..e619c9120ccd 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java @@ -689,4 +689,9 @@ protected void handleExceptionCausedByBlockedPortOnSecondaryConnector(RuntimeExc assertThat(((ConnectorStartFailedException) ex).getPort()).isEqualTo(blockedPort); } + @Override + protected String startedLogMessage() { + return ((TomcatWebServer) this.webServer).getStartedLogMessage(); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactoryTests.java index 836532109fff..8b607e228a97 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactoryTests.java @@ -27,6 +27,7 @@ import org.mockito.InOrder; import reactor.core.publisher.Mono; +import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests; import org.springframework.boot.web.server.Shutdown; import org.springframework.http.MediaType; @@ -155,4 +156,15 @@ private void awaitFile(File file) { Awaitility.waitAtMost(Duration.ofSeconds(10)).until(file::exists, is(true)); } + @Override + protected String startedLogMessage() { + return ((UndertowWebServer) this.webServer).getStartLogMessage(); + } + + @Override + protected void addConnector(int port, AbstractReactiveWebServerFactory factory) { + ((UndertowReactiveWebServerFactory) factory) + .addBuilderCustomizers((builder) -> builder.addHttpListener(port, "0.0.0.0")); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java index f0c5f7d4bd55..48d9aafda094 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java @@ -333,4 +333,9 @@ protected void handleExceptionCausedByBlockedPortOnSecondaryConnector(RuntimeExc handleExceptionCausedByBlockedPortOnPrimaryConnector(ex, blockedPort); } + @Override + protected String startedLogMessage() { + return ((UndertowServletWebServer) this.webServer).getStartLogMessage(); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java index 2fb586580812..509483854ee0 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java @@ -602,6 +602,25 @@ protected void whenHttp2IsEnabledAndSslIsDisabledThenHttp11CanStillBeUsed() { assertThat(result.block(Duration.ofSeconds(30))).isEqualTo("Hello World"); } + @Test + void startedLogMessageWithSinglePort() { + AbstractReactiveWebServerFactory factory = getFactory(); + this.webServer = factory.getWebServer(new EchoHandler()); + this.webServer.start(); + assertThat(startedLogMessage()).matches("(Jetty|Netty|Tomcat|Undertow) started on port " + + this.webServer.getPort() + "( \\(http(/1.1)?\\))?( with context path '(/)?')?"); + } + + @Test + protected void startedLogMessageWithMultiplePorts() { + AbstractReactiveWebServerFactory factory = getFactory(); + addConnector(0, factory); + this.webServer = factory.getWebServer(new EchoHandler()); + this.webServer.start(); + assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on ports " + this.webServer.getPort() + + "( \\(http(/1.1)?\\))?, [0-9]+( \\(http(/1.1)?\\))?( with context path '(/)?')?"); + } + protected WebClient prepareCompressionTest() { Compression compression = new Compression(); compression.setEnabled(true); @@ -673,6 +692,10 @@ protected final void doWithBlockedPort(BlockedPortAction action) throws Exceptio } } + protected abstract String startedLogMessage(); + + protected abstract void addConnector(int port, AbstractReactiveWebServerFactory factory); + public interface BlockedPortAction { void run(int port); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java index c6c53781daf6..0953d225aefe 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java @@ -1336,6 +1336,35 @@ void whenARequestIsActiveAfterGracefulShutdownEndsThenStopWillComplete() throws } } + @Test + void startedLogMessageWithSinglePort() { + AbstractServletWebServerFactory factory = getFactory(); + this.webServer = factory.getWebServer(); + this.webServer.start(); + assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on port " + this.webServer.getPort() + + " \\(http(/1.1)?\\)( with context path '(/)?')?"); + } + + @Test + void startedLogMessageWithSinglePortAndContextPath() { + AbstractServletWebServerFactory factory = getFactory(); + factory.setContextPath("/test"); + this.webServer = factory.getWebServer(); + this.webServer.start(); + assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on port " + this.webServer.getPort() + + " \\(http(/1.1)?\\) with context path '/test'"); + } + + @Test + void startedLogMessageWithMultiplePorts() { + AbstractServletWebServerFactory factory = getFactory(); + addConnector(0, factory); + this.webServer = factory.getWebServer(); + this.webServer.start(); + assertThat(startedLogMessage()).matches("(Jetty|Tomcat|Undertow) started on ports " + this.webServer.getPort() + + " \\(http(/1.1)?\\), [0-9]+ \\(http(/1.1)?\\)( with context path '(/)?')?"); + } + protected Future initiateGetRequest(int port, String path) { return initiateGetRequest(HttpClients.createMinimal(), port, path); } @@ -1584,6 +1613,8 @@ private void loadStore(KeyStore keyStore, Resource resource) } } + protected abstract String startedLogMessage(); + private class TestGzipInputStreamFactory implements InputStreamFactory { private final AtomicBoolean requested = new AtomicBoolean(); From 1bf334ae0fcc33e59d39598ae87e597548fad19c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 30 Jun 2023 22:15:19 +0100 Subject: [PATCH 0077/1656] Polish config metadata changelog generator See gh-21486 --- .../build.gradle | 2 +- .../changelog/Changelog.java | 64 +++++ .../changelog/ChangelogGenerator.java | 79 ++++++ .../changelog/ChangelogWriter.java | 224 ++++++++++++++++++ ...nfigurationMetadataChangelogGenerator.java | 56 ----- .../ConfigurationMetadataChangelogWriter.java | 204 ---------------- .../changelog/ConfigurationMetadataDiff.java | 109 --------- .../changelog/Difference.java | 52 ++++ .../changelog/DifferenceType.java | 42 ++++ .../NamedConfigurationMetadataRepository.java | 80 ------- .../changelog/ChangelogGeneratorTests.java | 68 ++++++ .../changelog/ChangelogTests.java | 73 ++++++ .../changelog/ChangelogWriterTests.java | 45 ++++ .../ConfigurationMetadataDiffTests.java | 92 ------- .../changelog/TestChangelog.java | 52 ++++ .../src/test/resources/sample-2.0.json | 4 +- .../src/test/resources/sample.adoc | 32 +++ 17 files changed, 735 insertions(+), 543 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle index 4d5c517e9040..186a2cff85a1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle @@ -50,7 +50,7 @@ if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { inputs.files(prepareOldMetadata, prepareNewMetadata) outputs.file(project.file("build/configuration-metadata-changelog.adoc")) classpath = sourceSets.main.runtimeClasspath - mainClass = 'org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataChangelogGenerator' + mainClass = 'org.springframework.boot.configurationmetadata.changelog.ChangelogGenerator' if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { args = [project.file("build/configuration-metadata-diff/$oldVersion"), project.file("build/configuration-metadata-diff/$newVersion"), project.file("build/configuration-metadata-changelog.adoc")] } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java new file mode 100644 index 000000000000..964298fe567c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; + +/** + * A changelog containing differences computed from two repositories of configuration + * metadata. + * + * @param oldVersionNumber the name of the old version + * @param newVersionNumber the name of the new version + * @param differences the differences + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +record Changelog(String oldVersionNumber, String newVersionNumber, List differences) { + + static Changelog of(String oldVersionNumber, ConfigurationMetadataRepository oldMetadata, String newVersionNumber, + ConfigurationMetadataRepository newMetadata) { + return new Changelog(oldVersionNumber, newVersionNumber, computeDifferences(oldMetadata, newMetadata)); + } + + static List computeDifferences(ConfigurationMetadataRepository oldMetadata, + ConfigurationMetadataRepository newMetadata) { + List seenIds = new ArrayList<>(); + List differences = new ArrayList<>(); + for (ConfigurationMetadataProperty oldProperty : oldMetadata.getAllProperties().values()) { + String id = oldProperty.getId(); + seenIds.add(id); + ConfigurationMetadataProperty newProperty = newMetadata.getAllProperties().get(id); + Difference difference = Difference.compute(oldProperty, newProperty); + if (difference != null) { + differences.add(difference); + } + } + for (ConfigurationMetadataProperty newProperty : newMetadata.getAllProperties().values()) { + if ((!seenIds.contains(newProperty.getId())) && (!newProperty.isDeprecated())) { + differences.add(new Difference(DifferenceType.ADDED, null, newProperty)); + } + } + return List.copyOf(differences); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java new file mode 100644 index 000000000000..9d1ee1d62282 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.File; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; + +/** + * Generates a configuration metadata changelog. Requires three arguments: + * + *
    + *
  1. The path of a directory containing jar files of the old version + *
  2. The path of a directory containing jar files of the new version + *
  3. The path of a file to which the asciidoc changelog will be written + *
+ * + * The name of each directory will be used as version numbers in generated changelog. + * + * @author Andy Wilkinson + * @author Phillip Webb + * @since 3.2.0 + */ +public final class ChangelogGenerator { + + private ChangelogGenerator() { + } + + public static void main(String[] args) throws IOException { + generate(new File(args[0]), new File(args[1]), new File(args[2])); + } + + private static void generate(File oldDir, File newDir, File out) throws IOException { + String oldVersionNumber = oldDir.getName(); + ConfigurationMetadataRepository oldMetadata = buildRepository(oldDir); + String newVersionNumber = newDir.getName(); + ConfigurationMetadataRepository newMetadata = buildRepository(newDir); + Changelog changelog = Changelog.of(oldVersionNumber, oldMetadata, newVersionNumber, newMetadata); + try (ChangelogWriter writer = new ChangelogWriter(out)) { + writer.write(changelog); + } + System.out.println("%nConfiguration metadata changelog written to '%s'".formatted(out)); + } + + static ConfigurationMetadataRepository buildRepository(File directory) { + ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create(); + for (File file : directory.listFiles()) { + try (JarFile jarFile = new JarFile(file)) { + JarEntry metadataEntry = jarFile.getJarEntry("META-INF/spring-configuration-metadata.json"); + if (metadataEntry != null) { + builder.withJsonResource(jarFile.getInputStream(metadataEntry)); + } + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java new file mode 100644 index 000000000000..fd79845d0ecf --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java @@ -0,0 +1,224 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.text.BreakIterator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation; + +/** + * Writes a {@link Changelog} using asciidoc markup. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +class ChangelogWriter implements AutoCloseable { + + private static final Comparator COMPARING_ID = Comparator + .comparing(ConfigurationMetadataProperty::getId); + + private final PrintWriter out; + + ChangelogWriter(File out) throws IOException { + this(new FileWriter(out)); + } + + ChangelogWriter(Writer out) { + this.out = new PrintWriter(out); + } + + void write(Changelog changelog) { + String oldVersionNumber = changelog.oldVersionNumber(); + String newVersionNumber = changelog.newVersionNumber(); + Map> differencesByType = collateByType(changelog); + write("Configuration property changes between `%s` and `%s`%n", oldVersionNumber, newVersionNumber); + write("%n%n%n== Deprecated in %s%n", newVersionNumber); + writeDeprecated(differencesByType.get(DifferenceType.DEPRECATED)); + write("%n%n%n== Added in %s%n", newVersionNumber); + writeAdded(differencesByType.get(DifferenceType.ADDED)); + write("%n%n%n== Removed in %s%n", newVersionNumber); + writeRemoved(differencesByType.get(DifferenceType.DELETED), differencesByType.get(DifferenceType.DEPRECATED)); + } + + private Map> collateByType(Changelog differences) { + Map> byType = new HashMap<>(); + for (DifferenceType type : DifferenceType.values()) { + byType.put(type, new ArrayList<>()); + } + for (Difference difference : differences.differences()) { + byType.get(difference.type()).add(difference); + } + return byType; + } + + private void writeDeprecated(List differences) { + List rows = sortProperties(differences, Difference::newProperty).stream() + .filter(this::isDeprecatedInRelease) + .collect(Collectors.toList()); + writeTable("| Key | Replacement | Reason", rows, this::writeDeprecated); + } + + private void writeDeprecated(Difference difference) { + writeDeprecatedPropertyRow(difference.newProperty()); + } + + private void writeAdded(List differences) { + List rows = sortProperties(differences, Difference::newProperty); + writeTable("| Key | Default value | Description", rows, this::writeAdded); + } + + private void writeAdded(Difference difference) { + writeRegularPropertyRow(difference.newProperty()); + } + + private void writeRemoved(List deleted, List deprecated) { + List rows = getRemoved(deleted, deprecated); + writeTable("| Key | Replacement | Reason", rows, this::writeRemoved); + } + + private List getRemoved(List deleted, List deprecated) { + List result = new ArrayList<>(deleted); + deprecated.stream().filter(Predicate.not(this::isDeprecatedInRelease)).forEach(result::remove); + return sortProperties(result, + (difference) -> getFirstNonNull(difference, Difference::oldProperty, Difference::newProperty)); + } + + private void writeRemoved(Difference difference) { + writeDeprecatedPropertyRow(getFirstNonNull(difference, Difference::newProperty, Difference::oldProperty)); + } + + private List sortProperties(List differences, + Function extractor) { + return differences.stream().sorted(Comparator.comparing(extractor, COMPARING_ID)).toList(); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private P getFirstNonNull(T t, Function... extractors) { + return Stream.of(extractors) + .map((extractor) -> extractor.apply(t)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + private void writeTable(String header, List rows, Consumer action) { + if (rows.isEmpty()) { + write("_None_.%n"); + } + else { + writeTableBreak(); + write(header + "%n%n"); + for (Iterator iterator = rows.iterator(); iterator.hasNext();) { + action.accept(iterator.next()); + write((!iterator.hasNext()) ? null : "%n"); + } + writeTableBreak(); + } + } + + private void writeTableBreak() { + write("|======================%n"); + } + + private void writeRegularPropertyRow(ConfigurationMetadataProperty property) { + writeCell(monospace(property.getId())); + writeCell(monospace(asString(property.getDefaultValue()))); + writeCell(property.getShortDescription()); + } + + private void writeDeprecatedPropertyRow(ConfigurationMetadataProperty property) { + Deprecation deprecation = (property.getDeprecation() != null) ? property.getDeprecation() : new Deprecation(); + writeCell(monospace(property.getId())); + writeCell(monospace(deprecation.getReplacement())); + writeCell(getFirstSentence(deprecation.getReason())); + } + + private String getFirstSentence(String text) { + if (text == null) { + return null; + } + int dot = text.indexOf('.'); + if (dot != -1) { + BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US); + breakIterator.setText(text); + String sentence = text.substring(breakIterator.first(), breakIterator.next()).trim(); + return removeSpaceBetweenLine(sentence); + } + String[] lines = text.split(System.lineSeparator()); + return lines[0].trim(); + } + + private String removeSpaceBetweenLine(String text) { + String[] lines = text.split(System.lineSeparator()); + return Arrays.stream(lines).map(String::trim).collect(Collectors.joining(" ")); + } + + private boolean isDeprecatedInRelease(Difference difference) { + Deprecation deprecation = difference.newProperty().getDeprecation(); + return (deprecation != null) && (deprecation.getLevel() != Deprecation.Level.ERROR); + } + + private String monospace(String value) { + return (value != null) ? "`%s`".formatted(value) : null; + } + + private void writeCell(String format, Object... args) { + write((format != null) ? "| %s%n".formatted(format) : "|%n", args); + } + + private void write(String format, Object... args) { + if (format != null) { + Object[] strings = Arrays.stream(args).map(this::asString).toArray(); + this.out.append(format.formatted(strings)); + } + } + + private String asString(Object value) { + if (value instanceof Object[] array) { + return Stream.of(array).map(this::asString).collect(Collectors.joining(", ")); + } + return (value != null) ? value.toString() : null; + } + + @Override + public void close() { + this.out.close(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java deleted file mode 100644 index 2eb5b1244ec0..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-2023 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.configurationmetadata.changelog; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -/** - * Generates a configuration metadata changelog. Requires three arguments: - * - *
    - *
  1. The path of a directory containing jar files from which the old metadata will be - * extracted - *
  2. The path of a directory containing jar files from which the new metadata will be - * extracted - *
  3. The path of a file to which the changelog will be written - *
- * - * The name of each directory will be used to name the old and new metadata in the - * generated changelog - * - * @author Andy Wilkinson - */ -final class ConfigurationMetadataChangelogGenerator { - - private ConfigurationMetadataChangelogGenerator() { - - } - - public static void main(String[] args) throws IOException { - ConfigurationMetadataDiff diff = ConfigurationMetadataDiff.of( - NamedConfigurationMetadataRepository.from(new File(args[0])), - NamedConfigurationMetadataRepository.from(new File(args[1]))); - try (ConfigurationMetadataChangelogWriter writer = new ConfigurationMetadataChangelogWriter( - new FileWriter(new File(args[2])))) { - writer.write(diff); - } - System.out.println("\nConfiguration metadata changelog written to '" + args[2] + "'"); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java deleted file mode 100644 index b08b1667d34e..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2012-2023 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.configurationmetadata.changelog; - -import java.io.PrintWriter; -import java.io.Writer; -import java.text.BreakIterator; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.Deprecation; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; - -/** - * Writes a configuration metadata changelog from a {@link ConfigurationMetadataDiff}. - * - * @author Stephane Nicoll - * @author Andy Wilkinson - */ -class ConfigurationMetadataChangelogWriter implements AutoCloseable { - - private final PrintWriter out; - - ConfigurationMetadataChangelogWriter(Writer out) { - this.out = new PrintWriter(out); - } - - void write(ConfigurationMetadataDiff diff) { - this.out.append(String.format("Configuration property changes between `%s` and " + "`%s`%n", diff.leftName(), - diff.rightName())); - this.out.append(System.lineSeparator()); - this.out.append(String.format("== Deprecated in `%s`%n", diff.rightName())); - Map> differencesByType = differencesByType(diff); - writeDeprecatedProperties(differencesByType.get(Type.DEPRECATED)); - this.out.append(System.lineSeparator()); - this.out.append(String.format("== New in `%s`%n", diff.rightName())); - writeAddedProperties(differencesByType.get(Type.ADDED)); - this.out.append(System.lineSeparator()); - this.out.append(String.format("== Removed in `%s`%n", diff.rightName())); - writeRemovedProperties(differencesByType.get(Type.DELETED), differencesByType.get(Type.DEPRECATED)); - } - - private Map> differencesByType(ConfigurationMetadataDiff diff) { - Map> differencesByType = new HashMap<>(); - for (Type type : Type.values()) { - differencesByType.put(type, new ArrayList<>()); - } - for (Difference difference : diff.differences()) { - differencesByType.get(difference.type()).add(difference); - } - return differencesByType; - } - - private void writeDeprecatedProperties(List differences) { - if (differences.isEmpty()) { - this.out.append(String.format("None.%n")); - } - else { - List properties = sortProperties(differences, Difference::right).stream() - .filter(this::isDeprecatedInRelease) - .collect(Collectors.toList()); - this.out.append(String.format("|======================%n")); - this.out.append(String.format("|Key |Replacement |Reason%n")); - properties.forEach((diff) -> { - ConfigurationMetadataProperty property = diff.right(); - writeDeprecatedProperty(property); - }); - this.out.append(String.format("|======================%n")); - } - this.out.append(String.format("%n%n")); - } - - private boolean isDeprecatedInRelease(Difference difference) { - return difference.right().getDeprecation() != null - && Deprecation.Level.ERROR != difference.right().getDeprecation().getLevel(); - } - - private void writeAddedProperties(List differences) { - if (differences.isEmpty()) { - this.out.append(String.format("None.%n")); - } - else { - List properties = sortProperties(differences, Difference::right); - this.out.append(String.format("|======================%n")); - this.out.append(String.format("|Key |Default value |Description%n")); - properties.forEach((diff) -> writeRegularProperty(diff.right())); - this.out.append(String.format("|======================%n")); - } - this.out.append(String.format("%n%n")); - } - - private void writeRemovedProperties(List deleted, List deprecated) { - List removed = getRemovedProperties(deleted, deprecated); - if (removed.isEmpty()) { - this.out.append(String.format("None.%n")); - } - else { - this.out.append(String.format("|======================%n")); - this.out.append(String.format("|Key |Replacement |Reason%n")); - removed.forEach((property) -> writeDeprecatedProperty( - (property.right() != null) ? property.right() : property.left())); - this.out.append(String.format("|======================%n")); - } - } - - private List getRemovedProperties(List deleted, List deprecated) { - List properties = new ArrayList<>(deleted); - properties.addAll(deprecated.stream().filter((p) -> !isDeprecatedInRelease(p)).collect(Collectors.toList())); - return sortProperties(properties, - (difference) -> (difference.left() != null) ? difference.left() : difference.right()); - } - - private void writeRegularProperty(ConfigurationMetadataProperty property) { - this.out.append("|`").append(property.getId()).append("` |"); - if (property.getDefaultValue() != null) { - this.out.append("`").append(defaultValueToString(property.getDefaultValue())).append("`"); - } - this.out.append(" |"); - if (property.getDescription() != null) { - this.out.append(property.getShortDescription()); - } - this.out.append(System.lineSeparator()); - } - - private void writeDeprecatedProperty(ConfigurationMetadataProperty property) { - Deprecation deprecation = (property.getDeprecation() != null) ? property.getDeprecation() : new Deprecation(); - this.out.append("|`").append(property.getId()).append("` |"); - if (deprecation.getReplacement() != null) { - this.out.append("`").append(deprecation.getReplacement()).append("`"); - } - this.out.append(" |"); - if (deprecation.getReason() != null) { - this.out.append(getFirstSentence(deprecation.getReason())); - } - this.out.append(System.lineSeparator()); - } - - private String getFirstSentence(String text) { - int dot = text.indexOf('.'); - if (dot != -1) { - BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US); - breakIterator.setText(text); - String sentence = text.substring(breakIterator.first(), breakIterator.next()).trim(); - return removeSpaceBetweenLine(sentence); - } - else { - String[] lines = text.split(System.lineSeparator()); - return lines[0].trim(); - } - } - - private static String removeSpaceBetweenLine(String text) { - String[] lines = text.split(System.lineSeparator()); - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - sb.append(line.trim()).append(" "); - } - return sb.toString().trim(); - } - - private List sortProperties(List properties, - Function property) { - List sorted = new ArrayList<>(properties); - sorted.sort((o1, o2) -> property.apply(o1).getId().compareTo(property.apply(o2).getId())); - return sorted; - } - - private static String defaultValueToString(Object defaultValue) { - if (defaultValue instanceof Object[]) { - return Stream.of((Object[]) defaultValue).map(Object::toString).collect(Collectors.joining(", ")); - } - else { - return defaultValue.toString(); - } - } - - @Override - public void close() { - this.out.close(); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java deleted file mode 100644 index 260c7f95ea4b..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012-2023 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.configurationmetadata.changelog; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; -import org.springframework.boot.configurationmetadata.Deprecation.Level; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; - -/** - * A diff of two repositories of configuration metadata. - * - * @param leftName the name of the left-hand side of the diff - * @param rightName the name of the right-hand side of the diff - * @param differences the differences - * @author Stephane Nicoll - * @author Andy Wilkinson - */ -record ConfigurationMetadataDiff(String leftName, String rightName, List differences) { - - static ConfigurationMetadataDiff of(NamedConfigurationMetadataRepository left, - NamedConfigurationMetadataRepository right) { - return new ConfigurationMetadataDiff(left.getName(), right.getName(), differences(left, right)); - } - - private static List differences(ConfigurationMetadataRepository left, - ConfigurationMetadataRepository right) { - List differences = new ArrayList<>(); - List matches = new ArrayList<>(); - Map leftProperties = left.getAllProperties(); - Map rightProperties = right.getAllProperties(); - for (ConfigurationMetadataProperty leftProperty : leftProperties.values()) { - String id = leftProperty.getId(); - matches.add(id); - ConfigurationMetadataProperty rightProperty = rightProperties.get(id); - if (rightProperty == null) { - if (!(leftProperty.isDeprecated() && leftProperty.getDeprecation().getLevel() == Level.ERROR)) { - differences.add(new Difference(Type.DELETED, leftProperty, null)); - } - } - else if (rightProperty.isDeprecated() && !leftProperty.isDeprecated()) { - differences.add(new Difference(Type.DEPRECATED, leftProperty, rightProperty)); - } - else if (leftProperty.isDeprecated() && leftProperty.getDeprecation().getLevel() == Level.WARNING - && rightProperty.isDeprecated() && rightProperty.getDeprecation().getLevel() == Level.ERROR) { - differences.add(new Difference(Type.DELETED, leftProperty, rightProperty)); - } - } - for (ConfigurationMetadataProperty rightProperty : rightProperties.values()) { - if ((!matches.contains(rightProperty.getId())) && (!rightProperty.isDeprecated())) { - differences.add(new Difference(Type.ADDED, null, rightProperty)); - } - } - return differences; - } - - /** - * A difference in the metadata. - * - * @param type the type of the difference - * @param left the left-hand side of the difference - * @param right the right-hand side of the difference - */ - static record Difference(Type type, ConfigurationMetadataProperty left, ConfigurationMetadataProperty right) { - - /** - * The type of a difference in the metadata. - */ - enum Type { - - /** - * The entry has been added. - */ - ADDED, - - /** - * The entry has been made deprecated. It may or may not still exist in the - * previous version. - */ - DEPRECATED, - - /** - * The entry has been deleted. - */ - DELETED - - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java new file mode 100644 index 000000000000..8d0fb66cfa7e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation.Level; + +/** + * A difference the metadata. + * + * @param type the type of the difference + * @param oldProperty the old property + * @param newProperty the new property + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +record Difference(DifferenceType type, ConfigurationMetadataProperty oldProperty, + ConfigurationMetadataProperty newProperty) { + + static Difference compute(ConfigurationMetadataProperty oldProperty, ConfigurationMetadataProperty newProperty) { + if (newProperty == null) { + if (!(oldProperty.isDeprecated() && oldProperty.getDeprecation().getLevel() == Level.ERROR)) { + return new Difference(DifferenceType.DELETED, oldProperty, null); + } + return null; + } + if (newProperty.isDeprecated() && !oldProperty.isDeprecated()) { + return new Difference(DifferenceType.DEPRECATED, oldProperty, newProperty); + } + if (oldProperty.isDeprecated() && oldProperty.getDeprecation().getLevel() == Level.WARNING + && newProperty.isDeprecated() && newProperty.getDeprecation().getLevel() == Level.ERROR) { + return new Difference(DifferenceType.DELETED, oldProperty, newProperty); + } + return null; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java new file mode 100644 index 000000000000..b673310b4072 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +/** + * The type of a difference in the metadata. + * + * @author Andy Wilkinson + */ +enum DifferenceType { + + /** + * The entry has been added. + */ + ADDED, + + /** + * The entry has been made deprecated. It may or may not still exist in the previous + * version. + */ + DEPRECATED, + + /** + * The entry has been deleted. + */ + DELETED + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java deleted file mode 100644 index 51ec5535e33e..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2023 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.configurationmetadata.changelog; - -import java.io.File; -import java.io.IOException; -import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataGroup; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; - -/** - * A {@link ConfigurationMetadataRepository} with a name. - * - * @author Andy Wilkinson - */ -class NamedConfigurationMetadataRepository implements ConfigurationMetadataRepository { - - private final String name; - - private final ConfigurationMetadataRepository delegate; - - NamedConfigurationMetadataRepository(String name, ConfigurationMetadataRepository delegate) { - this.name = name; - this.delegate = delegate; - } - - /** - * The name of the metadata held in the repository. - * @return the name of the metadata - */ - String getName() { - return this.name; - } - - @Override - public Map getAllGroups() { - return this.delegate.getAllGroups(); - } - - @Override - public Map getAllProperties() { - return this.delegate.getAllProperties(); - } - - static NamedConfigurationMetadataRepository from(File metadataDir) { - ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create(); - for (File jar : metadataDir.listFiles()) { - try (JarFile jarFile = new JarFile(jar)) { - JarEntry jsonMetadata = jarFile.getJarEntry("META-INF/spring-configuration-metadata.json"); - if (jsonMetadata != null) { - builder.withJsonResource(jarFile.getInputStream(jsonMetadata)); - } - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - return new NamedConfigurationMetadataRepository(metadataDir.getName(), builder.build()); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java new file mode 100644 index 000000000000..efa1760c2736 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ChangelogGenerator}. + * + * @author Phillip Webb + */ +class ChangelogGeneratorTests { + + @TempDir + File temp; + + @Test + void generateChangeLog() throws IOException { + File oldJars = new File(this.temp, "1.0"); + addJar(oldJars, "sample-1.0.json"); + File newJars = new File(this.temp, "2.0"); + addJar(newJars, "sample-2.0.json"); + File out = new File(this.temp, "changes.adoc"); + String[] args = new String[] { oldJars.getAbsolutePath(), newJars.getAbsolutePath(), out.getAbsolutePath() }; + ChangelogGenerator.main(args); + assertThat(out).usingCharset(StandardCharsets.UTF_8) + .hasSameTextualContentAs(new File("src/test/resources/sample.adoc")); + } + + private void addJar(File directory, String filename) throws IOException { + directory.mkdirs(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(new File(directory, "sample.jar")))) { + out.putNextEntry(new ZipEntry("META-INF/spring-configuration-metadata.json")); + try (InputStream in = new FileInputStream("src/test/resources/" + filename)) { + in.transferTo(out); + out.closeEntry(); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java new file mode 100644 index 000000000000..8e8516e5220c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Changelog}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class ChangelogTests { + + @Test + void diffContainsDifferencesBetweenLeftAndRightInputs() { + Changelog differences = TestChangelog.load(); + assertThat(differences).isNotNull(); + assertThat(differences.oldVersionNumber()).isEqualTo("1.0"); + assertThat(differences.newVersionNumber()).isEqualTo("2.0"); + assertThat(differences.differences()).hasSize(4); + List added = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.ADDED) + .collect(Collectors.toList()); + assertThat(added).hasSize(1); + assertProperty(added.get(0).newProperty(), "test.add", String.class, "new"); + List deleted = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.DELETED) + .collect(Collectors.toList()); + assertThat(deleted).hasSize(2) + .anySatisfy((entry) -> assertProperty(entry.oldProperty(), "test.delete", String.class, "delete")) + .anySatisfy( + (entry) -> assertProperty(entry.newProperty(), "test.delete.deprecated", String.class, "delete")); + List deprecated = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.DEPRECATED) + .collect(Collectors.toList()); + assertThat(deprecated).hasSize(1); + assertProperty(deprecated.get(0).oldProperty(), "test.deprecate", String.class, "wrong"); + assertProperty(deprecated.get(0).newProperty(), "test.deprecate", String.class, "wrong"); + } + + private void assertProperty(ConfigurationMetadataProperty property, String id, Class type, Object defaultValue) { + assertThat(property).isNotNull(); + assertThat(property.getId()).isEqualTo(id); + assertThat(property.getType()).isEqualTo(type.getName()); + assertThat(property.getDefaultValue()).isEqualTo(defaultValue); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java new file mode 100644 index 000000000000..5e72e3b567d0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.File; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +import org.assertj.core.util.Files; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ChangelogWriter}. + * + * @author Phillip Webb + */ +class ChangelogWriterTests { + + @Test + void writeChangelog() { + StringWriter out = new StringWriter(); + try (ChangelogWriter writer = new ChangelogWriter(out)) { + writer.write(TestChangelog.load()); + } + String expected = Files.contentOf(new File("src/test/resources/sample.adoc"), StandardCharsets.UTF_8); + assertThat(out).hasToString(expected); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java deleted file mode 100644 index 787184a93f43..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2012-2023 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.configurationmetadata.changelog; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ConfigurationMetadataDiff}. - * - * @author Stephane Nicoll - * @author Andy Wilkinson - */ -class ConfigurationMetadataDiffTests { - - @Test - void diffContainsDifferencesBetweenLeftAndRightInputs() { - NamedConfigurationMetadataRepository left = new NamedConfigurationMetadataRepository("1.0", - load("sample-1.0.json")); - NamedConfigurationMetadataRepository right = new NamedConfigurationMetadataRepository("2.0", - load("sample-2.0.json")); - ConfigurationMetadataDiff diff = ConfigurationMetadataDiff.of(left, right); - assertThat(diff).isNotNull(); - assertThat(diff.leftName()).isEqualTo("1.0"); - assertThat(diff.rightName()).isEqualTo("2.0"); - assertThat(diff.differences()).hasSize(4); - List added = diff.differences() - .stream() - .filter((difference) -> difference.type() == Type.ADDED) - .collect(Collectors.toList()); - assertThat(added).hasSize(1); - assertProperty(added.get(0).right(), "test.add", String.class, "new"); - List deleted = diff.differences() - .stream() - .filter((difference) -> difference.type() == Type.DELETED) - .collect(Collectors.toList()); - assertThat(deleted).hasSize(2) - .anySatisfy((entry) -> assertProperty(entry.left(), "test.delete", String.class, "delete")) - .anySatisfy((entry) -> assertProperty(entry.right(), "test.delete.deprecated", String.class, "delete")); - List deprecated = diff.differences() - .stream() - .filter((difference) -> difference.type() == Type.DEPRECATED) - .collect(Collectors.toList()); - assertThat(deprecated).hasSize(1); - assertProperty(deprecated.get(0).left(), "test.deprecate", String.class, "wrong"); - assertProperty(deprecated.get(0).right(), "test.deprecate", String.class, "wrong"); - } - - private void assertProperty(ConfigurationMetadataProperty property, String id, Class type, Object defaultValue) { - assertThat(property).isNotNull(); - assertThat(property.getId()).isEqualTo(id); - assertThat(property.getType()).isEqualTo(type.getName()); - assertThat(property.getDefaultValue()).isEqualTo(defaultValue); - } - - private ConfigurationMetadataRepository load(String filename) { - try (InputStream inputStream = new FileInputStream("src/test/resources/" + filename)) { - return ConfigurationMetadataRepositoryJsonBuilder.create(inputStream).build(); - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java new file mode 100644 index 000000000000..58a1c34642b7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 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.configurationmetadata.changelog; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; + +/** + * Factory to create test {@link Changelog} instance. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +final class TestChangelog { + + private TestChangelog() { + } + + static Changelog load() { + ConfigurationMetadataRepository previousRepository = load("sample-1.0.json"); + ConfigurationMetadataRepository repository = load("sample-2.0.json"); + return Changelog.of("1.0", previousRepository, "2.0", repository); + } + + private static ConfigurationMetadataRepository load(String filename) { + try (InputStream inputStream = new FileInputStream("src/test/resources/" + filename)) { + return ConfigurationMetadataRepositoryJsonBuilder.create(inputStream).build(); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json index 2de71ca99e57..ef959d39c9eb 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json @@ -27,7 +27,9 @@ "description": "Test delete deprecated.", "defaultValue": "delete", "deprecation": { - "level": "error" + "level": "error", + "replacement": "test.add", + "reason": "it was just bad" } } ] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc new file mode 100644 index 000000000000..ac5cc843e16f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc @@ -0,0 +1,32 @@ +Configuration property changes between `1.0` and `2.0` + + + +== Deprecated in 2.0 +_None_. + + + +== Added in 2.0 +|====================== +| Key | Default value | Description + +| `test.add` +| `new` +| Test add. +|====================== + + + +== Removed in 2.0 +|====================== +| Key | Replacement | Reason + +| `test.delete` +| +| + +| `test.delete.deprecated` +| `test.add` +| it was just bad +|====================== From 7c77e1bb85b94d15b6e33477711a4f2e1ba14cf1 Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Sun, 2 Jul 2023 19:07:40 +0900 Subject: [PATCH 0078/1656] Polish 'Log correlation IDs when Micrometer tracing is being used' See gh-36158 --- ...ogCorrelationEnvironmentPostProcessor.java | 2 +- ...relationEnvironmentPostProcessorTests.java | 4 ++-- .../boot/logging/CorrelationIdFormatter.java | 21 ++++++++++--------- .../logging/log4j2/Log4J2LoggingSystem.java | 3 +-- .../logging/CorrelationIdFormatterTests.java | 2 +- .../logback/LogbackLoggingSystemTests.java | 2 +- .../build.gradle | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java index d7e86849d05e..50a92939f4f5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessor.java @@ -26,7 +26,7 @@ /** * {@link EnvironmentPostProcessor} to add a {@link PropertySource} to support log - * correlation IDs when Micrometer is present. Adds support for the + * correlation IDs when Micrometer Tracing is present. Adds support for the * {@value LoggingSystem#EXPECT_CORRELATION_ID_PROPERTY} property by delegating to * {@code management.tracing.enabled}. * diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java index a75a91a28bd5..4bbfa4a0e8d9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/LogCorrelationEnvironmentPostProcessorTests.java @@ -42,7 +42,7 @@ class LogCorrelationEnvironmentPostProcessorTests { private final LogCorrelationEnvironmentPostProcessor postProcessor = new LogCorrelationEnvironmentPostProcessor(); @Test - void getExpectCorrelationIdPropertyWhenMicrometerPresentReturnsTrue() { + void getExpectCorrelationIdPropertyWhenMicrometerTracingPresentReturnsTrue() { this.postProcessor.postProcessEnvironment(this.environment, this.application); assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) .isTrue(); @@ -50,7 +50,7 @@ void getExpectCorrelationIdPropertyWhenMicrometerPresentReturnsTrue() { @Test @ClassPathExclusions("micrometer-tracing-*.jar") - void getExpectCorrelationIdPropertyWhenMicrometerMissingReturnsFalse() { + void getExpectCorrelationIdPropertyWhenMicrometerTracingMissingReturnsFalse() { this.postProcessor.postProcessEnvironment(this.environment, this.application); assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) .isFalse(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java index 5dcab9f4d503..1388aecca0b6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -36,7 +35,7 @@ /** * Utility class that can be used to format a correlation identifier for logging based on * w3c + * "https://www.w3.org/TR/trace-context/#examples-of-http-traceparent-headers">W3C * recommendations. *

* The formatter can be configured with a comma-separated list of names and the expected @@ -46,8 +45,8 @@ * {@code 16} respectively. *

* Correlation IDs are formatted as dash separated strings surrounded in square brackets. - * Formatted output is always of a fixed width and with trailing whitespace. Dashes are - * omitted if none of the named items can be resolved. + * Formatted output is always of a fixed width and with trailing space. Dashes are omitted + * if none of the named items can be resolved. *

* The following example would return a formatted result of * {@code "[01234567890123456789012345678901-0123456789012345] "}:

@@ -101,10 +100,12 @@ public void formatTo(Function resolver, Appendable appendable) {
 		Predicate canResolve = (part) -> StringUtils.hasLength(resolver.apply(part.name()));
 		try {
 			if (this.parts.stream().anyMatch(canResolve)) {
-				appendable.append("[");
+				appendable.append('[');
 				for (Iterator iterator = this.parts.iterator(); iterator.hasNext();) {
 					appendable.append(iterator.next().resolve(resolver));
-					appendable.append((!iterator.hasNext()) ? "" : "-");
+					if (iterator.hasNext()) {
+						appendable.append('-');
+					}
 				}
 				appendable.append("] ");
 			}
@@ -124,7 +125,7 @@ public String toString() {
 
 	/**
 	 * Create a new {@link CorrelationIdFormatter} instance from the given specification.
-	 * @param spec a comma separated specification
+	 * @param spec a comma-separated specification
 	 * @return a new {@link CorrelationIdFormatter} instance
 	 */
 	public static CorrelationIdFormatter of(String spec) {
@@ -142,7 +143,7 @@ public static CorrelationIdFormatter of(String spec) {
 	 * @return a new {@link CorrelationIdFormatter} instance
 	 */
 	public static CorrelationIdFormatter of(String[] spec) {
-		return of((spec != null) ? Arrays.asList(spec) : Collections.emptyList());
+		return of((spec != null) ? List.of(spec) : Collections.emptyList());
 	}
 
 	/**
@@ -166,7 +167,7 @@ public static CorrelationIdFormatter of(Collection spec) {
 	 */
 	record Part(String name, int length) {
 
-		private static final Pattern pattern = Pattern.compile("^(.+?)\\((\\d+)\\)?$");
+		private static final Pattern pattern = Pattern.compile("^(.+?)\\((\\d+)\\)$");
 
 		String resolve(Function resolver) {
 			String resolved = resolver.apply(name());
@@ -174,7 +175,7 @@ String resolve(Function resolver) {
 				return blank();
 			}
 			int padding = length() - resolved.length();
-			return resolved + " ".repeat((padding > 0) ? padding : 0);
+			return (padding <= 0) ? resolved : resolved + " ".repeat(padding);
 		}
 
 		String blank() {
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java
index da996bfd5c97..10152f88c398 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java
@@ -249,8 +249,7 @@ public void initialize(LoggingInitializationContext initializationContext, Strin
 
 	@Override
 	protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
-		String location = (logFile != null) ? getPackagedConfigFile("log4j2-file.xml")
-				: getPackagedConfigFile("log4j2.xml");
+		String location = getPackagedConfigFile((logFile != null) ? "log4j2-file.xml" : "log4j2.xml");
 		load(initializationContext, location, logFile);
 	}
 
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java
index b34771352a47..2e36d0f00fc0 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/CorrelationIdFormatterTests.java
@@ -96,7 +96,7 @@ void formatToWithDefaultSpec() {
 		context.put("traceId", "01234567890123456789012345678901");
 		context.put("spanId", "0123456789012345");
 		StringBuilder formatted = new StringBuilder();
-		CorrelationIdFormatter.of("").formatTo(context::get, formatted);
+		CorrelationIdFormatter.DEFAULT.formatTo(context::get, formatted);
 		assertThat(formatted).hasToString("[01234567890123456789012345678901-0123456789012345] ");
 	}
 
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java
index 685923ead60c..1baa88001219 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java
@@ -743,7 +743,7 @@ void correlationLoggingToConsoleWhenUsingXmlConfiguration(CapturedOutput output)
 	}
 
 	@Test
-	void correlationLoggingToConsoleWhenUsingFileConfiguration() {
+	void correlationLoggingToFileWhenUsingFileConfiguration() {
 		this.environment.setProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, "true");
 		File file = new File(tmpDir(), "logback-test.log");
 		LogFile logFile = getLogFile(file.getPath(), null);
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle
index 656ad05a752d..c5157df03e02 100644
--- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/build.gradle
@@ -11,7 +11,7 @@ dependencies {
 	implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
 	implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
 	implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-validation"))
-	implementation 'io.micrometer:micrometer-tracing-bridge-brave'
+	implementation("io.micrometer:micrometer-tracing-bridge-brave")
 
 	runtimeOnly("com.h2database:h2")
 

From df107890c7eb6ddbb1ddb24e5b88c8ee95949c7b Mon Sep 17 00:00:00 2001
From: Johnny Lim 
Date: Sun, 2 Jul 2023 19:06:03 +0900
Subject: [PATCH 0079/1656] Fix metadata for logging.include-application-name

See gh-36157
---
 .../META-INF/additional-spring-configuration-metadata.json      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index a22819a934c5..d4bd08721b86 100644
--- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -105,7 +105,7 @@
     },
     {
       "name": "logging.include-application-name",
-      "type": "java.lang.String>",
+      "type": "java.lang.Boolean",
       "description": "Whether to include the application name in the logs.",
       "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
       "defaultValue": true

From dc1d458e64aa9ff0fdb8036b2c6561fe280dc994 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:26:40 +0100
Subject: [PATCH 0080/1656] Start building against Micrometer 1.12.0 snapshots

See gh-36188
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index dac95aae50e5..8f2f7f57e1f1 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -972,7 +972,7 @@ bom {
 			]
 		}
 	}
-	library("Micrometer", "1.11.1") {
+	library("Micrometer", "1.12.0-SNAPSHOT") {
 		group("io.micrometer") {
 			modules = [
 				"micrometer-registry-stackdriver" {

From e847e662c2c215df479d2d3998212fd4f7463e24 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:26:45 +0100
Subject: [PATCH 0081/1656] Start building against Spring Batch 5.1.0 snapshots

See gh-36189
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index 8f2f7f57e1f1..d061f7816e9a 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1386,7 +1386,7 @@ bom {
 			]
 		}
 	}
-	library("Spring Batch", "5.0.2") {
+	library("Spring Batch", "5.1.0-SNAPSHOT") {
 		group("org.springframework.batch") {
 			imports = [
 				"spring-batch-bom"

From ec8e1e2c950632e65d38a50f0e81e4500b301450 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:26:50 +0100
Subject: [PATCH 0082/1656] Start building against Spring Data Bom 2023.1.0
 snapshots

See gh-36190
---
 .../cache/CacheAutoConfigurationTests.java    | 21 ++++++++++++---
 .../MongoDataAutoConfigurationTests.java      | 27 ++++++++++++++-----
 .../spring-boot-dependencies/build.gradle     |  2 +-
 .../connectingusingspringdata/MyBean.kt       |  5 ++--
 4 files changed, 41 insertions(+), 14 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java
index 2c887c890bd2..9a84e99eb3d3 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java
@@ -69,6 +69,7 @@
 import org.springframework.data.couchbase.cache.CouchbaseCache;
 import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration;
 import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
+import org.springframework.data.redis.cache.FixedDurationTtlFunction;
 import org.springframework.data.redis.cache.RedisCacheConfiguration;
 import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -273,7 +274,10 @@ void redisCacheExplicit() {
 				RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class);
 				assertThat(cacheManager.getCacheNames()).isEmpty();
 				RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager);
-				assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofSeconds(15));
+				assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction)
+					.isInstanceOf(FixedDurationTtlFunction.class)
+					.extracting("duration")
+					.isEqualTo(java.time.Duration.ofSeconds(15));
 				assertThat(redisCacheConfiguration.getAllowCacheNullValues()).isFalse();
 				assertThat(redisCacheConfiguration.getKeyPrefixFor("MyCache")).isEqualTo("prefixMyCache::");
 				assertThat(redisCacheConfiguration.usePrefix()).isTrue();
@@ -289,7 +293,10 @@ void redisCacheWithRedisCacheConfiguration() {
 				RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class);
 				assertThat(cacheManager.getCacheNames()).isEmpty();
 				RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager);
-				assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofSeconds(30));
+				assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction)
+					.isInstanceOf(FixedDurationTtlFunction.class)
+					.extracting("duration")
+					.isEqualTo(java.time.Duration.ofSeconds(30));
 				assertThat(redisCacheConfiguration.getKeyPrefixFor("")).isEqualTo("bar::");
 			});
 	}
@@ -301,7 +308,10 @@ void redisCacheWithRedisCacheManagerBuilderCustomizer() {
 			.run((context) -> {
 				RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class);
 				RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager);
-				assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofSeconds(10));
+				assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction)
+					.isInstanceOf(FixedDurationTtlFunction.class)
+					.extracting("duration")
+					.isEqualTo(java.time.Duration.ofSeconds(10));
 			});
 	}
 
@@ -321,7 +331,10 @@ void redisCacheExplicitWithCaches() {
 				RedisCacheManager cacheManager = getCacheManager(context, RedisCacheManager.class);
 				assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar");
 				RedisCacheConfiguration redisCacheConfiguration = getDefaultRedisCacheConfiguration(cacheManager);
-				assertThat(redisCacheConfiguration.getTtl()).isEqualTo(java.time.Duration.ofMinutes(0));
+				assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction)
+					.isInstanceOf(FixedDurationTtlFunction.class)
+					.extracting("duration")
+					.isEqualTo(java.time.Duration.ofSeconds(0));
 				assertThat(redisCacheConfiguration.getAllowCacheNullValues()).isTrue();
 				assertThat(redisCacheConfiguration.getKeyPrefixFor("test")).isEqualTo("test::");
 				assertThat(redisCacheConfiguration.usePrefix()).isTrue();
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java
index 590fb36bdf1f..caff745e56f1 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoDataAutoConfigurationTests.java
@@ -18,10 +18,14 @@
 
 import java.time.LocalDateTime;
 import java.util.Arrays;
+import java.util.function.Supplier;
 
 import com.mongodb.ConnectionString;
 import com.mongodb.client.MongoClient;
 import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.gridfs.GridFSBucket;
+import org.assertj.core.api.InstanceOfAssertFactories;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.beans.factory.BeanCreationException;
@@ -78,32 +82,43 @@ void templateExists() {
 	}
 
 	@Test
+	@SuppressWarnings("unchecked")
 	void whenGridFsDatabaseIsConfiguredThenGridFsTemplateIsAutoConfiguredAndUsesIt() {
 		this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.database:grid").run((context) -> {
 			assertThat(context).hasSingleBean(GridFsTemplate.class);
 			GridFsTemplate template = context.getBean(GridFsTemplate.class);
-			MongoDatabaseFactory factory = (MongoDatabaseFactory) ReflectionTestUtils.getField(template, "dbFactory");
-			assertThat(factory.getMongoDatabase().getName()).isEqualTo("grid");
+			GridFSBucket bucket = ((Supplier) ReflectionTestUtils.getField(template, "bucketSupplier"))
+				.get();
+			assertThat(bucket).extracting("filesCollection", InstanceOfAssertFactories.type(MongoCollection.class))
+				.extracting((collection) -> collection.getNamespace().getDatabaseName())
+				.isEqualTo("grid");
 		});
 	}
 
 	@Test
+	@SuppressWarnings("unchecked")
 	void usesMongoConnectionDetailsIfAvailable() {
 		this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
 			assertThat(context).hasSingleBean(GridFsTemplate.class);
 			GridFsTemplate template = context.getBean(GridFsTemplate.class);
-			assertThat(template).hasFieldOrPropertyWithValue("bucket", "connection-details-bucket");
-			MongoDatabaseFactory factory = (MongoDatabaseFactory) ReflectionTestUtils.getField(template, "dbFactory");
-			assertThat(factory.getMongoDatabase().getName()).isEqualTo("grid-database-1");
+			GridFSBucket bucket = ((Supplier) ReflectionTestUtils.getField(template, "bucketSupplier"))
+				.get();
+			assertThat(bucket.getBucketName()).isEqualTo("connection-details-bucket");
+			assertThat(bucket).extracting("filesCollection", InstanceOfAssertFactories.type(MongoCollection.class))
+				.extracting((collection) -> collection.getNamespace().getDatabaseName())
+				.isEqualTo("grid-database-1");
 		});
 	}
 
 	@Test
+	@SuppressWarnings("unchecked")
 	void whenGridFsBucketIsConfiguredThenGridFsTemplateIsAutoConfiguredAndUsesIt() {
 		this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.bucket:test-bucket").run((context) -> {
 			assertThat(context).hasSingleBean(GridFsTemplate.class);
 			GridFsTemplate template = context.getBean(GridFsTemplate.class);
-			assertThat(template).hasFieldOrPropertyWithValue("bucket", "test-bucket");
+			GridFSBucket bucket = ((Supplier) ReflectionTestUtils.getField(template, "bucketSupplier"))
+				.get();
+			assertThat(bucket.getBucketName()).isEqualTo("test-bucket");
 		});
 	}
 
diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index d061f7816e9a..154b064b6d8a 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1393,7 +1393,7 @@ bom {
 			]
 		}
 	}
-	library("Spring Data Bom", "2023.0.1") {
+	library("Spring Data Bom", "2023.1.0-SNAPSHOT") {
 		group("org.springframework.data") {
 			imports = [
 				"spring-data-bom"
diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt
index 95442da426a2..001ee654ce00 100644
--- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt
+++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/data/nosql/elasticsearch/connectingusingspringdata/MyBean.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2022 the original author or authors.
+ * Copyright 2012-2023 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.
@@ -19,8 +19,7 @@ package org.springframework.boot.docs.data.nosql.elasticsearch.connectingusingsp
 import org.springframework.stereotype.Component
 
 @Component
-@Suppress("DEPRECATION")
-class MyBean(private val template: org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate ) {
+class MyBean(private val template: org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate ) {
 
 	// @fold:on // ...
 	fun someMethod(id: String): Boolean {

From f85ba2a37e260c8c67df3d380207bd612abb6371 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:26:55 +0100
Subject: [PATCH 0083/1656] Start building against Spring GraphQL 1.2.2
 snapshots

See gh-36191
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index 154b064b6d8a..1cff8077aa05 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1407,7 +1407,7 @@ bom {
 			]
 		}
 	}
-	library("Spring GraphQL", "1.2.1") {
+	library("Spring GraphQL", "1.2.2-SNAPSHOT") {
 		group("org.springframework.graphql") {
 			modules = [
 					"spring-graphql",

From c794f52085230fbb1312aad664f743fce317530c Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:27:00 +0100
Subject: [PATCH 0084/1656] Start building against Spring HATEOAS 2.2.0
 snapshots

See gh-36192
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index 1cff8077aa05..a43dd7f2a3e9 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1415,7 +1415,7 @@ bom {
 			]
 		}
 	}
-	library("Spring HATEOAS", "2.1.0") {
+	library("Spring HATEOAS", "2.2.0-SNAPSHOT") {
 		group("org.springframework.hateoas") {
 			modules = [
 				"spring-hateoas"

From 1e0a572dfa2a33c878a26d600a94215fba82758a Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:27:05 +0100
Subject: [PATCH 0085/1656] Start building against Spring Integration 6.2.0
 snapshots

See gh-36193
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index a43dd7f2a3e9..8d2a70c7a69e 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1422,7 +1422,7 @@ bom {
 			]
 		}
 	}
-	library("Spring Integration", "6.1.1") {
+	library("Spring Integration", "6.2.0-SNAPSHOT") {
 		group("org.springframework.integration") {
 			imports = [
 				"spring-integration-bom"

From 32d83551919a13a4aa104147461d5ab22fb52258 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:27:10 +0100
Subject: [PATCH 0086/1656] Start building against Spring Kafka 3.0.9 snapshots

See gh-36194
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index 8d2a70c7a69e..64813b60ab2d 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1429,7 +1429,7 @@ bom {
 			]
 		}
 	}
-	library("Spring Kafka", "3.0.8") {
+	library("Spring Kafka", "3.0.9-SNAPSHOT") {
 		group("org.springframework.kafka") {
 			modules = [
 				"spring-kafka",

From e1b5eb5040e987f5780b000632ff89696e836bd8 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:27:14 +0100
Subject: [PATCH 0087/1656] Start building against Spring Security 6.2.0
 snapshots

See gh-36195
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index 64813b60ab2d..e1842e44ee51 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1461,7 +1461,7 @@ bom {
 			]
 		}
 	}
-	library("Spring Security", "6.1.1") {
+	library("Spring Security", "6.2.0-SNAPSHOT") {
 		group("org.springframework.security") {
 			imports = [
 				"spring-security-bom"

From afdc133d6a1465abca35f4d3c868b05bf528d45d Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:27:19 +0100
Subject: [PATCH 0088/1656] Start building against Spring Session 3.2.0
 snapshots

See gh-36196
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index e1842e44ee51..4af52df9df3d 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -1468,7 +1468,7 @@ bom {
 			]
 		}
 	}
-	library("Spring Session", "3.1.1") {
+	library("Spring Session", "3.2.0-SNAPSHOT") {
 		prohibit {
 			startsWith(["Apple-", "Bean-", "Corn-", "Dragonfruit-"])
 			because "Spring Session switched to numeric version numbers"

From 1fa079d9b5bfdb8a7aeadbf0a35342e365676d3b Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 19:35:06 +0100
Subject: [PATCH 0089/1656] Start building against Micrometer Tracing 1.2.0
 snapshots

See gh-36199
---
 spring-boot-project/spring-boot-dependencies/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle
index 4af52df9df3d..e77b1d30271d 100644
--- a/spring-boot-project/spring-boot-dependencies/build.gradle
+++ b/spring-boot-project/spring-boot-dependencies/build.gradle
@@ -984,7 +984,7 @@ bom {
 			]
 		}
 	}
-	library("Micrometer Tracing", "1.1.2") {
+	library("Micrometer Tracing", "1.2.0-SNAPSHOT") {
 		group("io.micrometer") {
 			imports = [
 				"micrometer-tracing-bom"

From 5a9ca67fbaba2c92c5a9ef0d13f5a7a5025eedba Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Mon, 3 Jul 2023 20:37:25 +0100
Subject: [PATCH 0090/1656] Start building against Spring Framework 6.2.0-M2
 snapshots

See gh-36198
---
 gradle.properties                             |  2 +-
 .../DispatcherServletAutoConfiguration.java   |  8 ++++++-
 .../web/servlet/WebMvcProperties.java         |  9 +++++++-
 ...spatcherServletAutoConfigurationTests.java | 23 ++++++++++++++++---
 .../web/servlet/MockMvcAutoConfiguration.java |  5 ++++
 .../boot/maven/JarIntegrationTests.java       |  3 ++-
 ...tpRequestFactoriesHttpComponentsTests.java |  2 +-
 7 files changed, 44 insertions(+), 8 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index eb9bbe14aff8..d4321ef602e8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,7 +6,7 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
 
 kotlinVersion=1.8.22
 nativeBuildToolsVersion=0.9.23
-springFrameworkVersion=6.1.0-M1
+springFrameworkVersion=6.1.0-SNAPSHOT
 tomcatVersion=10.1.10
 
 kotlin.stdlib.default.dependency=false
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java
index 5575bde20a9c..5aafbfb0091e 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java
@@ -89,12 +89,18 @@ public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
 			DispatcherServlet dispatcherServlet = new DispatcherServlet();
 			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
 			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
-			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
+			configureThrowExceptionIfNoHandlerFound(webMvcProperties, dispatcherServlet);
 			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
 			dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
 			return dispatcherServlet;
 		}
 
+		@SuppressWarnings({ "deprecation", "removal" })
+		private void configureThrowExceptionIfNoHandlerFound(WebMvcProperties webMvcProperties,
+				DispatcherServlet dispatcherServlet) {
+			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
+		}
+
 		@Bean
 		@ConditionalOnBean(MultipartResolver.class)
 		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java
index 0c73524c8f94..2ca9ae03743f 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java
@@ -21,6 +21,7 @@
 import java.util.Map;
 
 import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
 import org.springframework.http.MediaType;
 import org.springframework.util.Assert;
 import org.springframework.validation.DefaultMessageCodesResolver;
@@ -64,8 +65,10 @@ public class WebMvcProperties {
 	/**
 	 * Whether a "NoHandlerFoundException" should be thrown if no Handler was found to
 	 * process a request.
+	 * @deprecated since 3.2.0 for removal in 3.4.0
 	 */
-	private boolean throwExceptionIfNoHandlerFound = false;
+	@Deprecated(since = "3.2.0", forRemoval = true)
+	private boolean throwExceptionIfNoHandlerFound = true;
 
 	/**
 	 * Whether logging of (potentially sensitive) request details at DEBUG and TRACE level
@@ -121,10 +124,14 @@ public void setPublishRequestHandledEvents(boolean publishRequestHandledEvents)
 		this.publishRequestHandledEvents = publishRequestHandledEvents;
 	}
 
+	@Deprecated(since = "3.2.0", forRemoval = true)
+	@DeprecatedConfigurationProperty(
+			reason = "DispatcherServlet property is deprecated for removal and should no longer need to be configured")
 	public boolean isThrowExceptionIfNoHandlerFound() {
 		return this.throwExceptionIfNoHandlerFound;
 	}
 
+	@Deprecated(since = "3.2.0", forRemoval = true)
 	public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) {
 		this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
 	}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java
index 0dbf0eaf0ad6..100d36cdd917 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java
@@ -141,7 +141,6 @@ void renamesMultipartResolver() {
 	void dispatcherServletDefaultConfig() {
 		this.contextRunner.run((context) -> {
 			DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class);
-			assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(false);
 			assertThat(dispatcherServlet).extracting("dispatchOptionsRequest").isEqualTo(true);
 			assertThat(dispatcherServlet).extracting("dispatchTraceRequest").isEqualTo(false);
 			assertThat(dispatcherServlet).extracting("enableLoggingRequestDetails").isEqualTo(false);
@@ -151,15 +150,24 @@ void dispatcherServletDefaultConfig() {
 		});
 	}
 
+	@Test
+	@Deprecated(since = "3.2.0", forRemoval = true)
+	void dispatcherServletThrowExceptionIfNoHandlerFoundDefaultConfig() {
+		this.contextRunner.run((context) -> {
+			DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class);
+			assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(true);
+		});
+	}
+
 	@Test
 	void dispatcherServletCustomConfig() {
 		this.contextRunner
-			.withPropertyValues("spring.mvc.throw-exception-if-no-handler-found:true",
+			.withPropertyValues("spring.mvc.throw-exception-if-no-handler-found:false",
 					"spring.mvc.dispatch-options-request:false", "spring.mvc.dispatch-trace-request:true",
 					"spring.mvc.publish-request-handled-events:false", "spring.mvc.servlet.load-on-startup=5")
 			.run((context) -> {
 				DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class);
-				assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(true);
+				assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(false);
 				assertThat(dispatcherServlet).extracting("dispatchOptionsRequest").isEqualTo(false);
 				assertThat(dispatcherServlet).extracting("dispatchTraceRequest").isEqualTo(true);
 				assertThat(dispatcherServlet).extracting("publishEvents").isEqualTo(false);
@@ -168,6 +176,15 @@ void dispatcherServletCustomConfig() {
 			});
 	}
 
+	@Test
+	@Deprecated(since = "3.2.0", forRemoval = true)
+	void dispatcherServletThrowExceptionIfNoHandlerFoundCustomConfig() {
+		this.contextRunner.withPropertyValues("spring.mvc.throw-exception-if-no-handler-found:false").run((context) -> {
+			DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class);
+			assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(false);
+		});
+	}
+
 	@Configuration(proxyBeanMethods = false)
 	static class MultipartConfiguration {
 
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java
index 6d429eb4fae3..9e944e4fad3f 100644
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java
@@ -131,6 +131,11 @@ private static class MockMvcDispatcherServletCustomizer implements DispatcherSer
 		public void customize(DispatcherServlet dispatcherServlet) {
 			dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
 			dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
+			configureThrowExceptionIfNoHandlerFound(dispatcherServlet);
+		}
+
+		@SuppressWarnings({ "deprecation", "removal" })
+		private void configureThrowExceptionIfNoHandlerFound(DispatcherServlet dispatcherServlet) {
 			dispatcherServlet
 				.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
 		}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java
index 46ad4f7f3a83..07eeaaa70bc7 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java
@@ -428,7 +428,8 @@ private String buildJarWithOutputTimestamp(MavenBuild mavenBuild) {
 	void whenJarIsRepackagedWithOutputTimestampConfiguredThenLibrariesAreSorted(MavenBuild mavenBuild) {
 		mavenBuild.project("jar-output-timestamp").execute((project) -> {
 			File repackaged = new File(project, "target/jar-output-timestamp-0.0.1.BUILD-SNAPSHOT.jar");
-			List sortedLibs = Arrays.asList("BOOT-INF/lib/jakarta.servlet-api", "BOOT-INF/lib/spring-aop",
+			List sortedLibs = Arrays.asList("BOOT-INF/lib/jakarta.servlet-api",
+					"BOOT-INF/lib/micrometer-commons", "BOOT-INF/lib/micrometer-observation", "BOOT-INF/lib/spring-aop",
 					"BOOT-INF/lib/spring-beans", "BOOT-INF/lib/spring-boot-jarmode-layertools",
 					"BOOT-INF/lib/spring-context", "BOOT-INF/lib/spring-core", "BOOT-INF/lib/spring-expression",
 					"BOOT-INF/lib/spring-jcl");
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesHttpComponentsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesHttpComponentsTests.java
index 98b9095afec7..b8df330e7790 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesHttpComponentsTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesHttpComponentsTests.java
@@ -39,7 +39,7 @@ class ClientHttpRequestFactoriesHttpComponentsTests
 
 	@Override
 	protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
-		return (int) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
+		return (long) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
 	}
 
 	@Override

From 9985c845f29d40b5ab335167319acab0d0bc554b Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Tue, 4 Jul 2023 11:59:31 +0100
Subject: [PATCH 0091/1656] Adapt to Framework changes missed due to predictive
 test selection

See gh-36198
---
 .../org/springframework/boot/maven/WarIntegrationTests.java  | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java
index 77fd8842ec71..0a8894cb6bfc 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java
@@ -122,8 +122,9 @@ void whenWarIsRepackagedWithOutputTimestampConfiguredThenLibrariesAreSorted(Mave
 			List sortedLibs = Arrays.asList(
 					// these libraries are copied from the original war, sorted when
 					// packaged by Maven
-					"WEB-INF/lib/spring-aop", "WEB-INF/lib/spring-beans", "WEB-INF/lib/spring-context",
-					"WEB-INF/lib/spring-core", "WEB-INF/lib/spring-expression", "WEB-INF/lib/spring-jcl",
+					"WEB-INF/lib/micrometer-commons", "WEB-INF/lib/micrometer-observation", "WEB-INF/lib/spring-aop",
+					"WEB-INF/lib/spring-beans", "WEB-INF/lib/spring-context", "WEB-INF/lib/spring-core",
+					"WEB-INF/lib/spring-expression", "WEB-INF/lib/spring-jcl",
 					// these libraries are contributed by Spring Boot repackaging, and
 					// sorted separately
 					"WEB-INF/lib/spring-boot-jarmode-layertools");

From 2350d9c870461da229377e7f4426e68d7ba540cb Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Tue, 4 Jul 2023 12:08:27 +0100
Subject: [PATCH 0092/1656] Adapt to Data changes missed due to predictive test
 selection

See gh-36190
---
 ...ngoReactiveDataAutoConfigurationTests.java | 23 +++++++++++++++----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java
index df9a5f264ee2..a64fe5b7031d 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/mongo/MongoReactiveDataAutoConfigurationTests.java
@@ -16,8 +16,13 @@
 
 package org.springframework.boot.autoconfigure.data.mongo;
 
+import java.time.Duration;
+
 import com.mongodb.ConnectionString;
+import com.mongodb.reactivestreams.client.MongoCollection;
+import com.mongodb.reactivestreams.client.gridfs.GridFSBucket;
 import org.junit.jupiter.api.Test;
+import reactor.core.publisher.Mono;
 
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
@@ -68,20 +73,26 @@ void whenGridFsDatabaseIsConfiguredThenGridFsTemplateUsesIt() {
 	}
 
 	@Test
+	@SuppressWarnings("unchecked")
 	void usesMongoConnectionDetailsIfAvailable() {
 		this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
 			assertThat(grisFsTemplateDatabaseName(context)).isEqualTo("grid-database-1");
 			ReactiveGridFsTemplate template = context.getBean(ReactiveGridFsTemplate.class);
-			assertThat(template).hasFieldOrPropertyWithValue("bucket", "connection-details-bucket");
+			GridFSBucket bucket = ((Mono) ReflectionTestUtils.getField(template, "bucketSupplier"))
+				.block(Duration.ofSeconds(30));
+			assertThat(bucket.getBucketName()).isEqualTo("connection-details-bucket");
 		});
 	}
 
 	@Test
+	@SuppressWarnings("unchecked")
 	void whenGridFsBucketIsConfiguredThenGridFsTemplateUsesIt() {
 		this.contextRunner.withPropertyValues("spring.data.mongodb.gridfs.bucket:test-bucket").run((context) -> {
 			assertThat(context).hasSingleBean(ReactiveGridFsTemplate.class);
 			ReactiveGridFsTemplate template = context.getBean(ReactiveGridFsTemplate.class);
-			assertThat(template).hasFieldOrPropertyWithValue("bucket", "test-bucket");
+			GridFSBucket bucket = ((Mono) ReflectionTestUtils.getField(template, "bucketSupplier"))
+				.block(Duration.ofSeconds(30));
+			assertThat(bucket.getBucketName()).isEqualTo("test-bucket");
 		});
 	}
 
@@ -150,12 +161,14 @@ void contextFailsWhenDatabaseNotSet() {
 			.run((context) -> assertThat(context).getFailure().hasMessageContaining("Database name must not be empty"));
 	}
 
+	@SuppressWarnings("unchecked")
 	private String grisFsTemplateDatabaseName(AssertableApplicationContext context) {
 		assertThat(context).hasSingleBean(ReactiveGridFsTemplate.class);
 		ReactiveGridFsTemplate template = context.getBean(ReactiveGridFsTemplate.class);
-		ReactiveMongoDatabaseFactory factory = (ReactiveMongoDatabaseFactory) ReflectionTestUtils.getField(template,
-				"dbFactory");
-		return factory.getMongoDatabase().block().getName();
+		GridFSBucket bucket = ((Mono) ReflectionTestUtils.getField(template, "bucketSupplier"))
+			.block(Duration.ofSeconds(30));
+		MongoCollection collection = (MongoCollection) ReflectionTestUtils.getField(bucket, "filesCollection");
+		return collection.getNamespace().getDatabaseName();
 	}
 
 	@Configuration(proxyBeanMethods = false)

From 4562189125d2894e27aaac122591aac44f2ef1bb Mon Sep 17 00:00:00 2001
From: Laurent Martelli 
Date: Tue, 4 Jul 2023 12:22:57 +0100
Subject: [PATCH 0093/1656] Switch ImportsContextCustomizer to use
 MergedAnnotations.search #36211

Use `MergedAnnotations.search` in `ImportsContextCustomizer` rather than
needing dedicated search logic.

See gh-36211
---
 .../context/ImportsContextCustomizer.java     | 165 ++++--------------
 1 file changed, 35 insertions(+), 130 deletions(-)

diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java
index fc4a8babbf6a..4b8a6a753132 100644
--- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java
+++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java
@@ -16,13 +16,12 @@
 
 package org.springframework.boot.test.context;
 
-import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Constructor;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.BeanFactory;
@@ -42,10 +41,10 @@
 import org.springframework.context.annotation.ImportSelector;
 import org.springframework.context.support.AbstractApplicationContext;
 import org.springframework.core.Ordered;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationFilter;
 import org.springframework.core.annotation.MergedAnnotation;
 import org.springframework.core.annotation.MergedAnnotations;
-import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
 import org.springframework.core.annotation.Order;
 import org.springframework.core.style.ToStringCreator;
 import org.springframework.core.type.AnnotationMetadata;
@@ -53,6 +52,8 @@
 import org.springframework.test.context.MergedContextConfiguration;
 import org.springframework.util.ReflectionUtils;
 
+import static org.springframework.core.annotation.AnnotationFilter.packages;
+
 /**
  * {@link ContextCustomizer} to allow {@code @Import} annotations to be used directly on
  * test classes.
@@ -217,82 +218,41 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t
 	 */
 	static class ContextCustomizerKey {
 
-		private static final Class[] NO_IMPORTS = {};
-
-		private static final Set ANNOTATION_FILTERS;
-
-		static {
-			Set filters = new HashSet<>();
-			filters.add(new JavaLangAnnotationFilter());
-			filters.add(new KotlinAnnotationFilter());
-			filters.add(new SpockAnnotationFilter());
-			filters.add(new JUnitAnnotationFilter());
-			ANNOTATION_FILTERS = Collections.unmodifiableSet(filters);
-		}
+		private static final AnnotationFilter ANNOTATION_FILTERS = or(packages("java.lang.annotation"),
+				packages("org.spockframework", "spock"),
+				or(isEqualTo("kotlin.Metadata"), packages("kotlin.annotation")), packages(("org.junit")));
 
-		private final Set key;
+		private final Object key;
 
 		ContextCustomizerKey(Class testClass) {
-			Set annotations = new HashSet<>();
-			Set> seen = new HashSet<>();
-			collectClassAnnotations(testClass, annotations, seen);
-			Set determinedImports = determineImports(annotations, testClass);
-			this.key = Collections.unmodifiableSet((determinedImports != null) ? determinedImports : annotations);
-		}
-
-		private void collectClassAnnotations(Class classType, Set annotations, Set> seen) {
-			if (seen.add(classType)) {
-				collectElementAnnotations(classType, annotations, seen);
-				for (Class interfaceType : classType.getInterfaces()) {
-					collectClassAnnotations(interfaceType, annotations, seen);
-				}
-				if (classType.getSuperclass() != null) {
-					collectClassAnnotations(classType.getSuperclass(), annotations, seen);
-				}
+			MergedAnnotations mergedAnnotations = MergedAnnotations
+				.search(MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
+				.withAnnotationFilter(ANNOTATION_FILTERS)
+				.from(testClass);
+			Set determinedImports = determineImports(mergedAnnotations, testClass);
+			if (determinedImports != null) {
+				this.key = determinedImports;
 			}
-		}
-
-		private void collectElementAnnotations(AnnotatedElement element, Set annotations,
-				Set> seen) {
-			for (MergedAnnotation mergedAnnotation : MergedAnnotations.from(element,
-					SearchStrategy.DIRECT)) {
-				Annotation annotation = mergedAnnotation.synthesize();
-				if (!isIgnoredAnnotation(annotation)) {
-					annotations.add(annotation);
-					collectClassAnnotations(annotation.annotationType(), annotations, seen);
-				}
+			else {
+				this.key = AnnotatedElementUtils.findAllMergedAnnotations(testClass,
+						mergedAnnotations.stream().map(MergedAnnotation::getType).collect(Collectors.toSet()));
 			}
 		}
 
-		private boolean isIgnoredAnnotation(Annotation annotation) {
-			for (AnnotationFilter annotationFilter : ANNOTATION_FILTERS) {
-				if (annotationFilter.isIgnored(annotation)) {
-					return true;
-				}
-			}
-			return false;
-		}
-
-		private Set determineImports(Set annotations, Class testClass) {
-			Set determinedImports = new LinkedHashSet<>();
+		private Set determineImports(MergedAnnotations mergedAnnotations, Class testClass) {
 			AnnotationMetadata testClassMetadata = AnnotationMetadata.introspect(testClass);
-			for (Annotation annotation : annotations) {
-				for (Class source : getImports(annotation)) {
-					Set determinedSourceImports = determineImports(source, testClassMetadata);
-					if (determinedSourceImports == null) {
+			return mergedAnnotations.stream(Import.class)
+				.flatMap((ma) -> Stream.of(ma.getClassArray("value")))
+				.map((source) -> determineImports(source, testClassMetadata))
+				.reduce(new HashSet<>(), (a, b) -> {
+					if (a == null || b == null) {
 						return null;
 					}
-					determinedImports.addAll(determinedSourceImports);
-				}
-			}
-			return determinedImports;
-		}
-
-		private Class[] getImports(Annotation annotation) {
-			if (annotation instanceof Import importAnnotation) {
-				return importAnnotation.value();
-			}
-			return NO_IMPORTS;
+					else {
+						a.add(b);
+						return a;
+					}
+				});
 		}
 
 		private Set determineImports(Class source, AnnotationMetadata metadata) {
@@ -340,67 +300,12 @@ public String toString() {
 
 	}
 
-	/**
-	 * Filter used to limit considered annotations.
-	 */
-	private interface AnnotationFilter {
-
-		boolean isIgnored(Annotation annotation);
-
-	}
-
-	/**
-	 * {@link AnnotationFilter} for {@literal java.lang} annotations.
-	 */
-	private static final class JavaLangAnnotationFilter implements AnnotationFilter {
-
-		@Override
-		public boolean isIgnored(Annotation annotation) {
-			return AnnotationUtils.isInJavaLangAnnotationPackage(annotation);
-		}
-
-	}
-
-	/**
-	 * {@link AnnotationFilter} for Kotlin annotations.
-	 */
-	private static final class KotlinAnnotationFilter implements AnnotationFilter {
-
-		@Override
-		public boolean isIgnored(Annotation annotation) {
-			return "kotlin.Metadata".equals(annotation.annotationType().getName())
-					|| isInKotlinAnnotationPackage(annotation);
-		}
-
-		private boolean isInKotlinAnnotationPackage(Annotation annotation) {
-			return annotation.annotationType().getName().startsWith("kotlin.annotation.");
-		}
-
+	static AnnotationFilter or(AnnotationFilter... filters) {
+		return typeName -> Stream.of(filters).anyMatch(filter -> filter.matches(typeName));
 	}
 
-	/**
-	 * {@link AnnotationFilter} for Spock annotations.
-	 */
-	private static final class SpockAnnotationFilter implements AnnotationFilter {
-
-		@Override
-		public boolean isIgnored(Annotation annotation) {
-			return annotation.annotationType().getName().startsWith("org.spockframework.")
-					|| annotation.annotationType().getName().startsWith("spock.");
-		}
-
-	}
-
-	/**
-	 * {@link AnnotationFilter} for JUnit annotations.
-	 */
-	private static final class JUnitAnnotationFilter implements AnnotationFilter {
-
-		@Override
-		public boolean isIgnored(Annotation annotation) {
-			return annotation.annotationType().getName().startsWith("org.junit.");
-		}
-
+	static AnnotationFilter isEqualTo(String expectedTypeName) {
+		return typeName -> typeName.equals(expectedTypeName);
 	}
 
 }

From 7c942679ad85f254acaa0318cf07753c65a21716 Mon Sep 17 00:00:00 2001
From: Phillip Webb 
Date: Tue, 4 Jul 2023 12:49:52 +0100
Subject: [PATCH 0094/1656] Polish 'Switch ImportsContextCustomizer to use
 MergedAnnotations.search'

See gh-36211
---
 .../context/ImportsContextCustomizer.java     | 77 +++++++++----------
 1 file changed, 36 insertions(+), 41 deletions(-)

diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java
index 4b8a6a753132..ca5786240bbe 100644
--- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java
+++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java
@@ -18,10 +18,9 @@
 
 import java.lang.reflect.Constructor;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.BeanFactory;
@@ -41,7 +40,6 @@
 import org.springframework.context.annotation.ImportSelector;
 import org.springframework.context.support.AbstractApplicationContext;
 import org.springframework.core.Ordered;
-import org.springframework.core.annotation.AnnotatedElementUtils;
 import org.springframework.core.annotation.AnnotationFilter;
 import org.springframework.core.annotation.MergedAnnotation;
 import org.springframework.core.annotation.MergedAnnotations;
@@ -52,14 +50,13 @@
 import org.springframework.test.context.MergedContextConfiguration;
 import org.springframework.util.ReflectionUtils;
 
-import static org.springframework.core.annotation.AnnotationFilter.packages;
-
 /**
  * {@link ContextCustomizer} to allow {@code @Import} annotations to be used directly on
  * test classes.
  *
  * @author Phillip Webb
  * @author Andy Wilkinson
+ * @author Laurent Martelli
  * @see ImportsContextCustomizerFactory
  */
 class ImportsContextCustomizer implements ContextCustomizer {
@@ -218,41 +215,43 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t
 	 */
 	static class ContextCustomizerKey {
 
-		private static final AnnotationFilter ANNOTATION_FILTERS = or(packages("java.lang.annotation"),
-				packages("org.spockframework", "spock"),
-				or(isEqualTo("kotlin.Metadata"), packages("kotlin.annotation")), packages(("org.junit")));
-
-		private final Object key;
+		private static final Set ANNOTATION_FILTERS;
+		static {
+			Set annotationFilters = new LinkedHashSet<>();
+			annotationFilters.add(AnnotationFilter.PLAIN);
+			annotationFilters.add("kotlin.Metadata"::equals);
+			annotationFilters.add(AnnotationFilter.packages("kotlin.annotation"));
+			annotationFilters.add(AnnotationFilter.packages("org.spockframework", "spock"));
+			annotationFilters.add(AnnotationFilter.packages("org.junit"));
+			ANNOTATION_FILTERS = Collections.unmodifiableSet(annotationFilters);
+		}
+		private final Set key;
 
 		ContextCustomizerKey(Class testClass) {
-			MergedAnnotations mergedAnnotations = MergedAnnotations
-				.search(MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
-				.withAnnotationFilter(ANNOTATION_FILTERS)
+			MergedAnnotations annotations = MergedAnnotations.search(MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
+				.withAnnotationFilter(this::isFilteredAnnotation)
 				.from(testClass);
-			Set determinedImports = determineImports(mergedAnnotations, testClass);
-			if (determinedImports != null) {
-				this.key = determinedImports;
-			}
-			else {
-				this.key = AnnotatedElementUtils.findAllMergedAnnotations(testClass,
-						mergedAnnotations.stream().map(MergedAnnotation::getType).collect(Collectors.toSet()));
-			}
+			Set determinedImports = determineImports(annotations, testClass);
+			this.key = (determinedImports != null) ? determinedImports : synthesize(annotations);
+		}
+
+		private boolean isFilteredAnnotation(String typeName) {
+			return ANNOTATION_FILTERS.stream().anyMatch((filter) -> filter.matches(typeName));
 		}
 
-		private Set determineImports(MergedAnnotations mergedAnnotations, Class testClass) {
-			AnnotationMetadata testClassMetadata = AnnotationMetadata.introspect(testClass);
-			return mergedAnnotations.stream(Import.class)
-				.flatMap((ma) -> Stream.of(ma.getClassArray("value")))
-				.map((source) -> determineImports(source, testClassMetadata))
-				.reduce(new HashSet<>(), (a, b) -> {
-					if (a == null || b == null) {
+		private Set determineImports(MergedAnnotations annotations, Class testClass) {
+			Set determinedImports = new LinkedHashSet<>();
+			AnnotationMetadata metadata = AnnotationMetadata.introspect(testClass);
+			for (MergedAnnotation annotation : annotations.stream(Import.class).toList()) {
+				for (Class source : annotation.getClassArray(MergedAnnotation.VALUE)) {
+					Set determinedSourceImports = determineImports(source, metadata);
+					if (determinedSourceImports == null) {
 						return null;
 					}
-					else {
-						a.add(b);
-						return a;
-					}
-				});
+					determinedImports.addAll(determinedSourceImports);
+				}
+			}
+			return determinedImports;
 		}
 
 		private Set determineImports(Class source, AnnotationMetadata metadata) {
@@ -270,6 +269,10 @@ private Set determineImports(Class source, AnnotationMetadata metadat
 			return Collections.singleton(source.getName());
 		}
 
+		private Set synthesize(MergedAnnotations annotations) {
+			return annotations.stream().map(MergedAnnotation::synthesize).collect(Collectors.toSet());
+		}
+
 		@SuppressWarnings("unchecked")
 		private  T instantiate(Class source) {
 			try {
@@ -300,12 +303,4 @@ public String toString() {
 
 	}
 
-	static AnnotationFilter or(AnnotationFilter... filters) {
-		return typeName -> Stream.of(filters).anyMatch(filter -> filter.matches(typeName));
-	}
-
-	static AnnotationFilter isEqualTo(String expectedTypeName) {
-		return typeName -> typeName.equals(expectedTypeName);
-	}
-
 }

From 7ceece3d3d1a6a77672d00b32e3f166d81f06b66 Mon Sep 17 00:00:00 2001
From: Arjen Poutsma 
Date: Thu, 29 Jun 2023 13:24:43 +0200
Subject: [PATCH 0095/1656] Support Jetty in ClientHttpRequestFactories

This commit introduces support for the JettyClientHttpRequestFactory
in ClientHttpRequestFactories.

See gh-36116
---
 spring-boot-project/spring-boot/build.gradle  |  1 +
 .../client/ClientHttpRequestFactories.java    | 46 +++++++++++++++++
 .../ClientHttpRequestFactoriesJettyTests.java | 51 +++++++++++++++++++
 ...ClientHttpRequestFactoriesSimpleTests.java |  2 +-
 4 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java

diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle
index 3a862fe98b67..6b86fd02adcf 100644
--- a/spring-boot-project/spring-boot/build.gradle
+++ b/spring-boot-project/spring-boot/build.gradle
@@ -67,6 +67,7 @@ dependencies {
 	optional("org.eclipse.jetty.http2:http2-server") {
 		exclude(group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api")
 	}
+	optional("org.eclipse.jetty:jetty-client")
 	optional("org.flywaydb:flyway-core")
 	optional("org.hamcrest:hamcrest-library")
 	optional("org.hibernate.orm:hibernate-core")
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java
index 5b85a914c039..384e2f2b8914 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java
@@ -26,6 +26,7 @@
 import java.util.function.Supplier;
 
 import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
@@ -38,6 +39,9 @@
 import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
 import org.apache.hc.core5.http.io.SocketConfig;
+import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 import org.springframework.boot.context.properties.PropertyMapper;
 import org.springframework.boot.ssl.SslBundle;
@@ -45,6 +49,7 @@
 import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
 import org.springframework.http.client.ClientHttpRequestFactory;
 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.client.JettyClientHttpRequestFactory;
 import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
 import org.springframework.http.client.SimpleClientHttpRequestFactory;
 import org.springframework.util.Assert;
@@ -70,6 +75,10 @@ public final class ClientHttpRequestFactories {
 
 	private static final boolean OKHTTP_CLIENT_PRESENT = ClassUtils.isPresent(OKHTTP_CLIENT_CLASS, null);
 
+	static final String JETTY_CLIENT_CLASS = "org.eclipse.jetty.client.HttpClient";
+
+	private static final boolean JETTY_CLIENT_PRESENT = ClassUtils.isPresent(JETTY_CLIENT_CLASS, null);
+
 	private ClientHttpRequestFactories() {
 	}
 
@@ -87,6 +96,9 @@ public static ClientHttpRequestFactory get(ClientHttpRequestFactorySettings sett
 		if (OKHTTP_CLIENT_PRESENT) {
 			return OkHttp.get(settings);
 		}
+		if (JETTY_CLIENT_PRESENT) {
+			return Jetty.get(settings);
+		}
 		return Simple.get(settings);
 	}
 
@@ -111,6 +123,9 @@ public static  T get(Class requestFactory
 		if (requestFactoryType == OkHttp3ClientHttpRequestFactory.class) {
 			return (T) OkHttp.get(settings);
 		}
+		if (requestFactoryType == JettyClientHttpRequestFactory.class) {
+			return (T) Jetty.get(settings);
+		}
 		if (requestFactoryType == SimpleClientHttpRequestFactory.class) {
 			return (T) Simple.get(settings);
 		}
@@ -210,6 +225,37 @@ private static OkHttp3ClientHttpRequestFactory createRequestFactory(SslBundle ss
 
 	}
 
+	/**
+	 * Support for {@link JettyClientHttpRequestFactory}.
+	 */
+	static class Jetty {
+
+		static JettyClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) {
+			JettyClientHttpRequestFactory requestFactory = createRequestFactory(settings.sslBundle());
+			PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
+			map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
+			map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
+			return requestFactory;
+		}
+
+		private static JettyClientHttpRequestFactory createRequestFactory(SslBundle sslBundle) {
+			if (sslBundle != null) {
+				SSLContext sslContext = sslBundle.createSslContext();
+
+				SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+				sslContextFactory.setSslContext(sslContext);
+
+				ClientConnector connector = new ClientConnector();
+				connector.setSslContextFactory(sslContextFactory);
+				org.eclipse.jetty.client.HttpClient httpClient =
+						new org.eclipse.jetty.client.HttpClient(new HttpClientTransportDynamic(connector));
+				return new JettyClientHttpRequestFactory(httpClient);
+			}
+			return new JettyClientHttpRequestFactory();
+		}
+
+	}
+
 	/**
 	 * Support for {@link SimpleClientHttpRequestFactory}.
 	 */
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java
new file mode 100644
index 000000000000..10cb8a609fcb
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012-2023 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.web.client;
+
+import org.eclipse.jetty.client.HttpClient;
+
+import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
+import org.springframework.http.client.JettyClientHttpRequestFactory;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ * Tests for {@link ClientHttpRequestFactories} when Jetty is the
+ * predominant HTTP client.
+ *
+ * @author Arjen Poutsma
+ */
+@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar" })
+class ClientHttpRequestFactoriesJettyTests
+		extends AbstractClientHttpRequestFactoriesTests {
+
+	ClientHttpRequestFactoriesJettyTests() {
+		super(JettyClientHttpRequestFactory.class);
+	}
+
+	@Override
+	protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) {
+		HttpClient client = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");
+		return client.getConnectTimeout();
+	}
+
+	@Override
+	@SuppressWarnings("unchecked")
+	protected long readTimeout(JettyClientHttpRequestFactory requestFactory) {
+		return (int) ReflectionTestUtils.getField(requestFactory, "readTimeout");
+	}
+
+}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java
index a9e75aa6496b..f00882bc7df5 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java
@@ -26,7 +26,7 @@
  *
  * @author Andy Wilkinson
  */
-@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar" })
+@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar", "jetty-client-*.jar" })
 class ClientHttpRequestFactoriesSimpleTests
 		extends AbstractClientHttpRequestFactoriesTests {
 

From c3e2c9d684e8df0b2b598e883858b934b1cb8048 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Tue, 4 Jul 2023 13:57:46 +0100
Subject: [PATCH 0096/1656] Polish "Support Jetty in
 ClientHttpRequestFactories"

See gh-36116
---
 .../src/docs/asciidoc/io/rest-client.adoc     |  1 +
 .../client/ClientHttpRequestFactories.java    |  6 ++----
 ...lientHttpRequestFactoriesRuntimeHints.java | 14 +++++++++++--
 .../ClientHttpRequestFactoriesJettyTests.java |  9 +++-----
 ...HttpRequestFactoriesRuntimeHintsTests.java | 12 +++++++++++
 ...geSenderBuilderSimpleIntegrationTests.java |  2 +-
 .../jetty/SampleJettyApplicationTests.java    | 21 +++++++------------
 7 files changed, 38 insertions(+), 27 deletions(-)

diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc
index 0d46c8c21e15..0147af61f5e6 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc
@@ -25,6 +25,7 @@ In order of preference, the following clients are supported:
 
 . Apache HttpClient
 . OkHttp
+. Jetty HttpClient
 . Simple JDK client (`HttpURLConnection`)
 
 If multiple clients are available on the classpath, the most preferred client will be used.
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java
index 384e2f2b8914..347f6c4b0c58 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java
@@ -241,14 +241,12 @@ static JettyClientHttpRequestFactory get(ClientHttpRequestFactorySettings settin
 		private static JettyClientHttpRequestFactory createRequestFactory(SslBundle sslBundle) {
 			if (sslBundle != null) {
 				SSLContext sslContext = sslBundle.createSslContext();
-
 				SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
 				sslContextFactory.setSslContext(sslContext);
-
 				ClientConnector connector = new ClientConnector();
 				connector.setSslContextFactory(sslContextFactory);
-				org.eclipse.jetty.client.HttpClient httpClient =
-						new org.eclipse.jetty.client.HttpClient(new HttpClientTransportDynamic(connector));
+				org.eclipse.jetty.client.HttpClient httpClient = new org.eclipse.jetty.client.HttpClient(
+						new HttpClientTransportDynamic(connector));
 				return new JettyClientHttpRequestFactory(httpClient);
 			}
 			return new JettyClientHttpRequestFactory();
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java
index 457110d6c116..90f3db9af163 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHints.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2022 the original author or authors.
+ * Copyright 2012-2023 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
 import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
 import org.springframework.http.client.ClientHttpRequestFactory;
 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.client.JettyClientHttpRequestFactory;
 import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
 import org.springframework.http.client.SimpleClientHttpRequestFactory;
 import org.springframework.util.Assert;
@@ -59,6 +60,10 @@ private void registerHints(ReflectionHints hints, ClassLoader classLoader) {
 			typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS));
 			registerReflectionHints(hints, OkHttp3ClientHttpRequestFactory.class);
 		});
+		hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.JETTY_CLIENT_CLASS, (typeHint) -> {
+			typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.JETTY_CLIENT_CLASS));
+			registerReflectionHints(hints, JettyClientHttpRequestFactory.class, long.class);
+		});
 		hints.registerType(SimpleClientHttpRequestFactory.class, (typeHint) -> {
 			typeHint.onReachableType(HttpURLConnection.class);
 			registerReflectionHints(hints, SimpleClientHttpRequestFactory.class);
@@ -67,8 +72,13 @@ private void registerHints(ReflectionHints hints, ClassLoader classLoader) {
 
 	private void registerReflectionHints(ReflectionHints hints,
 			Class requestFactoryType) {
+		registerReflectionHints(hints, requestFactoryType, int.class);
+	}
+
+	private void registerReflectionHints(ReflectionHints hints,
+			Class requestFactoryType, Class readTimeoutType) {
 		registerMethod(hints, requestFactoryType, "setConnectTimeout", int.class);
-		registerMethod(hints, requestFactoryType, "setReadTimeout", int.class);
+		registerMethod(hints, requestFactoryType, "setReadTimeout", readTimeoutType);
 	}
 
 	private void registerMethod(ReflectionHints hints, Class requestFactoryType,
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java
index 10cb8a609fcb..27bc1800477c 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJettyTests.java
@@ -23,8 +23,7 @@
 import org.springframework.test.util.ReflectionTestUtils;
 
 /**
- * Tests for {@link ClientHttpRequestFactories} when Jetty is the
- * predominant HTTP client.
+ * Tests for {@link ClientHttpRequestFactories} when Jetty is the predominant HTTP client.
  *
  * @author Arjen Poutsma
  */
@@ -38,14 +37,12 @@ class ClientHttpRequestFactoriesJettyTests
 
 	@Override
 	protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) {
-		HttpClient client = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");
-		return client.getConnectTimeout();
+		return ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).getConnectTimeout();
 	}
 
 	@Override
-	@SuppressWarnings("unchecked")
 	protected long readTimeout(JettyClientHttpRequestFactory requestFactory) {
-		return (int) ReflectionTestUtils.getField(requestFactory, "readTimeout");
+		return (long) ReflectionTestUtils.getField(requestFactory, "readTimeout");
 	}
 
 }
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java
index bbfdafccf580..7eee323f1130 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesRuntimeHintsTests.java
@@ -26,6 +26,7 @@
 import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
 import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.client.JettyClientHttpRequestFactory;
 import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
 import org.springframework.http.client.SimpleClientHttpRequestFactory;
 import org.springframework.util.ReflectionUtils;
@@ -73,6 +74,17 @@ void shouldRegisterOkHttpHints() {
 		assertThat(hints.reflection().getTypeHint(OkHttp3ClientHttpRequestFactory.class).methods()).hasSize(2);
 	}
 
+	@Test
+	void shouldRegisterJettyClientHints() {
+		RuntimeHints hints = new RuntimeHints();
+		new ClientHttpRequestFactoriesRuntimeHints().registerHints(hints, getClass().getClassLoader());
+		ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
+		assertThat(reflection.onMethod(method(JettyClientHttpRequestFactory.class, "setConnectTimeout", int.class)))
+			.accepts(hints);
+		assertThat(reflection.onMethod(method(JettyClientHttpRequestFactory.class, "setReadTimeout", long.class)))
+			.accepts(hints);
+	}
+
 	@Test
 	void shouldRegisterSimpleHttpHints() {
 		RuntimeHints hints = new RuntimeHints();
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java
index 2c3cb5374861..0014d47cd7ef 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java
@@ -34,7 +34,7 @@
  *
  * @author Stephane Nicoll
  */
-@ClassPathExclusions({ "httpclient5-*.jar", "okhttp*.jar" })
+@ClassPathExclusions(files = { "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" })
 class HttpWebServiceMessageSenderBuilderSimpleIntegrationTests {
 
 	private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder();
diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java
index e21fadb0802f..abde5d9033a5 100644
--- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java
+++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty/src/test/java/smoketest/jetty/SampleJettyApplicationTests.java
@@ -16,10 +16,6 @@
 
 package smoketest.jetty;
 
-import java.io.ByteArrayInputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.zip.GZIPInputStream;
-
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import smoketest.jetty.util.RandomStringUtil;
@@ -35,7 +31,6 @@
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
-import org.springframework.util.StreamUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -65,16 +60,14 @@ void testHome() {
 	}
 
 	@Test
-	void testCompression() throws Exception {
-		HttpHeaders requestHeaders = new HttpHeaders();
-		requestHeaders.set("Accept-Encoding", "gzip");
-		HttpEntity requestEntity = new HttpEntity<>(requestHeaders);
-		ResponseEntity entity = this.restTemplate.exchange("/", HttpMethod.GET, requestEntity, byte[].class);
+	void testCompression() {
+		// Jetty HttpClient sends Accept-Encoding: gzip by default
+		ResponseEntity entity = this.restTemplate.getForEntity("/", String.class);
 		assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
-		assertThat(entity.getBody()).isNotNull();
-		try (GZIPInputStream inflater = new GZIPInputStream(new ByteArrayInputStream(entity.getBody()))) {
-			assertThat(StreamUtils.copyToString(inflater, StandardCharsets.UTF_8)).isEqualTo("Hello World");
-		}
+		assertThat(entity.getBody()).isEqualTo("Hello World");
+		// Jetty HttpClient decodes gzip reponses automatically
+		// Check that we received a gzip-encoded response
+		assertThat(entity.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING)).isEqualTo("gzip");
 	}
 
 	@Test

From 7500dab321f0532dd770732c96e4d23763c387ab Mon Sep 17 00:00:00 2001
From: Roman Golovin 
Date: Tue, 13 Jun 2023 20:35:46 +0300
Subject: [PATCH 0097/1656] Support custom token validators for OAuth2

See gh-35874
---
 ...eOAuth2ResourceServerJwkConfiguration.java | 22 ++++--
 .../OAuth2ResourceServerJwtConfiguration.java | 22 ++++--
 ...2ResourceServerAutoConfigurationTests.java | 70 ++++++++++++++++---
 ...2ResourceServerAutoConfigurationTests.java | 62 ++++++++++++++--
 4 files changed, 146 insertions(+), 30 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java
index 223fe260033b..e57b824e932c 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java
@@ -61,6 +61,7 @@
  * @author HaiTao Zhang
  * @author Anastasiia Losieva
  * @author Mushtaq Ahmed
+ * @author Roman Golovin
  */
 @Configuration(proxyBeanMethods = false)
 class ReactiveOAuth2ResourceServerJwkConfiguration {
@@ -71,8 +72,12 @@ static class JwtConfiguration {
 
 		private final OAuth2ResourceServerProperties.Jwt properties;
 
-		JwtConfiguration(OAuth2ResourceServerProperties properties) {
+		private final List> customOAuth2TokenValidators;
+
+		JwtConfiguration(OAuth2ResourceServerProperties properties,
+				List> customOAuth2TokenValidators) {
 			this.properties = properties.getJwt();
+			this.customOAuth2TokenValidators = customOAuth2TokenValidators;
 		}
 
 		@Bean
@@ -97,14 +102,17 @@ private void jwsAlgorithms(Set signatureAlgorithms) {
 		}
 
 		private OAuth2TokenValidator getValidators(OAuth2TokenValidator defaultValidator) {
-			List audiences = this.properties.getAudiences();
-			if (CollectionUtils.isEmpty(audiences)) {
-				return defaultValidator;
-			}
 			List> validators = new ArrayList<>();
 			validators.add(defaultValidator);
-			validators.add(new JwtClaimValidator>(JwtClaimNames.AUD,
-					(aud) -> aud != null && !Collections.disjoint(aud, audiences)));
+			validators.addAll(this.customOAuth2TokenValidators);
+			List audiences = this.properties.getAudiences();
+			if (!CollectionUtils.isEmpty(audiences)) {
+				validators.add(new JwtClaimValidator>(JwtClaimNames.AUD,
+						(aud) -> aud != null && !Collections.disjoint(aud, audiences)));
+			}
+			if (validators.size() == 1) {
+				return validators.get(0);
+			}
 			return new DelegatingOAuth2TokenValidator<>(validators);
 		}
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java
index b039ecd6b45f..bb32ea0149d1 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java
@@ -62,6 +62,7 @@
  * @author Artsiom Yudovin
  * @author HaiTao Zhang
  * @author Mushtaq Ahmed
+ * @author Roman Golovin
  */
 @Configuration(proxyBeanMethods = false)
 class OAuth2ResourceServerJwtConfiguration {
@@ -72,8 +73,12 @@ static class JwtDecoderConfiguration {
 
 		private final OAuth2ResourceServerProperties.Jwt properties;
 
-		JwtDecoderConfiguration(OAuth2ResourceServerProperties properties) {
+		private final List> customOAuth2TokenValidators;
+
+		JwtDecoderConfiguration(OAuth2ResourceServerProperties properties,
+				List> customOAuth2TokenValidators) {
 			this.properties = properties.getJwt();
+			this.customOAuth2TokenValidators = customOAuth2TokenValidators;
 		}
 
 		@Bean
@@ -97,14 +102,17 @@ private void jwsAlgorithms(Set signatureAlgorithms) {
 		}
 
 		private OAuth2TokenValidator getValidators(OAuth2TokenValidator defaultValidator) {
-			List audiences = this.properties.getAudiences();
-			if (CollectionUtils.isEmpty(audiences)) {
-				return defaultValidator;
-			}
 			List> validators = new ArrayList<>();
 			validators.add(defaultValidator);
-			validators.add(new JwtClaimValidator>(JwtClaimNames.AUD,
-					(aud) -> aud != null && !Collections.disjoint(aud, audiences)));
+			validators.addAll(this.customOAuth2TokenValidators);
+			List audiences = this.properties.getAudiences();
+			if (!CollectionUtils.isEmpty(audiences)) {
+				validators.add(new JwtClaimValidator>(JwtClaimNames.AUD,
+						(aud) -> aud != null && !Collections.disjoint(aud, audiences)));
+			}
+			if (validators.size() == 1) {
+				return validators.get(0);
+			}
 			return new DelegatingOAuth2TokenValidator<>(validators);
 		}
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java
index cc5a652d10c2..65c522417f8c 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java
@@ -24,7 +24,9 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -87,6 +89,7 @@
  * @author HaiTao Zhang
  * @author Anastasiia Losieva
  * @author Mushtaq Ahmed
+ * @author Roman Golovin
  */
 class ReactiveOAuth2ResourceServerAutoConfigurationTests {
 
@@ -502,36 +505,73 @@ void autoConfigurationShouldConfigureIssuerAndAudienceJwtValidatorIfPropertyProv
 			.run((context) -> {
 				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
 				ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
-				validate(issuerUri, reactiveJwtDecoder);
+				validate(issuerUri, reactiveJwtDecoder, null);
 			});
 	}
 
 	@SuppressWarnings("unchecked")
-	private void validate(String issuerUri, ReactiveJwtDecoder jwtDecoder) throws MalformedURLException {
+	@Test
+	void autoConfigurationShouldConfigureAudienceAndCustomValidatorsIfPropertyProvidedAndIssuerUri() throws Exception {
+		this.server = new MockWebServer();
+		this.server.start();
+		String path = "test";
+		String issuer = this.server.url(path).toString();
+		String cleanIssuerPath = cleanIssuerPath(issuer);
+		setupMockResponse(cleanIssuerPath);
+		String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
+		this.contextRunner.withPropertyValues(
+				"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
+				"spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri,
+				"spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com")
+			.withUserConfiguration(CustomTokenValidatorsConfig.class)
+			.run((context) -> {
+				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
+				ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
+				assertThat(context).hasBean("customJwtClaimValidator");
+				OAuth2TokenValidator customValidator = (OAuth2TokenValidator) context
+					.getBean("customJwtClaimValidator");
+				validate(issuerUri, reactiveJwtDecoder, customValidator);
+			});
+	}
+
+	@SuppressWarnings("unchecked")
+	private void validate(String issuerUri, ReactiveJwtDecoder jwtDecoder, OAuth2TokenValidator customValidator)
+			throws MalformedURLException {
 		DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
 			.getField(jwtDecoder, "jwtValidator");
 		Jwt.Builder builder = jwt().claim("aud", Collections.singletonList("https://test-audience.com"));
 		if (issuerUri != null) {
 			builder.claim("iss", new URL(issuerUri));
 		}
+		if (customValidator != null) {
+			builder.claim("custom_claim", "custom_claim_value");
+		}
 		Jwt jwt = builder.build();
 		assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse();
 		Collection> delegates = (Collection>) ReflectionTestUtils
 			.getField(jwtValidator, "tokenValidators");
-		validateDelegates(issuerUri, delegates);
+		validateDelegates(issuerUri, delegates, customValidator);
 	}
 
-	@SuppressWarnings("unchecked")
-	private void validateDelegates(String issuerUri, Collection> delegates) {
+	private void validateDelegates(String issuerUri, Collection> delegates,
+			OAuth2TokenValidator customValidator) {
 		assertThat(delegates).hasAtLeastOneElementOfType(JwtClaimValidator.class);
 		OAuth2TokenValidator delegatingValidator = delegates.stream()
 			.filter((v) -> v instanceof DelegatingOAuth2TokenValidator)
 			.findFirst()
 			.get();
-		Collection> nestedDelegates = (Collection>) ReflectionTestUtils
-			.getField(delegatingValidator, "tokenValidators");
 		if (issuerUri != null) {
-			assertThat(nestedDelegates).hasAtLeastOneElementOfType(JwtIssuerValidator.class);
+			assertThat(delegatingValidator).extracting("tokenValidators")
+				.asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class))
+				.hasAtLeastOneElementOfType(JwtIssuerValidator.class);
+		}
+		List> claimValidators = delegates.stream()
+			.filter((d) -> d instanceof JwtClaimValidator)
+			.collect(Collectors.toList());
+		assertThat(claimValidators).anyMatch((v) -> "aud".equals(ReflectionTestUtils.getField(v, "claim")));
+		if (customValidator != null) {
+			assertThat(claimValidators)
+				.anyMatch((v) -> "custom_claim".equals(ReflectionTestUtils.getField(v, "claim")));
 		}
 	}
 
@@ -552,7 +592,7 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssue
 				Mono jwtDecoderSupplier = (Mono) ReflectionTestUtils
 					.getField(supplierJwtDecoderBean, "jwtDecoderMono");
 				ReactiveJwtDecoder jwtDecoder = jwtDecoderSupplier.block();
-				validate(issuerUri, jwtDecoder);
+				validate(issuerUri, jwtDecoder, null);
 			});
 	}
 
@@ -570,7 +610,7 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPubli
 			.run((context) -> {
 				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
 				ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class);
-				validate(null, jwtDecoder);
+				validate(null, jwtDecoder, null);
 			});
 	}
 
@@ -740,4 +780,14 @@ SecurityWebFilterChain testSpringSecurityFilterChain(ServerHttpSecurity http) {
 
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	static class CustomTokenValidatorsConfig {
+
+		@Bean
+		JwtClaimValidator customJwtClaimValidator() {
+			return new JwtClaimValidator<>("custom_claim", "custom_claim_value"::equals);
+		}
+
+	}
+
 }
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java
index 878415ec9f9f..42ec6ca08d5f 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java
@@ -25,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -80,6 +81,7 @@
  * @author Artsiom Yudovin
  * @author HaiTao Zhang
  * @author Mushtaq Ahmed
+ * @author Roman Golovin
  */
 class OAuth2ResourceServerAutoConfigurationTests {
 
@@ -515,7 +517,7 @@ void autoConfigurationShouldConfigureAudienceAndIssuerJwtValidatorIfPropertyProv
 			.run((context) -> {
 				assertThat(context).hasSingleBean(JwtDecoder.class);
 				JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
-				validate(issuerUri, jwtDecoder);
+				validate(issuerUri, jwtDecoder, null);
 			});
 	}
 
@@ -536,26 +538,56 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssue
 				Supplier jwtDecoderSupplier = (Supplier) ReflectionTestUtils
 					.getField(supplierJwtDecoderBean, "delegate");
 				JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
-				validate(issuerUri, jwtDecoder);
+				validate(issuerUri, jwtDecoder, null);
 			});
 	}
 
 	@SuppressWarnings("unchecked")
-	private void validate(String issuerUri, JwtDecoder jwtDecoder) throws MalformedURLException {
+	@Test
+	void autoConfigurationShouldConfigureAudienceAndCustomValidatorsIfPropertyProvidedAndIssuerUri() throws Exception {
+		this.server = new MockWebServer();
+		this.server.start();
+		String path = "test";
+		String issuer = this.server.url(path).toString();
+		String cleanIssuerPath = cleanIssuerPath(issuer);
+		setupMockResponse(cleanIssuerPath);
+		String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
+		this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri,
+				"spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com")
+			.withUserConfiguration(CustomTokenValidatorsConfig.class)
+			.run((context) -> {
+				SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
+				Supplier jwtDecoderSupplier = (Supplier) ReflectionTestUtils
+					.getField(supplierJwtDecoderBean, "delegate");
+				JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
+				assertThat(context).hasBean("customJwtClaimValidator");
+				OAuth2TokenValidator customValidator = (OAuth2TokenValidator) context
+					.getBean("customJwtClaimValidator");
+				validate(issuerUri, jwtDecoder, customValidator);
+			});
+	}
+
+	@SuppressWarnings("unchecked")
+	private void validate(String issuerUri, JwtDecoder jwtDecoder, OAuth2TokenValidator customValidator)
+			throws MalformedURLException {
 		DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
 			.getField(jwtDecoder, "jwtValidator");
 		Jwt.Builder builder = jwt().claim("aud", Collections.singletonList("https://test-audience.com"));
 		if (issuerUri != null) {
 			builder.claim("iss", new URL(issuerUri));
 		}
+		if (customValidator != null) {
+			builder.claim("custom_claim", "custom_claim_value");
+		}
 		Jwt jwt = builder.build();
 		assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse();
 		Collection> delegates = (Collection>) ReflectionTestUtils
 			.getField(jwtValidator, "tokenValidators");
-		validateDelegates(issuerUri, delegates);
+		validateDelegates(issuerUri, delegates, customValidator);
 	}
 
-	private void validateDelegates(String issuerUri, Collection> delegates) {
+	private void validateDelegates(String issuerUri, Collection> delegates,
+			OAuth2TokenValidator customValidator) {
 		assertThat(delegates).hasAtLeastOneElementOfType(JwtClaimValidator.class);
 		OAuth2TokenValidator delegatingValidator = delegates.stream()
 			.filter((v) -> v instanceof DelegatingOAuth2TokenValidator)
@@ -566,6 +598,14 @@ private void validateDelegates(String issuerUri, Collection> claimValidators = delegates.stream()
+			.filter((d) -> d instanceof JwtClaimValidator)
+			.collect(Collectors.toList());
+		assertThat(claimValidators).anyMatch((v) -> "aud".equals(ReflectionTestUtils.getField(v, "claim")));
+		if (customValidator != null) {
+			assertThat(claimValidators)
+				.anyMatch((v) -> "custom_claim".equals(ReflectionTestUtils.getField(v, "claim")));
+		}
 	}
 
 	@Test
@@ -582,7 +622,7 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPubli
 			.run((context) -> {
 				assertThat(context).hasSingleBean(JwtDecoder.class);
 				JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
-				validate(null, jwtDecoder);
+				validate(null, jwtDecoder, null);
 			});
 	}
 
@@ -745,4 +785,14 @@ SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception
 
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	static class CustomTokenValidatorsConfig {
+
+		@Bean
+		JwtClaimValidator customJwtClaimValidator() {
+			return new JwtClaimValidator<>("custom_claim", "custom_claim_value"::equals);
+		}
+
+	}
+
 }

From 4feaa28fd1ce83e251aefb1aac1a42272054b9bc Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Wed, 5 Jul 2023 14:01:08 +0100
Subject: [PATCH 0098/1656] Polish "Support custom token validators for OAuth2"

See gh-35874
---
 ...eOAuth2ResourceServerJwkConfiguration.java |  16 +-
 .../OAuth2ResourceServerJwtConfiguration.java |  16 +-
 ...2ResourceServerAutoConfigurationTests.java | 187 ++++++++++--------
 ...2ResourceServerAutoConfigurationTests.java | 126 ++++++------
 4 files changed, 181 insertions(+), 164 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java
index e57b824e932c..5f5cba160eaa 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java
@@ -72,12 +72,12 @@ static class JwtConfiguration {
 
 		private final OAuth2ResourceServerProperties.Jwt properties;
 
-		private final List> customOAuth2TokenValidators;
+		private final List> additionalValidators;
 
 		JwtConfiguration(OAuth2ResourceServerProperties properties,
-				List> customOAuth2TokenValidators) {
+				ObjectProvider> additionalValidators) {
 			this.properties = properties.getJwt();
-			this.customOAuth2TokenValidators = customOAuth2TokenValidators;
+			this.additionalValidators = additionalValidators.orderedStream().toList();
 		}
 
 		@Bean
@@ -102,17 +102,17 @@ private void jwsAlgorithms(Set signatureAlgorithms) {
 		}
 
 		private OAuth2TokenValidator getValidators(OAuth2TokenValidator defaultValidator) {
+			List audiences = this.properties.getAudiences();
+			if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) {
+				return defaultValidator;
+			}
 			List> validators = new ArrayList<>();
 			validators.add(defaultValidator);
-			validators.addAll(this.customOAuth2TokenValidators);
-			List audiences = this.properties.getAudiences();
 			if (!CollectionUtils.isEmpty(audiences)) {
 				validators.add(new JwtClaimValidator>(JwtClaimNames.AUD,
 						(aud) -> aud != null && !Collections.disjoint(aud, audiences)));
 			}
-			if (validators.size() == 1) {
-				return validators.get(0);
-			}
+			validators.addAll(this.additionalValidators);
 			return new DelegatingOAuth2TokenValidator<>(validators);
 		}
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java
index bb32ea0149d1..84bafab99db0 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java
@@ -73,12 +73,12 @@ static class JwtDecoderConfiguration {
 
 		private final OAuth2ResourceServerProperties.Jwt properties;
 
-		private final List> customOAuth2TokenValidators;
+		private final List> additionalValidators;
 
 		JwtDecoderConfiguration(OAuth2ResourceServerProperties properties,
-				List> customOAuth2TokenValidators) {
+				ObjectProvider> additionalValidators) {
 			this.properties = properties.getJwt();
-			this.customOAuth2TokenValidators = customOAuth2TokenValidators;
+			this.additionalValidators = additionalValidators.orderedStream().toList();
 		}
 
 		@Bean
@@ -102,17 +102,17 @@ private void jwsAlgorithms(Set signatureAlgorithms) {
 		}
 
 		private OAuth2TokenValidator getValidators(OAuth2TokenValidator defaultValidator) {
+			List audiences = this.properties.getAudiences();
+			if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) {
+				return defaultValidator;
+			}
 			List> validators = new ArrayList<>();
 			validators.add(defaultValidator);
-			validators.addAll(this.customOAuth2TokenValidators);
-			List audiences = this.properties.getAudiences();
 			if (!CollectionUtils.isEmpty(audiences)) {
 				validators.add(new JwtClaimValidator>(JwtClaimNames.AUD,
 						(aud) -> aud != null && !Collections.disjoint(aud, audiences)));
 			}
-			if (validators.size() == 1) {
-				return validators.get(0);
-			}
+			validators.addAll(this.additionalValidators);
 			return new DelegatingOAuth2TokenValidator<>(validators);
 		}
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java
index 65c522417f8c..e8165ee18916 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java
@@ -17,16 +17,17 @@
 package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
 
 import java.io.IOException;
-import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -35,6 +36,7 @@
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
 import org.assertj.core.api.InstanceOfAssertFactories;
+import org.assertj.core.api.ThrowingConsumer;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.InOrder;
@@ -441,7 +443,6 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldBeConditionalOnClass()
 			.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOpaqueTokenIntrospector.class));
 	}
 
-	@SuppressWarnings("unchecked")
 	@Test
 	void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception {
 		this.server = new MockWebServer();
@@ -457,15 +458,11 @@ void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri()
 			.run((context) -> {
 				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
 				ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
-				DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
-					.getField(reactiveJwtDecoder, "jwtValidator");
-				Collection> tokenValidators = (Collection>) ReflectionTestUtils
-					.getField(jwtValidator, "tokenValidators");
-				assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtIssuerValidator.class);
+				validate(jwt().claim("iss", issuer), reactiveJwtDecoder,
+						(validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class));
 			});
 	}
 
-	@SuppressWarnings("unchecked")
 	@Test
 	void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfPropertyNotConfigured() throws Exception {
 		this.server = new MockWebServer();
@@ -479,13 +476,8 @@ void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfProper
 			.run((context) -> {
 				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
 				ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
-				DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
-					.getField(reactiveJwtDecoder, "jwtValidator");
-				Collection> tokenValidators = (Collection>) ReflectionTestUtils
-					.getField(jwtValidator, "tokenValidators");
-				assertThat(tokenValidators).hasExactlyElementsOfTypes(JwtTimestampValidator.class);
-				assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtClaimValidator.class);
-				assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtIssuerValidator.class);
+				validate(jwt(), reactiveJwtDecoder, (validators) -> assertThat(validators).singleElement()
+					.isInstanceOf(JwtTimestampValidator.class));
 			});
 	}
 
@@ -505,13 +497,18 @@ void autoConfigurationShouldConfigureIssuerAndAudienceJwtValidatorIfPropertyProv
 			.run((context) -> {
 				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
 				ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
-				validate(issuerUri, reactiveJwtDecoder, null);
+				validate(
+						jwt().claim("iss", URI.create(issuerUri).toURL())
+							.claim("aud", List.of("https://test-audience.com")),
+						reactiveJwtDecoder,
+						(validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class)
+							.satisfiesOnlyOnce(audClaimValidator()));
 			});
 	}
 
 	@SuppressWarnings("unchecked")
 	@Test
-	void autoConfigurationShouldConfigureAudienceAndCustomValidatorsIfPropertyProvidedAndIssuerUri() throws Exception {
+	void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssuerUri() throws Exception {
 		this.server = new MockWebServer();
 		this.server.start();
 		String path = "test";
@@ -519,98 +516,63 @@ void autoConfigurationShouldConfigureAudienceAndCustomValidatorsIfPropertyProvid
 		String cleanIssuerPath = cleanIssuerPath(issuer);
 		setupMockResponse(cleanIssuerPath);
 		String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
-		this.contextRunner.withPropertyValues(
-				"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
-				"spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri,
+		this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri,
 				"spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com")
-			.withUserConfiguration(CustomTokenValidatorsConfig.class)
 			.run((context) -> {
-				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
-				ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
-				assertThat(context).hasBean("customJwtClaimValidator");
-				OAuth2TokenValidator customValidator = (OAuth2TokenValidator) context
-					.getBean("customJwtClaimValidator");
-				validate(issuerUri, reactiveJwtDecoder, customValidator);
+				SupplierReactiveJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierReactiveJwtDecoder.class);
+				Mono jwtDecoderSupplier = (Mono) ReflectionTestUtils
+					.getField(supplierJwtDecoderBean, "jwtDecoderMono");
+				ReactiveJwtDecoder jwtDecoder = jwtDecoderSupplier.block();
+				validate(
+						jwt().claim("iss", URI.create(issuerUri).toURL())
+							.claim("aud", List.of("https://test-audience.com")),
+						jwtDecoder,
+						(validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class)
+							.satisfiesOnlyOnce(audClaimValidator()));
 			});
 	}
 
-	@SuppressWarnings("unchecked")
-	private void validate(String issuerUri, ReactiveJwtDecoder jwtDecoder, OAuth2TokenValidator customValidator)
-			throws MalformedURLException {
-		DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
-			.getField(jwtDecoder, "jwtValidator");
-		Jwt.Builder builder = jwt().claim("aud", Collections.singletonList("https://test-audience.com"));
-		if (issuerUri != null) {
-			builder.claim("iss", new URL(issuerUri));
-		}
-		if (customValidator != null) {
-			builder.claim("custom_claim", "custom_claim_value");
-		}
-		Jwt jwt = builder.build();
-		assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse();
-		Collection> delegates = (Collection>) ReflectionTestUtils
-			.getField(jwtValidator, "tokenValidators");
-		validateDelegates(issuerUri, delegates, customValidator);
-	}
-
-	private void validateDelegates(String issuerUri, Collection> delegates,
-			OAuth2TokenValidator customValidator) {
-		assertThat(delegates).hasAtLeastOneElementOfType(JwtClaimValidator.class);
-		OAuth2TokenValidator delegatingValidator = delegates.stream()
-			.filter((v) -> v instanceof DelegatingOAuth2TokenValidator)
-			.findFirst()
-			.get();
-		if (issuerUri != null) {
-			assertThat(delegatingValidator).extracting("tokenValidators")
-				.asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class))
-				.hasAtLeastOneElementOfType(JwtIssuerValidator.class);
-		}
-		List> claimValidators = delegates.stream()
-			.filter((d) -> d instanceof JwtClaimValidator)
-			.collect(Collectors.toList());
-		assertThat(claimValidators).anyMatch((v) -> "aud".equals(ReflectionTestUtils.getField(v, "claim")));
-		if (customValidator != null) {
-			assertThat(claimValidators)
-				.anyMatch((v) -> "custom_claim".equals(ReflectionTestUtils.getField(v, "claim")));
-		}
-	}
-
-	@SuppressWarnings("unchecked")
 	@Test
-	void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssuerUri() throws Exception {
+	void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPublicKey() throws Exception {
 		this.server = new MockWebServer();
 		this.server.start();
 		String path = "test";
 		String issuer = this.server.url(path).toString();
 		String cleanIssuerPath = cleanIssuerPath(issuer);
 		setupMockResponse(cleanIssuerPath);
-		String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
-		this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri,
+		this.contextRunner.withPropertyValues(
+				"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location",
 				"spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com")
 			.run((context) -> {
-				SupplierReactiveJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierReactiveJwtDecoder.class);
-				Mono jwtDecoderSupplier = (Mono) ReflectionTestUtils
-					.getField(supplierJwtDecoderBean, "jwtDecoderMono");
-				ReactiveJwtDecoder jwtDecoder = jwtDecoderSupplier.block();
-				validate(issuerUri, jwtDecoder, null);
+				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
+				ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class);
+				validate(jwt().claim("aud", List.of("https://test-audience.com")), jwtDecoder,
+						(validators) -> assertThat(validators).satisfiesOnlyOnce(audClaimValidator()));
 			});
 	}
 
+	@SuppressWarnings("unchecked")
 	@Test
-	void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPublicKey() throws Exception {
+	void autoConfigurationShouldConfigureCustomValidators() throws Exception {
 		this.server = new MockWebServer();
 		this.server.start();
 		String path = "test";
 		String issuer = this.server.url(path).toString();
 		String cleanIssuerPath = cleanIssuerPath(issuer);
 		setupMockResponse(cleanIssuerPath);
-		this.contextRunner.withPropertyValues(
-				"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location",
-				"spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com")
+		String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
+		this.contextRunner
+			.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
+					"spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri)
+			.withUserConfiguration(CustomJwtClaimValidatorConfig.class)
 			.run((context) -> {
 				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
-				ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class);
-				validate(null, jwtDecoder, null);
+				ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
+				OAuth2TokenValidator customValidator = (OAuth2TokenValidator) context
+					.getBean("customJwtClaimValidator");
+				validate(jwt().claim("iss", URI.create(issuerUri).toURL()).claim("custom_claim", "custom_claim_value"),
+						reactiveJwtDecoder, (validators) -> assertThat(validators).contains(customValidator)
+							.hasAtLeastOneElementOfType(JwtIssuerValidator.class));
 			});
 	}
 
@@ -640,6 +602,30 @@ void audienceValidatorWhenAudienceInvalid() throws Exception {
 			});
 	}
 
+	@SuppressWarnings("unchecked")
+	@Test
+	void customValidatorWhenInvalid() throws Exception {
+		this.server = new MockWebServer();
+		this.server.start();
+		String path = "test";
+		String issuer = this.server.url(path).toString();
+		String cleanIssuerPath = cleanIssuerPath(issuer);
+		setupMockResponse(cleanIssuerPath);
+		String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
+		this.contextRunner
+			.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
+					"spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri)
+			.withUserConfiguration(CustomJwtClaimValidatorConfig.class)
+			.run((context) -> {
+				assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
+				ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class);
+				DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
+					.getField(jwtDecoder, "jwtValidator");
+				Jwt jwt = jwt().claim("iss", new URL(issuerUri)).claim("custom_claim", "invalid_value").build();
+				assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue();
+			});
+	}
+
 	private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) {
 		MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context
 			.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
@@ -723,6 +709,37 @@ static Jwt.Builder jwt() {
 			.subject("mock-test-subject");
 	}
 
+	@SuppressWarnings("unchecked")
+	private void validate(Jwt.Builder builder, ReactiveJwtDecoder jwtDecoder,
+			ThrowingConsumer>> validatorsConsumer) {
+		DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
+			.getField(jwtDecoder, "jwtValidator");
+		assertThat(jwtValidator.validate(builder.build()).hasErrors()).isFalse();
+		validatorsConsumer.accept(extractValidators(jwtValidator));
+	}
+
+	@SuppressWarnings("unchecked")
+	private List> extractValidators(DelegatingOAuth2TokenValidator delegatingValidator) {
+		Collection> delegates = (Collection>) ReflectionTestUtils
+			.getField(delegatingValidator, "tokenValidators");
+		List> extracted = new ArrayList<>();
+		for (OAuth2TokenValidator delegate : delegates) {
+			if (delegate instanceof DelegatingOAuth2TokenValidator delegatingDelegate) {
+				extracted.addAll(extractValidators(delegatingDelegate));
+			}
+			else {
+				extracted.add(delegate);
+			}
+		}
+		return extracted;
+	}
+
+	private Consumer> audClaimValidator() {
+		return (validator) -> assertThat(validator).isInstanceOf(JwtClaimValidator.class)
+			.extracting("claim")
+			.isEqualTo("aud");
+	}
+
 	@EnableWebFluxSecurity
 	static class TestConfig {
 
@@ -781,7 +798,7 @@ SecurityWebFilterChain testSpringSecurityFilterChain(ServerHttpSecurity http) {
 	}
 
 	@Configuration(proxyBeanMethods = false)
-	static class CustomTokenValidatorsConfig {
+	static class CustomJwtClaimValidatorConfig {
 
 		@Bean
 		JwtClaimValidator customJwtClaimValidator() {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java
index 42ec6ca08d5f..b7aa1a5ec67b 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java
@@ -16,16 +16,17 @@
 
 package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
 
-import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -34,6 +35,7 @@
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
 import org.assertj.core.api.InstanceOfAssertFactories;
+import org.assertj.core.api.ThrowingConsumer;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.InOrder;
@@ -192,8 +194,8 @@ void autoConfigurationUsingPublicKeyValueWithMultipleJwsAlgorithmsShouldFail() {
 			});
 	}
 
-	@Test
 	@SuppressWarnings("unchecked")
+	@Test
 	void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception {
 		this.server = new MockWebServer();
 		this.server.start();
@@ -217,8 +219,8 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws E
 		assertThat(this.server.getRequestCount()).isEqualTo(2);
 	}
 
-	@Test
 	@SuppressWarnings("unchecked")
+	@Test
 	void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
 		this.server = new MockWebServer();
 		this.server.start();
@@ -242,8 +244,8 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() t
 		assertThat(this.server.getRequestCount()).isEqualTo(3);
 	}
 
-	@Test
 	@SuppressWarnings("unchecked")
+	@Test
 	void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
 		this.server = new MockWebServer();
 		this.server.start();
@@ -474,9 +476,8 @@ void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri()
 			.run((context) -> {
 				assertThat(context).hasSingleBean(JwtDecoder.class);
 				JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
-				assertThat(jwtDecoder).extracting("jwtValidator.tokenValidators")
-					.asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class))
-					.hasAtLeastOneElementOfType(JwtIssuerValidator.class);
+				validate(jwt().claim("iss", issuer), jwtDecoder,
+						(validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class));
 			});
 	}
 
@@ -493,11 +494,8 @@ void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfProper
 			.run((context) -> {
 				assertThat(context).hasSingleBean(JwtDecoder.class);
 				JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
-				assertThat(jwtDecoder).extracting("jwtValidator.tokenValidators")
-					.asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class))
-					.hasExactlyElementsOfTypes(JwtTimestampValidator.class)
-					.doesNotHaveAnyElementsOfTypes(JwtClaimValidator.class)
-					.doesNotHaveAnyElementsOfTypes(JwtIssuerValidator.class);
+				validate(jwt(), jwtDecoder, (validators) -> assertThat(validators).singleElement()
+					.isInstanceOf(JwtTimestampValidator.class));
 			});
 	}
 
@@ -517,7 +515,12 @@ void autoConfigurationShouldConfigureAudienceAndIssuerJwtValidatorIfPropertyProv
 			.run((context) -> {
 				assertThat(context).hasSingleBean(JwtDecoder.class);
 				JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
-				validate(issuerUri, jwtDecoder, null);
+				validate(
+						jwt().claim("iss", URI.create(issuerUri).toURL())
+							.claim("aud", List.of("https://test-audience.com")),
+						jwtDecoder,
+						(validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class)
+							.satisfiesOnlyOnce(audClaimValidator()));
 			});
 	}
 
@@ -538,13 +541,18 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssue
 				Supplier jwtDecoderSupplier = (Supplier) ReflectionTestUtils
 					.getField(supplierJwtDecoderBean, "delegate");
 				JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
-				validate(issuerUri, jwtDecoder, null);
+				validate(
+						jwt().claim("iss", URI.create(issuerUri).toURL())
+							.claim("aud", List.of("https://test-audience.com")),
+						jwtDecoder,
+						(validators) -> assertThat(validators).hasAtLeastOneElementOfType(JwtIssuerValidator.class)
+							.satisfiesOnlyOnce(audClaimValidator()));
 			});
 	}
 
 	@SuppressWarnings("unchecked")
 	@Test
-	void autoConfigurationShouldConfigureAudienceAndCustomValidatorsIfPropertyProvidedAndIssuerUri() throws Exception {
+	void autoConfigurationShouldConfigureCustomValidators() throws Exception {
 		this.server = new MockWebServer();
 		this.server.start();
 		String path = "test";
@@ -552,9 +560,8 @@ void autoConfigurationShouldConfigureAudienceAndCustomValidatorsIfPropertyProvid
 		String cleanIssuerPath = cleanIssuerPath(issuer);
 		setupMockResponse(cleanIssuerPath);
 		String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
-		this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri,
-				"spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com")
-			.withUserConfiguration(CustomTokenValidatorsConfig.class)
+		this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri)
+			.withUserConfiguration(CustomJwtClaimValidatorConfig.class)
 			.run((context) -> {
 				SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
 				Supplier jwtDecoderSupplier = (Supplier) ReflectionTestUtils
@@ -563,51 +570,12 @@ void autoConfigurationShouldConfigureAudienceAndCustomValidatorsIfPropertyProvid
 				assertThat(context).hasBean("customJwtClaimValidator");
 				OAuth2TokenValidator customValidator = (OAuth2TokenValidator) context
 					.getBean("customJwtClaimValidator");
-				validate(issuerUri, jwtDecoder, customValidator);
+				validate(jwt().claim("iss", URI.create(issuerUri).toURL()).claim("custom_claim", "custom_claim_value"),
+						jwtDecoder, (validators) -> assertThat(validators).contains(customValidator)
+							.hasAtLeastOneElementOfType(JwtIssuerValidator.class));
 			});
 	}
 
-	@SuppressWarnings("unchecked")
-	private void validate(String issuerUri, JwtDecoder jwtDecoder, OAuth2TokenValidator customValidator)
-			throws MalformedURLException {
-		DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
-			.getField(jwtDecoder, "jwtValidator");
-		Jwt.Builder builder = jwt().claim("aud", Collections.singletonList("https://test-audience.com"));
-		if (issuerUri != null) {
-			builder.claim("iss", new URL(issuerUri));
-		}
-		if (customValidator != null) {
-			builder.claim("custom_claim", "custom_claim_value");
-		}
-		Jwt jwt = builder.build();
-		assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse();
-		Collection> delegates = (Collection>) ReflectionTestUtils
-			.getField(jwtValidator, "tokenValidators");
-		validateDelegates(issuerUri, delegates, customValidator);
-	}
-
-	private void validateDelegates(String issuerUri, Collection> delegates,
-			OAuth2TokenValidator customValidator) {
-		assertThat(delegates).hasAtLeastOneElementOfType(JwtClaimValidator.class);
-		OAuth2TokenValidator delegatingValidator = delegates.stream()
-			.filter((v) -> v instanceof DelegatingOAuth2TokenValidator)
-			.findFirst()
-			.get();
-		if (issuerUri != null) {
-			assertThat(delegatingValidator).extracting("tokenValidators")
-				.asInstanceOf(InstanceOfAssertFactories.collection(OAuth2TokenValidator.class))
-				.hasAtLeastOneElementOfType(JwtIssuerValidator.class);
-		}
-		List> claimValidators = delegates.stream()
-			.filter((d) -> d instanceof JwtClaimValidator)
-			.collect(Collectors.toList());
-		assertThat(claimValidators).anyMatch((v) -> "aud".equals(ReflectionTestUtils.getField(v, "claim")));
-		if (customValidator != null) {
-			assertThat(claimValidators)
-				.anyMatch((v) -> "custom_claim".equals(ReflectionTestUtils.getField(v, "claim")));
-		}
-	}
-
 	@Test
 	void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPublicKey() throws Exception {
 		this.server = new MockWebServer();
@@ -622,7 +590,8 @@ void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPubli
 			.run((context) -> {
 				assertThat(context).hasSingleBean(JwtDecoder.class);
 				JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
-				validate(null, jwtDecoder, null);
+				validate(jwt().claim("aud", List.of("https://test-audience.com")), jwtDecoder,
+						(validators) -> assertThat(validators).satisfiesOnlyOnce(audClaimValidator()));
 			});
 	}
 
@@ -732,6 +701,37 @@ static Jwt.Builder jwt() {
 			.subject("mock-test-subject");
 	}
 
+	@SuppressWarnings("unchecked")
+	private void validate(Jwt.Builder builder, JwtDecoder jwtDecoder,
+			ThrowingConsumer>> validatorsConsumer) {
+		DelegatingOAuth2TokenValidator jwtValidator = (DelegatingOAuth2TokenValidator) ReflectionTestUtils
+			.getField(jwtDecoder, "jwtValidator");
+		assertThat(jwtValidator.validate(builder.build()).hasErrors()).isFalse();
+		validatorsConsumer.accept(extractValidators(jwtValidator));
+	}
+
+	@SuppressWarnings("unchecked")
+	private List> extractValidators(DelegatingOAuth2TokenValidator delegatingValidator) {
+		Collection> delegates = (Collection>) ReflectionTestUtils
+			.getField(delegatingValidator, "tokenValidators");
+		List> extracted = new ArrayList<>();
+		for (OAuth2TokenValidator delegate : delegates) {
+			if (delegate instanceof DelegatingOAuth2TokenValidator delegatingDelegate) {
+				extracted.addAll(extractValidators(delegatingDelegate));
+			}
+			else {
+				extracted.add(delegate);
+			}
+		}
+		return extracted;
+	}
+
+	private Consumer> audClaimValidator() {
+		return (validator) -> assertThat(validator).isInstanceOf(JwtClaimValidator.class)
+			.extracting("claim")
+			.isEqualTo("aud");
+	}
+
 	@Configuration(proxyBeanMethods = false)
 	@EnableWebSecurity
 	static class TestConfig {
@@ -786,7 +786,7 @@ SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception
 	}
 
 	@Configuration(proxyBeanMethods = false)
-	static class CustomTokenValidatorsConfig {
+	static class CustomJwtClaimValidatorConfig {
 
 		@Bean
 		JwtClaimValidator customJwtClaimValidator() {

From fc8a8d363f5a947096e8721319ca6c6d0df26f5d Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Wed, 5 Jul 2023 15:50:32 +0100
Subject: [PATCH 0099/1656] Polish

---
 .../boot/configurationmetadata/changelog/ChangelogWriter.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java
index fd79845d0ecf..033d5e20977e 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java
@@ -89,7 +89,7 @@ private Map> collateByType(Changelog difference
 	private void writeDeprecated(List differences) {
 		List rows = sortProperties(differences, Difference::newProperty).stream()
 			.filter(this::isDeprecatedInRelease)
-			.collect(Collectors.toList());
+			.toList();
 		writeTable("| Key | Replacement | Reason", rows, this::writeDeprecated);
 	}
 

From a1a5acf1282975097ade701c4bee56e7da4cb085 Mon Sep 17 00:00:00 2001
From: Arjen Poutsma 
Date: Tue, 4 Jul 2023 15:50:53 +0200
Subject: [PATCH 0100/1656] Add initial support for RestClient

Introduce initial support for Spring Framework's `RestClient`, in the
form of a `RestClientCustomizer` and `RestClientAutoConfiguration`.

See gh-36213
---
 .../client/RestClientAutoConfiguration.java   |  70 +++++++++++
 .../RestClientAutoConfigurationTests.java     | 114 ++++++++++++++++++
 .../boot/web/client/RestClientCustomizer.java |  38 ++++++
 3 files changed, 222 insertions(+)
 create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java
 create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java
 create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestClientCustomizer.java

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java
new file mode 100644
index 000000000000..bcd98abbf547
--- /dev/null
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.autoconfigure.web.client;
+
+import org.springframework.beans.factory.ObjectProvider;
+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.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
+import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
+import org.springframework.boot.web.client.RestClientCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Scope;
+import org.springframework.web.client.RestClient;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for {@link RestClient}.
+ * 

+ * This will produce a {@link RestClient.Builder RestClient.Builder} bean with the + * {@code prototype} scope, meaning each injection point will receive a newly cloned + * instance of the builder. + * + * @author Arjen Poutsma + * @since 3.2.0 + */ +@AutoConfiguration(after = HttpMessageConvertersAutoConfiguration.class) +@ConditionalOnClass(RestClient.class) +@Conditional(RestClientAutoConfiguration.NotReactiveWebApplicationCondition.class) +public class RestClientAutoConfiguration { + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + public RestClient.Builder webClientBuilder(ObjectProvider customizerProvider) { + RestClient.Builder builder = RestClient.builder(); + customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder; + } + + static class NotReactiveWebApplicationCondition extends NoneNestedConditions { + + NotReactiveWebApplicationCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + private static class ReactiveWebApplication { + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java new file mode 100644 index 000000000000..2a256bb3fb31 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.boot.web.codec.CodecCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link RestClientAutoConfiguration} + * + * @author Arjen Poutsma + */ +class RestClientAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class)); + + @Test + void shouldCreateBuilder() { + this.contextRunner.run((context) -> { + RestClient.Builder builder = context.getBean(RestClient.Builder.class); + RestClient restClient = builder.build(); + assertThat(restClient).isNotNull(); + }); + } + + @Test + void restClientShouldApplyCustomizers() { + this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> { + RestClient.Builder builder = context.getBean(RestClient.Builder.class); + RestClientCustomizer customizer = context.getBean("webClientCustomizer", RestClientCustomizer.class); + builder.build(); + then(customizer).should().customize(any(RestClient.Builder.class)); + }); + } + + @Test + void shouldGetPrototypeScopedBean() { + this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> { + RestClient.Builder firstBuilder = context.getBean(RestClient.Builder.class); + RestClient.Builder secondBuilder = context.getBean(RestClient.Builder.class); + assertThat(firstBuilder).isNotEqualTo(secondBuilder); + }); + } + + @Test + void shouldNotCreateClientBuilderIfAlreadyPresent() { + this.contextRunner.withUserConfiguration(CustomRestClientBuilderConfig.class).run((context) -> { + RestClient.Builder builder = context.getBean(RestClient.Builder.class); + assertThat(builder).isInstanceOf(MyWebClientBuilder.class); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CodecConfiguration { + + @Bean + CodecCustomizer myCodecCustomizer() { + return mock(CodecCustomizer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class RestClientCustomizerConfig { + + @Bean + RestClientCustomizer webClientCustomizer() { + return mock(RestClientCustomizer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomRestClientBuilderConfig { + + @Bean + MyWebClientBuilder myWebClientBuilder() { + return mock(MyWebClientBuilder.class); + } + + } + + interface MyWebClientBuilder extends RestClient.Builder { + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestClientCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestClientCustomizer.java new file mode 100644 index 000000000000..e19b9f5a02c7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestClientCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2023 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.web.client; + +import org.springframework.web.client.RestClient; + +/** + * Callback interface that can be used to customize a + * {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}. + * + * @author Arjen Poutsma + * @since 3.2.0 + */ +@FunctionalInterface +public interface RestClientCustomizer { + + /** + * Callback to customize a {@link org.springframework.web.client.RestClient.Builder + * RestClient.Builder} instance. + * @param restClientBuilder the client builder to customize + */ + void customize(RestClient.Builder restClientBuilder); + +} From 2d2f0502623232d994b7f0ed40d99032838fe745 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 5 Jul 2023 10:42:06 +0100 Subject: [PATCH 0101/1656] Polish 'Add initial support for RestClient' See gh-36213 --- .../NotReactiveWebApplicationCondition.java | 40 +++++++++++++++++++ .../client/RestClientAutoConfiguration.java | 28 ++++--------- .../client/RestTemplateAutoConfiguration.java | 17 -------- 3 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/NotReactiveWebApplicationCondition.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/NotReactiveWebApplicationCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/NotReactiveWebApplicationCondition.java new file mode 100644 index 000000000000..622f4ee19304 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/NotReactiveWebApplicationCondition.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; + +/** + * {@link SpringBootCondition} that applies only when running in a non-reactive web + * application. + * + * @author Phillip Webb + */ +class NotReactiveWebApplicationCondition extends NoneNestedConditions { + + NotReactiveWebApplicationCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + private static class ReactiveWebApplication { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java index bcd98abbf547..e62dc869f4de 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java @@ -21,9 +21,9 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; import org.springframework.boot.web.client.RestClientCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -33,38 +33,26 @@ /** * {@link EnableAutoConfiguration Auto-configuration} for {@link RestClient}. *

- * This will produce a {@link RestClient.Builder RestClient.Builder} bean with the - * {@code prototype} scope, meaning each injection point will receive a newly cloned - * instance of the builder. + * This will produce a {@link org.springframework.web.client.RestClient.Builder + * RestClient.Builder} bean with the {@code prototype} scope, meaning each injection point + * will receive a newly cloned instance of the builder. * * @author Arjen Poutsma * @since 3.2.0 */ @AutoConfiguration(after = HttpMessageConvertersAutoConfiguration.class) @ConditionalOnClass(RestClient.class) -@Conditional(RestClientAutoConfiguration.NotReactiveWebApplicationCondition.class) +@Conditional(NotReactiveWebApplicationCondition.class) public class RestClientAutoConfiguration { @Bean @Scope("prototype") @ConditionalOnMissingBean public RestClient.Builder webClientBuilder(ObjectProvider customizerProvider) { - RestClient.Builder builder = RestClient.builder(); + RestClient.Builder builder = RestClient.builder() + .requestFactory(ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS)); customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder; } - static class NotReactiveWebApplicationCondition extends NoneNestedConditions { - - NotReactiveWebApplicationCondition() { - super(ConfigurationPhase.PARSE_CONFIGURATION); - } - - @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) - private static class ReactiveWebApplication { - - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java index bbefd47fccf4..fab1a2c4a475 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java @@ -21,12 +21,8 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.NotReactiveWebApplicationCondition; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateRequestCustomizer; @@ -69,17 +65,4 @@ public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer res return restTemplateBuilderConfigurer.configure(builder); } - static class NotReactiveWebApplicationCondition extends NoneNestedConditions { - - NotReactiveWebApplicationCondition() { - super(ConfigurationPhase.PARSE_CONFIGURATION); - } - - @ConditionalOnWebApplication(type = Type.REACTIVE) - private static class ReactiveWebApplication { - - } - - } - } From 5e01c66552247cb30512f2f8569ac36f97808819 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 5 Jul 2023 14:11:28 +0100 Subject: [PATCH 0102/1656] Add RestClient HttpMessageConverters support Update `RestClientAutoConfiguration` to apply `HttpMessageConverters` configuration. See gh-36213 --- ...MessageConvertersRestClientCustomizer.java | 60 +++++++++++++++ .../client/RestClientAutoConfiguration.java | 11 +++ ...geConvertersRestClientCustomizerTests.java | 77 +++++++++++++++++++ .../RestClientAutoConfigurationTests.java | 64 +++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizerTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizer.java new file mode 100644 index 000000000000..c5372d5d7f84 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.boot.web.client.RestClientCustomizer; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.util.Assert; +import org.springframework.web.client.RestClient; + +/** + * {@link RestClientCustomizer} to apply {@link HttpMessageConverter + * HttpMessageConverters}. + * + * @author Phillip Webb + * @since 3.2.0 + */ +public class HttpMessageConvertersRestClientCustomizer implements RestClientCustomizer { + + private final Iterable> messageConverters; + + public HttpMessageConvertersRestClientCustomizer(HttpMessageConverter... messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); + this.messageConverters = Arrays.asList(messageConverters); + } + + HttpMessageConvertersRestClientCustomizer(HttpMessageConverters messageConverters) { + this.messageConverters = messageConverters; + } + + @Override + public void customize(RestClient.Builder restClientBuilder) { + restClientBuilder.messageConverters(this::configureMessageConverters); + } + + private void configureMessageConverters(List> messageConverters) { + if (this.messageConverters != null) { + messageConverters.clear(); + this.messageConverters.forEach(messageConverters::add); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java index e62dc869f4de..b33fc882f942 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.web.client.ClientHttpRequestFactories; import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; @@ -28,6 +29,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.web.client.RestClient; /** @@ -45,6 +48,14 @@ @Conditional(NotReactiveWebApplicationCondition.class) public class RestClientAutoConfiguration { + @Bean + @ConditionalOnMissingBean + @Order(Ordered.LOWEST_PRECEDENCE) + public HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClientCustomizer( + ObjectProvider messageConverters) { + return new HttpMessageConvertersRestClientCustomizer(messageConverters.getIfUnique()); + } + @Bean @Scope("prototype") @ConditionalOnMissingBean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizerTests.java new file mode 100644 index 000000000000..f4138baa1819 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/HttpMessageConvertersRestClientCustomizerTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.client.RestClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HttpMessageConvertersRestClientCustomizer} + * + * @author Phillip Webb + */ +class HttpMessageConvertersRestClientCustomizerTests { + + @Test + void createWhenNullMessageConvertersArrayThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new HttpMessageConvertersRestClientCustomizer((HttpMessageConverter[]) null)) + .withMessage("MessageConverters must not be null"); + } + + @Test + void createWhenNullMessageConvertersDoesNotCustomize() { + HttpMessageConverter c0 = mock(); + assertThat(apply(new HttpMessageConvertersRestClientCustomizer((HttpMessageConverters) null), c0)) + .containsExactly(c0); + } + + @Test + void customizeConfiguresMessageConverters() { + HttpMessageConverter c0 = mock(); + HttpMessageConverter c1 = mock(); + HttpMessageConverter c2 = mock(); + assertThat(apply(new HttpMessageConvertersRestClientCustomizer(c1, c2), c0)).containsExactly(c1, c2); + } + + @SuppressWarnings("unchecked") + private List> apply(HttpMessageConvertersRestClientCustomizer customizer, + HttpMessageConverter... converters) { + List> messageConverters = new ArrayList<>(Arrays.asList(converters)); + RestClient.Builder restClientBuilder = mock(); + ArgumentCaptor>>> captor = ArgumentCaptor.forClass(Consumer.class); + given(restClientBuilder.messageConverters(captor.capture())).willReturn(restClientBuilder); + customizer.customize(restClientBuilder); + captor.getValue().accept(messageConverters); + return messageConverters; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java index 2a256bb3fb31..368c29d88d63 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java @@ -16,14 +16,21 @@ package org.springframework.boot.autoconfigure.web.client; +import java.util.List; + import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.web.client.RestClientCustomizer; import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RestClient; import static org.assertj.core.api.Assertions.assertThat; @@ -77,6 +84,49 @@ void shouldNotCreateClientBuilderIfAlreadyPresent() { }); } + @Test + @SuppressWarnings("unchecked") + void restClientWhenMessageConvertersDefinedShouldHaveMessageConverters() { + this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .withUserConfiguration(RestClientConfig.class) + .run((context) -> { + RestClient restClient = context.getBean(RestClient.class); + List> expectedConverters = context.getBean(HttpMessageConverters.class) + .getConverters(); + List> actualConverters = (List>) ReflectionTestUtils + .getField(restClient, "messageConverters"); + assertThat(actualConverters).containsExactlyElementsOf(expectedConverters); + }); + } + + @Test + @SuppressWarnings("unchecked") + void restClientWhenNoMessageConvertersDefinedShouldHaveDefaultMessageConverters() { + this.contextRunner.withUserConfiguration(RestClientConfig.class).run((context) -> { + RestClient restClient = context.getBean(RestClient.class); + RestClient defaultRestClient = RestClient.builder().build(); + List> actualConverters = (List>) ReflectionTestUtils + .getField(restClient, "messageConverters"); + List> expectedConverters = (List>) ReflectionTestUtils + .getField(defaultRestClient, "messageConverters"); + assertThat(actualConverters).hasSameSizeAs(expectedConverters); + }); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + void restClientWhenHasCustomMessageConvertersShouldHaveMessageConverters() { + this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .withUserConfiguration(CustomHttpMessageConverter.class, RestClientConfig.class) + .run((context) -> { + RestClient restClient = context.getBean(RestClient.class); + List> actualConverters = (List>) ReflectionTestUtils + .getField(restClient, "messageConverters"); + assertThat(actualConverters).extracting(HttpMessageConverter::getClass) + .contains((Class) CustomHttpMessageConverter.class); + }); + } + @Configuration(proxyBeanMethods = false) static class CodecConfiguration { @@ -111,4 +161,18 @@ interface MyWebClientBuilder extends RestClient.Builder { } + @Configuration(proxyBeanMethods = false) + static class RestClientConfig { + + @Bean + RestClient restClient(RestClient.Builder restClientBuilder) { + return restClientBuilder.build(); + } + + } + + static class CustomHttpMessageConverter extends StringHttpMessageConverter { + + } + } From 7c1b168ed64b07489152ff53e18692d98f90febe Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 5 Jul 2023 20:31:21 +0100 Subject: [PATCH 0103/1656] Overhaul reference documentation for RestClient Reorder "Calling REST services" documentation and add a new section covering `RestClient`. See gh-36213 --- .../docs/asciidoc/anchor-rewrite.properties | 3 + .../src/docs/asciidoc/io/rest-client.adoc | 173 +++++++++++------- .../io/restclient/restclient/Details.java | 21 +++ .../io/restclient/restclient/MyService.java | 35 ++++ .../docs/io/restclient/restclient/Details.kt | 19 ++ .../io/restclient/restclient/MyService.kt | 38 ++++ 6 files changed, 222 insertions(+), 67 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/Details.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/MyService.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/Details.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/MyService.kt diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties index 0c84776b2f36..44164b239812 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties @@ -1020,3 +1020,6 @@ howto.testing.testcontainers.dynamic-properties=features.testing.testcontainers. # gh-32905 container-images.efficient-images.unpacking=deployment.efficient.unpacking + +# Spring Boot 3.1 - 3.2 migrations +io.rest-client.resttemplate.http-client=io.rest-client.clienthttprequestfactory diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc index 0147af61f5e6..13c58443247a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc @@ -1,80 +1,23 @@ [[io.rest-client]] == Calling REST Services -If your application calls remote REST services, Spring Boot makes that very convenient using a `RestTemplate` or a `WebClient`. - -[[io.rest-client.resttemplate]] -=== RestTemplate -If you need to call remote REST services from your application, you can use the Spring Framework's {spring-framework-api}/web/client/RestTemplate.html[`RestTemplate`] class. -Since `RestTemplate` instances often need to be customized before being used, Spring Boot does not provide any single auto-configured `RestTemplate` bean. -It does, however, auto-configure a `RestTemplateBuilder`, which can be used to create `RestTemplate` instances when needed. -The auto-configured `RestTemplateBuilder` ensures that sensible `HttpMessageConverters` are applied to `RestTemplate` instances. - -The following code shows a typical example: - -include::code:MyService[] - -`RestTemplateBuilder` includes a number of useful methods that can be used to quickly configure a `RestTemplate`. -For example, to add BASIC authentication support, you can use `builder.basicAuthentication("user", "password").build()`. - - - -[[io.rest-client.resttemplate.http-client]] -==== RestTemplate HTTP Client -Spring Boot will auto-detect which HTTP client to use with `RestTemplate` depending on the libraries available on the application classpath. -In order of preference, the following clients are supported: - -. Apache HttpClient -. OkHttp -. Jetty HttpClient -. Simple JDK client (`HttpURLConnection`) - -If multiple clients are available on the classpath, the most preferred client will be used. - - - -[[io.rest-client.resttemplate.customization]] -==== RestTemplate Customization -There are three main approaches to `RestTemplate` customization, depending on how broadly you want the customizations to apply. - -To make the scope of any customizations as narrow as possible, inject the auto-configured `RestTemplateBuilder` and then call its methods as required. -Each method call returns a new `RestTemplateBuilder` instance, so the customizations only affect this use of the builder. - -To make an application-wide, additive customization, use a `RestTemplateCustomizer` bean. -All such beans are automatically registered with the auto-configured `RestTemplateBuilder` and are applied to any templates that are built with it. - -The following example shows a customizer that configures the use of a proxy for all hosts except `192.168.0.5`: - -include::code:MyRestTemplateCustomizer[] - -Finally, you can define your own `RestTemplateBuilder` bean. -Doing so will replace the auto-configured builder. -If you want any `RestTemplateCustomizer` beans to be applied to your custom builder, as the auto-configuration would have done, configure it using a `RestTemplateBuilderConfigurer`. -The following example exposes a `RestTemplateBuilder` that matches what Spring Boot's auto-configuration would have done, except that custom connect and read timeouts are also specified: - -include::code:MyRestTemplateBuilderConfiguration[] - -The most extreme (and rarely used) option is to create your own `RestTemplateBuilder` bean without using a configurer. -In addition to replacing the auto-configured builder, this also prevents any `RestTemplateCustomizer` beans from being used. - - - -[[io.rest-client.resttemplate.ssl]] -==== RestTemplate SSL Support -If you need custom SSL configuration on the `RestTemplate`, you can apply an <> to the `RestTemplateBuilder` as shown in this example: - -include::code:MyService[] +Spring Boot provides various convenient ways to call remote REST services. +If you are developing a non-blocking reactive application and you're using Spring WebFlux, then you can use `WebClient`. +If you prefer blocking APIs then you can use `RestClient` or `RestTemplate`. [[io.rest-client.webclient]] === WebClient -If you have Spring WebFlux on your classpath, you can also choose to use `WebClient` to call remote REST services. -Compared to `RestTemplate`, this client has a more functional feel and is fully reactive. +If you have Spring WebFlux on your classpath we recommend that you use `WebClient` to call remote REST services. +The `WebClient` interface provides a functional style API and is fully reactive. You can learn more about the `WebClient` in the dedicated {spring-framework-docs}/web-reactive.html#webflux-client[section in the Spring Framework docs]. -Spring Boot creates and pre-configures a `WebClient.Builder` for you. +TIP: If you are not writing a reactive Spring WebFlux application you can use the a <> instead of a `WebClient`. +This provides a similar functional API, but is blocking rather than reactive. + +Spring Boot creates and pre-configures a prototype `WebClient.Builder` bean for you. It is strongly advised to inject it in your components and use it to create `WebClient` instances. -Spring Boot is configuring that builder to share HTTP resources, reflect codecs setup in the same fashion as the server ones (see <>), and more. +Spring Boot is configuring that builder to share HTTP resources and reflect codecs setup in the same fashion as the server ones (see <>), and more. The following code shows a typical example: @@ -131,3 +74,99 @@ The following code shows a typical example: include::code:MyService[] + + +[[io.rest-client.restclient]] +=== RestClient +If you are not using Spring WebFlux or Project Reactor in your application we recommend that you use `RestClient` to call remote REST services. + +The `RestClient` interface provides a functional style blocking API. + +Spring Boot creates and pre-configures a prototype `RestClient.Builder` bean for you. +It is strongly advised to inject it in your components and use it to create `RestClient` instances. +Spring Boot is configuring that builder with `HttpMessageConverters` and an appropriate `ClientHttpRequestFactory`. + +The following code shows a typical example: + +include::code:MyService[] + + + +[[io.rest-client.restclient.customization]] +==== RestClient Customization +There are three main approaches to `RestClient` customization, depending on how broadly you want the customizations to apply. + +To make the scope of any customizations as narrow as possible, inject the auto-configured `RestClient.Builder` and then call its methods as required. +`RestClient.Builder` instances are stateful: Any change on the builder is reflected in all clients subsequently created with it. +If you want to create several clients with the same builder, you can also consider cloning the builder with `RestClient.Builder other = builder.clone();`. + +To make an application-wide, additive customization to all `RestClient.Builder` instances, you can declare `RestClientCustomizer` beans and change the `RestClient.Builder` locally at the point of injection. + +Finally, you can fall back to the original API and use `RestClient.create()`. +In that case, no auto-configuration or `RestClientCustomizer` is applied. + + + +[[io.rest-client.resttemplate]] +=== RestTemplate +Spring Framework's {spring-framework-api}/web/client/RestTemplate.html[`RestTemplate`] class predates `RestClient` and is the classic way that many applications use to call remote REST services. +You might choose to use `RestTemplate` when you have existing code that you don't want to migrate to `RestClient`, or because you're already familiar with the `RestTemplate` API. + +Since `RestTemplate` instances often need to be customized before being used, Spring Boot does not provide any single auto-configured `RestTemplate` bean. +It does, however, auto-configure a `RestTemplateBuilder`, which can be used to create `RestTemplate` instances when needed. +The auto-configured `RestTemplateBuilder` ensures that sensible `HttpMessageConverters` and an appropriate `ClientHttpRequestFactory` are applied to `RestTemplate` instances. + +The following code shows a typical example: + +include::code:MyService[] + +`RestTemplateBuilder` includes a number of useful methods that can be used to quickly configure a `RestTemplate`. +For example, to add BASIC authentication support, you can use `builder.basicAuthentication("user", "password").build()`. + + + +[[io.rest-client.resttemplate.customization]] +==== RestTemplate Customization +There are three main approaches to `RestTemplate` customization, depending on how broadly you want the customizations to apply. + +To make the scope of any customizations as narrow as possible, inject the auto-configured `RestTemplateBuilder` and then call its methods as required. +Each method call returns a new `RestTemplateBuilder` instance, so the customizations only affect this use of the builder. + +To make an application-wide, additive customization, use a `RestTemplateCustomizer` bean. +All such beans are automatically registered with the auto-configured `RestTemplateBuilder` and are applied to any templates that are built with it. + +The following example shows a customizer that configures the use of a proxy for all hosts except `192.168.0.5`: + +include::code:MyRestTemplateCustomizer[] + +Finally, you can define your own `RestTemplateBuilder` bean. +Doing so will replace the auto-configured builder. +If you want any `RestTemplateCustomizer` beans to be applied to your custom builder, as the auto-configuration would have done, configure it using a `RestTemplateBuilderConfigurer`. +The following example exposes a `RestTemplateBuilder` that matches what Spring Boot's auto-configuration would have done, except that custom connect and read timeouts are also specified: + +include::code:MyRestTemplateBuilderConfiguration[] + +The most extreme (and rarely used) option is to create your own `RestTemplateBuilder` bean without using a configurer. +In addition to replacing the auto-configured builder, this also prevents any `RestTemplateCustomizer` beans from being used. + + + +[[io.rest-client.resttemplate.ssl]] +==== RestTemplate SSL Support +If you need custom SSL configuration on the `RestTemplate`, you can apply an <> to the `RestTemplateBuilder` as shown in this example: + +include::code:MyService[] + + + +[[io.rest-client.clienthttprequestfactory]] +=== HTTP Client Detection for RestClient and RestTemplate +Spring Boot will auto-detect which HTTP client to use with `RestClient` and `RestTemplate` depending on the libraries available on the application classpath. +In order of preference, the following clients are supported: + +. Apache HttpClient +. OkHttp +. Jetty HttpClient +. Simple JDK client (`HttpURLConnection`) + +If multiple clients are available on the classpath, the most preferred client will be used. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/Details.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/Details.java new file mode 100644 index 000000000000..28c038969b28 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/Details.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient; + +public class Details { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/MyService.java new file mode 100644 index 000000000000..98bd2049406c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/MyService.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient; + +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class MyService { + + private final RestClient restClient; + + public MyService(RestClient.Builder restClientBuilder) { + this.restClient = restClientBuilder.baseUrl("https://example.org").build(); + } + + public Details someRestCall(String name) { + return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/Details.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/Details.kt new file mode 100644 index 000000000000..219b0a9ffe29 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/Details.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient + +class Details diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/MyService.kt new file mode 100644 index 000000000000..cb1854c03c5e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/MyService.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.io.restclient.restclient + +import org.springframework.boot.docs.io.restclient.restclient.ssl.Details +import org.springframework.stereotype.Service +import org.springframework.web.client.RestClient + +@Service +class MyService(restClientBuilder: RestClient.Builder) { + + private val restClient: RestClient + + init { + restClient = restClientBuilder.baseUrl("https://example.org").build() + } + + fun someRestCall(name: String?): Details { + return restClient.get().uri("/{name}/details", name) + .retrieve().body(Details::class.java)!! + } + +} + From cfdc173e34363b490d00c9825145d4ff9ba63905 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 5 Jul 2023 13:09:56 +0100 Subject: [PATCH 0104/1656] Add RestClient SSL support Add `RestClientSsl` support class to help apply an `SslBundle` to a `RestClient.Builder`. See gh-36213 --- .../client/AutoConfiguredRestClientSsl.java | 55 +++++++++++++++ .../web/client/RestClientSsl.java | 68 +++++++++++++++++++ .../src/docs/asciidoc/io/rest-client.adoc | 16 +++++ .../restclient/restclient/ssl/MyService.java | 36 ++++++++++ .../restclient/ssl/settings/Details.java | 21 ++++++ .../restclient/ssl/settings/MyService.java | 45 ++++++++++++ .../io/restclient/restclient/ssl/Details.kt | 19 ++++++ .../io/restclient/restclient/ssl/MyService.kt | 39 +++++++++++ .../restclient/ssl/settings/Details.kt | 19 ++++++ .../restclient/ssl/settings/MyService.kt | 46 +++++++++++++ 10 files changed, 364 insertions(+) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.kt create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.kt diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java new file mode 100644 index 000000000000..8fc9dd9663b2 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.function.Consumer; + +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +/** + * An auto-configured {@link RestClientSsl} implementation. + * + * @author Phillip Webb + */ +class AutoConfiguredRestClientSsl implements RestClientSsl { + + private final SslBundles sslBundles; + + AutoConfiguredRestClientSsl(SslBundles sslBundles) { + this.sslBundles = sslBundles; + } + + @Override + public Consumer fromBundle(String bundleName) { + return fromBundle(this.sslBundles.getBundle(bundleName)); + } + + @Override + public Consumer fromBundle(SslBundle bundle) { + return (builder) -> { + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(bundle); + ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings); + builder.requestFactory(requestFactory); + }; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java new file mode 100644 index 000000000000..bcf97ddca19e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.web.client; + +import java.util.function.Consumer; + +import org.springframework.boot.ssl.NoSuchSslBundleException; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +/** + * Interface that can be used to {@link RestClient.Builder#apply apply} SSL configuration + * to a {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}. + *

+ * Typically used as follows:

+ * @Bean
+ * public MyBean myBean(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
+ *     RestClient restClientrestClient= restClientBuilder.apply(ssl.forBundle("mybundle")).build();
+ *     return new MyBean(webClient);
+ * }
+ * 
NOTE: Apply SSL configuration will replace any previously + * {@link RestClient.Builder#requestFactory configured} {@link ClientHttpRequestFactory}. + * If you need to configure {@link ClientHttpRequestFactory} with more than just SSL + * consider using a {@link ClientHttpRequestFactorySettings} with + * {@link ClientHttpRequestFactories}. + * + * @author Phillip Webb + * @since 3.2.0 + */ +public interface RestClientSsl { + + /** + * Return a {@link Consumer} that will apply SSL configuration for the named + * {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder + * RestClient.Builder}. + * @param bundleName the name of the SSL bundle to apply + * @return a {@link Consumer} to apply the configuration + * @throws NoSuchSslBundleException if a bundle with the provided name does not exist + */ + Consumer fromBundle(String bundleName) throws NoSuchSslBundleException; + + /** + * Return a {@link Consumer} that will apply SSL configuration for the + * {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder + * RestClient.Builder}. + * @param bundle the SSL bundle to apply + * @return a {@link Consumer} to apply the configuration + */ + Consumer fromBundle(SslBundle bundle); + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc index 13c58443247a..b371623f77c4 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc @@ -107,6 +107,22 @@ In that case, no auto-configuration or `RestClientCustomizer` is applied. +[[io.rest-client.restclient.ssl]] +==== RestClient SSL Support +If you need custom SSL configuration on the `ClientHttpRequestFactory` used by the `RestClient`, you can inject a `RestClientSsl` instance that can be used with the builder's `apply` method. + +The `RestClientSsl` interface provides access to any <> that you have defined in your `application.properties` or `application.yaml` file. + +The following code shows a typical example: + +include::code:MyService[] + +If you need to apply other customization in addition to an SSL bundle, you can use the `ClientHttpRequestFactorySettings` class with `ClientHttpRequestFactories`: + +include::code:settings/MyService[] + + + [[io.rest-client.resttemplate]] === RestTemplate Spring Framework's {spring-framework-api}/web/client/RestTemplate.html[`RestTemplate`] class predates `RestClient` and is the classic way that many applications use to call remote REST services. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.java new file mode 100644 index 000000000000..0fa7fa50cbba --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient.ssl; + +import org.springframework.boot.autoconfigure.web.client.RestClientSsl; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class MyService { + + private final RestClient restClient; + + public MyService(RestClient.Builder restClientBuilder, RestClientSsl ssl) { + this.restClient = restClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build(); + } + + public Details someRestCall(String name) { + return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.java new file mode 100644 index 000000000000..1b0bbdb7533b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient.ssl.settings; + +public class Details { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.java new file mode 100644 index 000000000000..8fef86df53e3 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient.ssl.settings; + +import java.time.Duration; + +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class MyService { + + private final RestClient restClient; + + public MyService(RestClient.Builder restClientBuilder, SslBundles sslBundles) { + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS + .withReadTimeout(Duration.ofMinutes(2)) + .withSslBundle(sslBundles.getBundle("mybundle")); + ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings); + this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build(); + } + + public Details someRestCall(String name) { + return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.kt new file mode 100644 index 000000000000..613bbadb3fd7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/Details.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient.ssl + +class Details diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.kt new file mode 100644 index 000000000000..220a44252e7f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/MyService.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient.ssl + +import org.springframework.boot.autoconfigure.web.client.RestClientSsl +import org.springframework.boot.docs.io.restclient.restclient.ssl.settings.Details +import org.springframework.stereotype.Service +import org.springframework.web.client.RestClient + +@Service +class MyService(restClientBuilder: RestClient.Builder, ssl: RestClientSsl) { + + private val restClient: RestClient + + init { + restClient = restClientBuilder.baseUrl("https://example.org") + .apply(ssl.fromBundle("mybundle")).build() + } + + fun someRestCall(name: String?): Details { + return restClient.get().uri("/{name}/details", name) + .retrieve().body(Details::class.java)!! + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.kt new file mode 100644 index 000000000000..3a73e355e1c1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/Details.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient.ssl.settings + +class Details diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.kt new file mode 100644 index 000000000000..e153262f8248 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/restclient/ssl/settings/MyService.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 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.docs.io.restclient.restclient.ssl.settings + +import org.springframework.boot.ssl.SslBundles +import org.springframework.boot.web.client.ClientHttpRequestFactories +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings +import org.springframework.stereotype.Service +import org.springframework.web.client.RestClient +import java.time.Duration + +@Service +class MyService(restClientBuilder: RestClient.Builder, sslBundles: SslBundles) { + + private val restClient: RestClient + + init { + val settings = ClientHttpRequestFactorySettings.DEFAULTS + .withReadTimeout(Duration.ofMinutes(2)) + .withSslBundle(sslBundles.getBundle("mybundle")) + val requestFactory = ClientHttpRequestFactories.get(settings) + restClient = restClientBuilder + .baseUrl("https://example.org") + .requestFactory(requestFactory).build() + } + + fun someRestCall(name: String?): Details { + return restClient.get().uri("/{name}/details", name).retrieve().body(Details::class.java)!! + } + +} + From 89880a773ca1ac48f09af5d7d8f379f91b0075b7 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 5 Jul 2023 18:56:49 -0400 Subject: [PATCH 0105/1656] Add RestClientAutoConfiguration to AutoConfiguration.imports See gh-36249 --- ....springframework.boot.autoconfigure.AutoConfiguration.imports | 1 + 1 file changed, 1 insertion(+) 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 f0018406978d..5997fb8ea2db 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 @@ -123,6 +123,7 @@ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration +org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration From 3b90919313bd3294c848f4c6c4829a5feb8ffeb8 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 6 Jul 2023 12:22:54 +0100 Subject: [PATCH 0106/1656] Polish RestClient auto-config and tests For consistency, replace webClient and WebClient with restClient and RestClient. This also address a bean name clash between RestClientAutoConfiguration's RestClient.Builder bean and WebClientAutoConfiguration's WebClient.Builder bean that were both previously named webClientBuilder. --- .../web/client/RestClientAutoConfiguration.java | 2 +- .../web/client/RestClientAutoConfigurationTests.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java index b33fc882f942..1543caa80da0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java @@ -59,7 +59,7 @@ public HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClient @Bean @Scope("prototype") @ConditionalOnMissingBean - public RestClient.Builder webClientBuilder(ObjectProvider customizerProvider) { + public RestClient.Builder restClientBuilder(ObjectProvider customizerProvider) { RestClient.Builder builder = RestClient.builder() .requestFactory(ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS)); customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java index 368c29d88d63..b64a62d1f3c3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java @@ -61,7 +61,7 @@ void shouldCreateBuilder() { void restClientShouldApplyCustomizers() { this.contextRunner.withUserConfiguration(RestClientCustomizerConfig.class).run((context) -> { RestClient.Builder builder = context.getBean(RestClient.Builder.class); - RestClientCustomizer customizer = context.getBean("webClientCustomizer", RestClientCustomizer.class); + RestClientCustomizer customizer = context.getBean("restClientCustomizer", RestClientCustomizer.class); builder.build(); then(customizer).should().customize(any(RestClient.Builder.class)); }); @@ -80,7 +80,7 @@ void shouldGetPrototypeScopedBean() { void shouldNotCreateClientBuilderIfAlreadyPresent() { this.contextRunner.withUserConfiguration(CustomRestClientBuilderConfig.class).run((context) -> { RestClient.Builder builder = context.getBean(RestClient.Builder.class); - assertThat(builder).isInstanceOf(MyWebClientBuilder.class); + assertThat(builder).isInstanceOf(MyRestClientBuilder.class); }); } @@ -141,7 +141,7 @@ CodecCustomizer myCodecCustomizer() { static class RestClientCustomizerConfig { @Bean - RestClientCustomizer webClientCustomizer() { + RestClientCustomizer restClientCustomizer() { return mock(RestClientCustomizer.class); } @@ -151,13 +151,13 @@ RestClientCustomizer webClientCustomizer() { static class CustomRestClientBuilderConfig { @Bean - MyWebClientBuilder myWebClientBuilder() { - return mock(MyWebClientBuilder.class); + MyRestClientBuilder myRestClientBuilder() { + return mock(MyRestClientBuilder.class); } } - interface MyWebClientBuilder extends RestClient.Builder { + interface MyRestClientBuilder extends RestClient.Builder { } From 3bbfee5e938f03048c1b8b0cc976b536633df73f Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 29 Jun 2023 15:11:20 +0200 Subject: [PATCH 0107/1656] Support JDK HttpClient in ClientHttpRequestFactories See gh-36118 --- .../client/ClientHttpRequestFactories.java | 37 +++++++++++++ ...entHttpRequestFactoriesJdkClientTests.java | 52 +++++++++++++++++++ ...ClientHttpRequestFactoriesSimpleTests.java | 4 +- ...geSenderBuilderSimpleIntegrationTests.java | 2 +- 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java index 347f6c4b0c58..63b6595ae9b3 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java @@ -49,6 +49,7 @@ import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.http.client.JettyClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -79,6 +80,10 @@ public final class ClientHttpRequestFactories { private static final boolean JETTY_CLIENT_PRESENT = ClassUtils.isPresent(JETTY_CLIENT_CLASS, null); + static final String JDK_CLIENT_CLASS = "java.net.http.HttpClient"; + + private static final boolean JDK_CLIENT_PRESENT = ClassUtils.isPresent(JDK_CLIENT_CLASS, null); + private ClientHttpRequestFactories() { } @@ -99,6 +104,9 @@ public static ClientHttpRequestFactory get(ClientHttpRequestFactorySettings sett if (JETTY_CLIENT_PRESENT) { return Jetty.get(settings); } + if (JDK_CLIENT_PRESENT) { + return Jdk.get(settings); + } return Simple.get(settings); } @@ -126,6 +134,9 @@ public static T get(Class requestFactory if (requestFactoryType == JettyClientHttpRequestFactory.class) { return (T) Jetty.get(settings); } + if (requestFactoryType == JdkClientHttpRequestFactory.class) { + return (T) Jdk.get(settings); + } if (requestFactoryType == SimpleClientHttpRequestFactory.class) { return (T) Simple.get(settings); } @@ -254,6 +265,32 @@ private static JettyClientHttpRequestFactory createRequestFactory(SslBundle sslB } + /** + * Support for {@link JdkClientHttpRequestFactory}. + */ + static class Jdk { + + static JdkClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) { + java.net.http.HttpClient httpClient = createHttpClient(settings.connectTimeout(), settings.sslBundle()); + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout); + return requestFactory; + } + + private static java.net.http.HttpClient createHttpClient(Duration connectTimeout, SslBundle sslBundle) { + java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder(); + if (connectTimeout != null) { + builder.connectTimeout(connectTimeout); + } + if (sslBundle != null) { + builder.sslContext(sslBundle.createSslContext()); + } + return builder.build(); + } + + } + /** * Support for {@link SimpleClientHttpRequestFactory}. */ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java new file mode 100644 index 000000000000..a3dd693ec7b2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 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.web.client; + +import java.net.http.HttpClient; +import java.time.Duration; + +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * Tests for {@link ClientHttpRequestFactories} when JDK HttpClient is the + * predominant HTTP client. + * + * @author Andy Wilkinson + */ +@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar" }) +class ClientHttpRequestFactoriesJdkClientTests + extends AbstractClientHttpRequestFactoriesTests { + + ClientHttpRequestFactoriesJdkClientTests() { + super(JdkClientHttpRequestFactory.class); + } + + @Override + protected long connectTimeout(JdkClientHttpRequestFactory requestFactory) { + HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient"); + return httpClient.connectTimeout().map(Duration::toMillis).orElse(-1L); + } + + @Override + @SuppressWarnings("unchecked") + protected long readTimeout(JdkClientHttpRequestFactory requestFactory) { + return ((Duration) ReflectionTestUtils.getField(requestFactory, "readTimeout")).toMillis(); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java index f00882bc7df5..189901f20c42 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -26,7 +26,7 @@ * * @author Andy Wilkinson */ -@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar", "jetty-client-*.jar" }) +@ClassPathExclusions(files = {"httpclient5-*.jar", "jetty-client-*.jar", "okhttp-*.jar"}, packages = "java.net.http") class ClientHttpRequestFactoriesSimpleTests extends AbstractClientHttpRequestFactoriesTests { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java index 0014d47cd7ef..5872c3a092dc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java @@ -34,7 +34,7 @@ * * @author Stephane Nicoll */ -@ClassPathExclusions(files = { "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" }) +@ClassPathExclusions(files = { "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" }, packages = "java.net.http") class HttpWebServiceMessageSenderBuilderSimpleIntegrationTests { private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder(); From bb2c4cc742b9b2770ea3809d8c0d9b74855d514e Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 4 Jul 2023 18:12:27 +0100 Subject: [PATCH 0108/1656] Polish "Support JDK HttpClient in ClientHttpRequestFactories" See gh-36118 --- .../client/ClientHttpRequestFactories.java | 7 --- ...entHttpRequestFactoriesJdkClientTests.java | 52 ------------------- ...ClientHttpRequestFactoriesSimpleTests.java | 2 +- .../ClientHttpRequestFactoriesTests.java | 8 +++ ...geSenderBuilderSimpleIntegrationTests.java | 2 +- 5 files changed, 10 insertions(+), 61 deletions(-) delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java index 63b6595ae9b3..1af4d92bc901 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java @@ -80,10 +80,6 @@ public final class ClientHttpRequestFactories { private static final boolean JETTY_CLIENT_PRESENT = ClassUtils.isPresent(JETTY_CLIENT_CLASS, null); - static final String JDK_CLIENT_CLASS = "java.net.http.HttpClient"; - - private static final boolean JDK_CLIENT_PRESENT = ClassUtils.isPresent(JDK_CLIENT_CLASS, null); - private ClientHttpRequestFactories() { } @@ -104,9 +100,6 @@ public static ClientHttpRequestFactory get(ClientHttpRequestFactorySettings sett if (JETTY_CLIENT_PRESENT) { return Jetty.get(settings); } - if (JDK_CLIENT_PRESENT) { - return Jdk.get(settings); - } return Simple.get(settings); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java deleted file mode 100644 index a3dd693ec7b2..000000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesJdkClientTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012-2023 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.web.client; - -import java.net.http.HttpClient; -import java.time.Duration; - -import org.springframework.boot.testsupport.classpath.ClassPathExclusions; -import org.springframework.http.client.JdkClientHttpRequestFactory; -import org.springframework.test.util.ReflectionTestUtils; - -/** - * Tests for {@link ClientHttpRequestFactories} when JDK HttpClient is the - * predominant HTTP client. - * - * @author Andy Wilkinson - */ -@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar" }) -class ClientHttpRequestFactoriesJdkClientTests - extends AbstractClientHttpRequestFactoriesTests { - - ClientHttpRequestFactoriesJdkClientTests() { - super(JdkClientHttpRequestFactory.class); - } - - @Override - protected long connectTimeout(JdkClientHttpRequestFactory requestFactory) { - HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient"); - return httpClient.connectTimeout().map(Duration::toMillis).orElse(-1L); - } - - @Override - @SuppressWarnings("unchecked") - protected long readTimeout(JdkClientHttpRequestFactory requestFactory) { - return ((Duration) ReflectionTestUtils.getField(requestFactory, "readTimeout")).toMillis(); - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java index 189901f20c42..bb4425484511 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesSimpleTests.java @@ -26,7 +26,7 @@ * * @author Andy Wilkinson */ -@ClassPathExclusions(files = {"httpclient5-*.jar", "jetty-client-*.jar", "okhttp-*.jar"}, packages = "java.net.http") +@ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar", "okhttp-*.jar" }) class ClientHttpRequestFactoriesSimpleTests extends AbstractClientHttpRequestFactoriesTests { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java index 546b862d987a..239023e8b9c0 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesTests.java @@ -27,6 +27,7 @@ import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -75,6 +76,13 @@ void getOfOkHttpFactoryReturnsOkHttpFactory() { assertThat(requestFactory).isInstanceOf(OkHttp3ClientHttpRequestFactory.class); } + @Test + void getOfJdkFactoryReturnsJdkFactory() { + ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(JdkClientHttpRequestFactory.class, + ClientHttpRequestFactorySettings.DEFAULTS); + assertThat(requestFactory).isInstanceOf(JdkClientHttpRequestFactory.class); + } + @Test void getOfUnknownTypeCreatesFactory() { ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(TestClientHttpRequestFactory.class, diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java index 5872c3a092dc..6c3a0b2ef18e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java @@ -34,7 +34,7 @@ * * @author Stephane Nicoll */ -@ClassPathExclusions(files = { "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" }, packages = "java.net.http") +@ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar", "okhttp*.jar" }) class HttpWebServiceMessageSenderBuilderSimpleIntegrationTests { private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder(); From 62674de4728ad542a57e4c438eed1c20c3cde07c Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 6 Jul 2023 15:57:25 -0400 Subject: [PATCH 0109/1656] Skip int conversion in ClientHttpRequestFactories @poutsma added `JdkClientHttpRequestFactory.setReadTimeout(Duration)` so the conversion to and from int is no longer needed. See gh-36270 --- .../boot/web/client/ClientHttpRequestFactories.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java index 0d9847c6237d..93e4e407c862 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java @@ -285,7 +285,7 @@ static JdkClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings java.net.http.HttpClient httpClient = createHttpClient(settings.connectTimeout(), settings.sslBundle()); JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout); + map.from(settings::readTimeout).to(requestFactory::setReadTimeout); return requestFactory; } From f4e05c91c74ee65e242febd4d46543e8f74c26c7 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 6 Jul 2023 20:17:10 +0100 Subject: [PATCH 0110/1656] Use converter beans in preference to ObjectToObjectConverter Previously, with the converter beans in a conversion service that appears after the bean factory's conversion service, they would not be called for a conversion that could be handled by the ObjectToObjectConverter in the bean factory's conversion service. This commit creates a new FormattingConversionService that is empty except for the converter beans and places it first in the list. It's followed by the bean factory's conversion service. The shared application conversion service is added to the end of the list to pick up any conversions that the previous two services could not handle. This should maintain backwards compatibility with the previous arrangement where the converter beans were added to an application conversion service that went after the bean factory's conversion service. Fixes gh-34631 --- .../properties/ConversionServiceDeducer.java | 16 ++++-- .../ConfigurationPropertiesTests.java | 57 ++++++++++++------- .../ConversionServiceDeducerTests.java | 10 ++-- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java index 3593faf4230a..775680c794ae 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java @@ -31,6 +31,7 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; +import org.springframework.format.support.FormattingConversionService; /** * Utility to deduce the {@link ConversionService} to use for configuration properties @@ -59,15 +60,22 @@ List getConversionServices() { private List getConversionServices(ConfigurableApplicationContext applicationContext) { List conversionServices = new ArrayList<>(); - if (applicationContext.getBeanFactory().getConversionService() != null) { - conversionServices.add(applicationContext.getBeanFactory().getConversionService()); - } ConverterBeans converterBeans = new ConverterBeans(applicationContext); if (!converterBeans.isEmpty()) { - ApplicationConversionService beansConverterService = new ApplicationConversionService(); + FormattingConversionService beansConverterService = new FormattingConversionService(); converterBeans.addTo(beansConverterService); conversionServices.add(beansConverterService); } + if (applicationContext.getBeanFactory().getConversionService() != null) { + conversionServices.add(applicationContext.getBeanFactory().getConversionService()); + } + if (!converterBeans.isEmpty()) { + // Converters beans used to be added to a custom ApplicationConversionService + // after the BeanFactory's ConversionService. For backwards compatibility, we + // add an ApplicationConversationService as a fallback in the same place in + // the list. + conversionServices.add(ApplicationConversionService.getSharedInstance()); + } return conversionServices; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index a0683316bbc1..0e7a8fe02a0d 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -644,24 +644,36 @@ void customProtocolResolver() { @Test void loadShouldUseConverterBean() { - prepareConverterContext(ConverterConfiguration.class, PersonProperties.class); + prepareConverterContext(PersonConverterConfiguration.class, PersonProperties.class); Person person = this.context.getBean(PersonProperties.class).getPerson(); assertThat(person.firstName).isEqualTo("John"); assertThat(person.lastName).isEqualTo("Smith"); } @Test - void loadWhenBeanFactoryConversionServiceAndConverterBean() { + void loadWhenBeanFactoryConversionServiceAndConverterBeanCanUseBeanFactoryConverter() { DefaultConversionService conversionService = new DefaultConversionService(); conversionService.addConverter(new AlienConverter()); this.context.getBeanFactory().setConversionService(conversionService); - load(new Class[] { ConverterConfiguration.class, PersonAndAlienProperties.class }, "test.person=John Smith", - "test.alien=Alf Tanner"); + load(new Class[] { PersonConverterConfiguration.class, PersonAndAlienProperties.class }, + "test.person=John Smith", "test.alien=Alf Tanner"); PersonAndAlienProperties properties = this.context.getBean(PersonAndAlienProperties.class); assertThat(properties.getPerson().firstName).isEqualTo("John"); assertThat(properties.getPerson().lastName).isEqualTo("Smith"); - assertThat(properties.getAlien().firstName).isEqualTo("Alf"); - assertThat(properties.getAlien().lastName).isEqualTo("Tanner"); + assertThat(properties.getAlien().name).isEqualTo("rennaT flA"); + } + + @Test + void loadWhenBeanFactoryConversionServiceAndConverterBeanCanUseConverterBean() { + DefaultConversionService conversionService = new DefaultConversionService(); + conversionService.addConverter(new PersonConverter()); + this.context.getBeanFactory().setConversionService(conversionService); + load(new Class[] { AlienConverterConfiguration.class, PersonAndAlienProperties.class }, + "test.person=John Smith", "test.alien=Alf Tanner"); + PersonAndAlienProperties properties = this.context.getBean(PersonAndAlienProperties.class); + assertThat(properties.getPerson().firstName).isEqualTo("John"); + assertThat(properties.getPerson().lastName).isEqualTo("Smith"); + assertThat(properties.getAlien().name).isEqualTo("rennaT flA"); } @Test @@ -1440,7 +1452,7 @@ public Resource resolve(String location, ResourceLoader resourceLoader) { } @Configuration(proxyBeanMethods = false) - static class ConverterConfiguration { + static class PersonConverterConfiguration { @Bean @ConfigurationPropertiesBinding @@ -1450,6 +1462,17 @@ Converter personConverter() { } + @Configuration(proxyBeanMethods = false) + static class AlienConverterConfiguration { + + @Bean + @ConfigurationPropertiesBinding + Converter alienConverter() { + return new AlienConverter(); + } + + } + @Configuration(proxyBeanMethods = false) static class NonQualifiedConverterConfiguration { @@ -2398,8 +2421,7 @@ static class AlienConverter implements Converter { @Override public Alien convert(String source) { - String[] content = StringUtils.split(source, " "); - return new Alien(content[0], content[1]); + return new Alien(new StringBuilder(source).reverse().toString()); } } @@ -2467,21 +2489,14 @@ String getLastName() { static class Alien { - private final String firstName; - - private final String lastName; - - Alien(String firstName, String lastName) { - this.firstName = firstName; - this.lastName = lastName; - } + private final String name; - String getFirstName() { - return this.firstName; + Alien(String name) { + this.name = name; } - String getLastName() { - return this.lastName; + String getName() { + return this.name; } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java index 30286fe950b5..06b27871c9ed 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -30,6 +30,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; +import org.springframework.format.support.FormattingConversionService; import static org.assertj.core.api.Assertions.assertThat; @@ -69,14 +70,15 @@ void getConversionServiceWhenHasNoConversionServiceBeanAndNoQualifiedBeansAndBea } @Test - void getConversionServiceWhenHasQualifiedConverterBeansContainsCustomizedApplicationService() { + void getConversionServiceWhenHasQualifiedConverterBeansContainsCustomizedFormattingService() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext( CustomConverterConfiguration.class); ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext); List conversionServices = deducer.getConversionServices(); - assertThat(conversionServices).hasSize(1); - assertThat(conversionServices.get(0)).isNotSameAs(ApplicationConversionService.getSharedInstance()); + assertThat(conversionServices).hasSize(2); + assertThat(conversionServices.get(0)).isExactlyInstanceOf(FormattingConversionService.class); assertThat(conversionServices.get(0).canConvert(InputStream.class, OutputStream.class)).isTrue(); + assertThat(conversionServices.get(1)).isSameAs(ApplicationConversionService.getSharedInstance()); } @Configuration(proxyBeanMethods = false) From a285a7389c7b72f8a7fb43915396cdca82f3214b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 10 Jul 2023 14:02:25 +0100 Subject: [PATCH 0111/1656] Start building against Spring LDAP 3.2.0 snapshots See gh-36299 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 58ab83852479..e2be6869345d 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1433,7 +1433,7 @@ bom { ] } } - library("Spring LDAP", "3.1.0") { + library("Spring LDAP", "3.2.0-SNAPSHOT") { group("org.springframework.ldap") { modules = [ "spring-ldap-core", From 7e5bfc4b2e88eb4a841df3108ee4344d2e486817 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 10 Jul 2023 16:19:05 +0100 Subject: [PATCH 0112/1656] Start building against Reactor Bom 2023.0.0 snapshots See gh-36300 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index e2be6869345d..64e63f6405d7 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1205,7 +1205,7 @@ bom { ] } } - library("Reactor Bom", "2022.0.8") { + library("Reactor Bom", "2023.0.0-SNAPSHOT") { group("io.projectreactor") { imports = [ "reactor-bom" From ad72d22c9049262c5ebaa39e57b8fd78d97b2400 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 17:00:11 +0200 Subject: [PATCH 0113/1656] Upgrade to Micrometer 1.12.0-M1 Closes gh-36188 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 64e63f6405d7..d777030ddd33 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -972,7 +972,7 @@ bom { ] } } - library("Micrometer", "1.12.0-SNAPSHOT") { + library("Micrometer", "1.12.0-M1") { group("io.micrometer") { modules = [ "micrometer-registry-stackdriver" { From 122f6599c0e69cbe141ae8943d876f3f77d33d70 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 17:00:43 +0200 Subject: [PATCH 0114/1656] Upgrade to Micrometer Tracing 1.2.0-M1 Closes gh-36199 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index d777030ddd33..c396ebe000c9 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -984,7 +984,7 @@ bom { ] } } - library("Micrometer Tracing", "1.2.0-SNAPSHOT") { + library("Micrometer Tracing", "1.2.0-M1") { group("io.micrometer") { imports = [ "micrometer-tracing-bom" From c59d474ec4e586a437c50afd6b19ae6973d4db64 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 17:01:10 +0200 Subject: [PATCH 0115/1656] Upgrade to Reactor Bom 2023.0.0-M1 Closes gh-36300 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index c396ebe000c9..cfa28cc5a1f0 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1205,7 +1205,7 @@ bom { ] } } - library("Reactor Bom", "2023.0.0-SNAPSHOT") { + library("Reactor Bom", "2023.0.0-M1") { group("io.projectreactor") { imports = [ "reactor-bom" From 556fcc92bb786b55f0d14784a70ff0040641a0c4 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:22 +0200 Subject: [PATCH 0116/1656] Upgrade to ActiveMQ 5.18.2 Closes gh-36348 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index cfa28cc5a1f0..b9b729ec3296 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -14,7 +14,7 @@ bom { issueLabels = ["type: dependency-upgrade"] } } - library("ActiveMQ", "5.18.1") { + library("ActiveMQ", "5.18.2") { group("org.apache.activemq") { modules = [ "activemq-amqp", From 49f5002241a9a064a0bab52de5d202c2c694f79b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:22 +0200 Subject: [PATCH 0117/1656] Upgrade to Artemis 2.29.0 Closes gh-36349 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index b9b729ec3296..2d09c05c392b 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -65,7 +65,7 @@ bom { ] } } - library("Artemis", "2.28.0") { + library("Artemis", "2.29.0") { group("org.apache.activemq") { modules = [ "artemis-amqp-protocol", From ee1044f4a55ff81c6dc8700bbef47f98b7174dda Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:23 +0200 Subject: [PATCH 0118/1656] Upgrade to Commons Codec 1.16.0 Closes gh-36350 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 2d09c05c392b..fd699ea2a444 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -172,7 +172,7 @@ bom { ] } } - library("Commons Codec", "1.15") { + library("Commons Codec", "1.16.0") { group("commons-codec") { modules = [ "commons-codec" From 5e2575371dcb50c8e9e26b6a0db493e515bc81ec Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:28 +0200 Subject: [PATCH 0119/1656] Upgrade to Dependency Management Plugin 1.1.1 Closes gh-36363 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index fd699ea2a444..85b438ad7643 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -223,7 +223,7 @@ bom { ] } } - library("Dependency Management Plugin", "1.1.0") { + library("Dependency Management Plugin", "1.1.1") { group("io.spring.gradle") { modules = [ "dependency-management-plugin" From 85194c703bca5095973cb93b16db361b10891c46 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:28 +0200 Subject: [PATCH 0120/1656] Upgrade to Elasticsearch Client 8.8.2 Closes gh-36351 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 85b438ad7643..04008356de13 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -262,7 +262,7 @@ bom { ] } } - library("Elasticsearch Client", "8.8.1") { + library("Elasticsearch Client", "8.8.2") { group("org.elasticsearch.client") { modules = [ "elasticsearch-rest-client" { From c6ffcb94c0dab481e7c3eb0009e45ed31e415a0a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:40 +0200 Subject: [PATCH 0121/1656] Upgrade to GraphQL Java 20.4 Closes gh-36365 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 04008356de13..9404079fe497 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -320,7 +320,7 @@ bom { ] } } - library("GraphQL Java", "20.2") { + library("GraphQL Java", "20.4") { group("com.graphql-java") { modules = [ "graphql-java" From d08c2f072350eb73f7b5d79e9731ac3cc896e67f Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:45 +0200 Subject: [PATCH 0122/1656] Upgrade to Groovy 4.0.13 Closes gh-36366 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 9404079fe497..a7dff9f051f6 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -327,7 +327,7 @@ bom { ] } } - library("Groovy", "4.0.12") { + library("Groovy", "4.0.13") { group("org.apache.groovy") { imports = [ "groovy-bom" From 2073f1d194792bd5dfffeb3cd66fccd1a1a0e1e9 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:50 +0200 Subject: [PATCH 0123/1656] Upgrade to H2 2.2.220 Closes gh-36367 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index a7dff9f051f6..5352c16fc8c0 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -341,7 +341,7 @@ bom { ] } } - library("H2", "2.1.214") { + library("H2", "2.2.220") { group("com.h2database") { modules = [ "h2" From 2f9d016581cc55a675c8576f493cc2302f07e50e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:13:57 +0200 Subject: [PATCH 0124/1656] Upgrade to Hibernate 6.2.6.Final Closes gh-36368 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 5352c16fc8c0..5f84cef2fe01 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -365,7 +365,7 @@ bom { ] } } - library("Hibernate", "6.2.5.Final") { + library("Hibernate", "6.2.6.Final") { group("org.hibernate.orm") { modules = [ "hibernate-agroal", From c6128904eebe8022becd39468f4e502889fed778 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:02 +0200 Subject: [PATCH 0125/1656] Upgrade to Hibernate Validator 8.0.1.Final Closes gh-36369 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 5f84cef2fe01..55e0cb591358 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -386,7 +386,7 @@ bom { ] } } - library("Hibernate Validator", "8.0.0.Final") { + library("Hibernate Validator", "8.0.1.Final") { group("org.hibernate.validator") { modules = [ "hibernate-validator", From d6d93fb1125d800b4a05bf62459b44130a9ba696 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:08 +0200 Subject: [PATCH 0126/1656] Upgrade to Infinispan 14.0.12.Final Closes gh-36370 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 55e0cb591358..a3bfaab3703c 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -453,7 +453,7 @@ bom { ] } } - library("Infinispan", "14.0.11.Final") { + library("Infinispan", "14.0.12.Final") { group("org.infinispan") { imports = [ "infinispan-bom" From 3e9eeea2b0df11aea6b853d7d3aebfb3ed6021c5 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:13 +0200 Subject: [PATCH 0127/1656] Upgrade to Jakarta WebSocket 2.1.1 Closes gh-36371 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index a3bfaab3703c..6f6e2752a422 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -558,7 +558,7 @@ bom { ] } } - library("Jakarta WebSocket", "2.1.0") { + library("Jakarta WebSocket", "2.1.1") { group("jakarta.websocket") { modules = [ "jakarta.websocket-api", From f05ec7e3001947928ef05b78498059cef0dbd720 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:18 +0200 Subject: [PATCH 0128/1656] Upgrade to Janino 3.1.10 Closes gh-36372 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 6f6e2752a422..3f6ff8eb4f11 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -594,7 +594,7 @@ bom { ] } } - library("Janino", "3.1.9") { + library("Janino", "3.1.10") { group("org.codehaus.janino") { modules = [ "commons-compiler", From 590526717de45aa789efb11d6e7992cfd3a4b7b1 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:23 +0200 Subject: [PATCH 0129/1656] Upgrade to JBoss Logging 3.5.3.Final Closes gh-36373 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 3f6ff8eb4f11..6a6230807924 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -631,7 +631,7 @@ bom { ] } } - library("JBoss Logging", "3.5.1.Final") { + library("JBoss Logging", "3.5.3.Final") { group("org.jboss.logging") { modules = [ "jboss-logging" From 1c0217f40aa9ec3626fdada9e1cd895b05d0fa6a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:28 +0200 Subject: [PATCH 0130/1656] Upgrade to jOOQ 3.18.5 Closes gh-36374 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 6a6230807924..d9ae39a7c2db 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -680,7 +680,7 @@ bom { ] } } - library("jOOQ", "3.18.4") { + library("jOOQ", "3.18.5") { group("org.jooq") { modules = [ "jooq", From 11898c0b56cc86b99f5d6f8282daa085329cdba2 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:32 +0200 Subject: [PATCH 0131/1656] Upgrade to Json-smart 2.5.0 Closes gh-36375 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index d9ae39a7c2db..890393268cf4 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -701,7 +701,7 @@ bom { ] } } - library("Json-smart", "2.4.11") { + library("Json-smart", "2.5.0") { group("net.minidev") { modules = [ "json-smart" From 9a8b3246a43ec6a2db190fbb5408ed5ffd017cae Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:39 +0200 Subject: [PATCH 0132/1656] Upgrade to Kotlin Coroutines 1.7.2 Closes gh-36376 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 890393268cf4..bcf608622783 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -787,7 +787,7 @@ bom { ] } } - library("Kotlin Coroutines", "1.7.1") { + library("Kotlin Coroutines", "1.7.2") { group("org.jetbrains.kotlinx") { imports = [ "kotlinx-coroutines-bom" From 5e3445ad4a6d651b6a20ca7f77c7e274498dc5db Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:49 +0200 Subject: [PATCH 0133/1656] Upgrade to Maven Clean Plugin 3.3.1 Closes gh-36378 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index bcf608622783..01e701351ac7 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -860,7 +860,7 @@ bom { ] } } - library("Maven Clean Plugin", "3.2.0") { + library("Maven Clean Plugin", "3.3.1") { group("org.apache.maven.plugins") { plugins = [ "maven-clean-plugin" From 2d472c526f226541c1a7e8aa24d027714f246fa8 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:14:55 +0200 Subject: [PATCH 0134/1656] Upgrade to MongoDB 4.10.2 Closes gh-36379 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 01e701351ac7..d5e6d9a390dc 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -998,7 +998,7 @@ bom { ] } } - library("MongoDB", "4.9.1") { + library("MongoDB", "4.10.2") { group("org.mongodb") { modules = [ "bson", From 85d9cddd252b2b2ee8fa0cfd692d57e37aa89fc3 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:15:01 +0200 Subject: [PATCH 0135/1656] Upgrade to Neo4j Java Driver 5.10.0 Closes gh-36380 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index d5e6d9a390dc..52b0639da7e5 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1044,7 +1044,7 @@ bom { ] } } - library("Neo4j Java Driver", "5.9.0") { + library("Neo4j Java Driver", "5.10.0") { group("org.neo4j.driver") { modules = [ "neo4j-java-driver" From 7cb5b96919219d377c9cddf17c05430c2d9b114d Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:15:07 +0200 Subject: [PATCH 0136/1656] Upgrade to OpenTelemetry 1.28.0 Closes gh-36381 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 52b0639da7e5..b8edb3f85fcc 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1065,7 +1065,7 @@ bom { ] } } - library("OpenTelemetry", "1.27.0") { + library("OpenTelemetry", "1.28.0") { group("io.opentelemetry") { imports = [ "opentelemetry-bom" From f689f68695d0f6036c280c0a842699ed691a0858 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:15:14 +0200 Subject: [PATCH 0137/1656] Upgrade to Rabbit Stream Client 0.11.0 Closes gh-36382 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index b8edb3f85fcc..6dbc9ae4b644 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1191,7 +1191,7 @@ bom { ] } } - library("Rabbit Stream Client", "0.10.0") { + library("Rabbit Stream Client", "0.11.0") { group("com.rabbitmq") { modules = [ "stream-client" From 725774de5b492b0223d46fcb53d6833e867668c2 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:15:18 +0200 Subject: [PATCH 0138/1656] Upgrade to Tomcat 10.1.11 Closes gh-36383 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d4321ef602e8..7e5a78d8f27f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 kotlinVersion=1.8.22 nativeBuildToolsVersion=0.9.23 springFrameworkVersion=6.1.0-SNAPSHOT -tomcatVersion=10.1.10 +tomcatVersion=10.1.11 kotlin.stdlib.default.dependency=false From 6684404f67757bfdcaeb491320dc7447591a2fe0 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:15:23 +0200 Subject: [PATCH 0139/1656] Upgrade to WebJars Locator Core 0.53 Closes gh-36384 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 6dbc9ae4b644..38f2a7b17351 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1565,7 +1565,7 @@ bom { ] } } - library("WebJars Locator Core", "0.52") { + library("WebJars Locator Core", "0.53") { group("org.webjars") { modules = [ "webjars-locator-core" From 83b7b902c0b5806b0c2709b03cbaba7dd6071106 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 12 Jul 2023 18:15:28 +0200 Subject: [PATCH 0140/1656] Upgrade to XML Maven Plugin 1.1.0 Closes gh-36385 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 38f2a7b17351..ced8584a9ff1 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1579,7 +1579,7 @@ bom { ] } } - library("XML Maven Plugin", "1.0.2") { + library("XML Maven Plugin", "1.1.0") { group("org.codehaus.mojo") { plugins = [ "xml-maven-plugin" From 0d8bae5953c8d82be1f645170d4cc346ff9925c2 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 13 Jul 2023 15:34:23 +0200 Subject: [PATCH 0141/1656] Prohibit upgrades to Liquibase 4.23.0 Closes gh-36377 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index ced8584a9ff1..347a1b4327ba 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -803,7 +803,7 @@ bom { } library("Liquibase", "4.20.0") { prohibit { - versionRange "[4.21.0,4.22.0]" + versionRange "[4.21.0,4.23.0]" because "https://github.com/liquibase/liquibase/issues/4135" } group("org.liquibase") { From 605ceaf471c8fae060a0d432fadae4e72785cfca Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 13 Jul 2023 16:13:00 +0200 Subject: [PATCH 0142/1656] Upgrade to Spring Framework 6.1.0-M2 Closes gh-36198 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7e5a78d8f27f..030ee45e65a8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 kotlinVersion=1.8.22 nativeBuildToolsVersion=0.9.23 -springFrameworkVersion=6.1.0-SNAPSHOT +springFrameworkVersion=6.1.0-M2 tomcatVersion=10.1.11 kotlin.stdlib.default.dependency=false From 1ced770f1584b17891d3f333026d15fabc1005da Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 14 Jul 2023 19:11:57 +0200 Subject: [PATCH 0143/1656] Upgrade to Spring Data Bom 2023.1.0-M1 Closes gh-36190 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 347a1b4327ba..ce5ed0a3a23e 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1389,7 +1389,7 @@ bom { ] } } - library("Spring Data Bom", "2023.1.0-SNAPSHOT") { + library("Spring Data Bom", "2023.1.0-M1") { group("org.springframework.data") { imports = [ "spring-data-bom" From 7692171119d9cebc69212d6c787055adb6d79da4 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 14 Jul 2023 19:12:38 +0200 Subject: [PATCH 0144/1656] Upgrade to Spring HATEOAS 2.2.0-M1 Closes gh-36192 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index ce5ed0a3a23e..2adbffd429e4 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1411,7 +1411,7 @@ bom { ] } } - library("Spring HATEOAS", "2.2.0-SNAPSHOT") { + library("Spring HATEOAS", "2.2.0-M1") { group("org.springframework.hateoas") { modules = [ "spring-hateoas" From b8c4fb6b9a20bad8ffa6d781a8bf38681562ffed Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 14 Jul 2023 19:56:44 +0100 Subject: [PATCH 0145/1656] Upgrade to Liquibase 4.23.0 Closes gh-36377 --- .../spring-boot-actuator-autoconfigure/build.gradle | 1 + spring-boot-project/spring-boot-dependencies/build.gradle | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 078187aff0cd..a165eeee1b00 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -106,6 +106,7 @@ dependencies { optional("org.hibernate.validator:hibernate-validator") optional("org.influxdb:influxdb-java") optional("org.liquibase:liquibase-core") { + exclude group: "javax.activation", module: "javax.activation-api" exclude group: "javax.xml.bind", module: "jaxb-api" } optional("org.mongodb:mongodb-driver-reactivestreams") diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 2adbffd429e4..549041507fc8 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -801,11 +801,7 @@ bom { ] } } - library("Liquibase", "4.20.0") { - prohibit { - versionRange "[4.21.0,4.23.0]" - because "https://github.com/liquibase/liquibase/issues/4135" - } + library("Liquibase", "4.23.0") { group("org.liquibase") { modules = [ "liquibase-cdi", From f33874e98ee6e5026089a4bc9091559744cd5c75 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 17 Jul 2023 09:47:54 +0100 Subject: [PATCH 0146/1656] Allow auto-configured applicationTaskExecutor to use virtual threads With this commit, when virtual threads are enabled, the auto-configured applicationTaskExecutor changes from a ThreadPoolTaskExecutor to a SimpleAsyncTaskExecutor with virtual threads enabled. As before, any TaskDecorator bean is applied to the auto-configured executor and the spring.task.execution.thread-name-prefix property is applied. Other spring.task.execution.* properties are ignored as they are specific to a pool-based executor. Closes gh-35710 --- .../task/TaskExecutionAutoConfiguration.java | 3 + .../task/TaskExecutorConfigurations.java | 72 +++++++++++++++++ .../TaskExecutionAutoConfigurationTests.java | 78 ++++++++++++++++++- .../task-execution-and-scheduling.adoc | 7 +- 4 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java index 1ebb19871931..0ee647f712c0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.task.TaskExecutorBuilder; import org.springframework.boot.task.TaskExecutorCustomizer; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskExecutor; @@ -44,6 +45,8 @@ @ConditionalOnClass(ThreadPoolTaskExecutor.class) @AutoConfiguration @EnableConfigurationProperties(TaskExecutionProperties.class) +@Import({ TaskExecutorConfigurations.VirtualThreadTaskExecutorConfiguration.class, + TaskExecutorConfigurations.ThreadPoolTaskExecutorConfiguration.class }) public class TaskExecutionAutoConfiguration { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java new file mode 100644 index 000000000000..f80bbaf493b5 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.task; + +import java.util.concurrent.Executor; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnVirtualThreads; +import org.springframework.boot.task.TaskExecutorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskDecorator; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +/** + * {@link TaskExecutor} configurations to be imported by + * {@link TaskExecutionAutoConfiguration} in a specific order. + * + * @author Andy Wilkinson + */ +class TaskExecutorConfigurations { + + @ConditionalOnVirtualThreads + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(Executor.class) + static class VirtualThreadTaskExecutorConfiguration { + + @Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, + AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) + SimpleAsyncTaskExecutor applicationTaskExecutor(TaskExecutionProperties properties, + ObjectProvider taskDecorator) { + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(properties.getThreadNamePrefix()); + executor.setVirtualThreads(true); + executor.setTaskDecorator(taskDecorator.getIfUnique()); + return executor; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(Executor.class) + static class ThreadPoolTaskExecutorConfiguration { + + @Lazy + @Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, + AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) + ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) { + return builder.build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java index c1d0507903ac..8a1e09e922f0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java @@ -17,11 +17,16 @@ package org.springframework.boot.autoconfigure.task; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.config.BeanDefinition; @@ -34,6 +39,7 @@ import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskExecutor; @@ -98,7 +104,7 @@ void taskExecutorBuilderShouldUseTaskDecorator() { } @Test - void taskExecutorAutoConfiguredIsLazy() { + void whenThreadPoolTaskExecutorIsAutoConfiguredThenItIsLazy() { this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor"); BeanDefinition beanDefinition = context.getSourceApplicationContext() @@ -109,6 +115,51 @@ void taskExecutorAutoConfiguredIsLazy() { }); } + @Test + @DisabledForJreRange(max = JRE.JAVA_20) + void whenVirtualThreadsAreEnabledThenSimpleAsyncTaskExecutorWithVirtualThreadsIsAutoConfigured() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor"); + assertThat(context).getBean("applicationTaskExecutor").isInstanceOf(SimpleAsyncTaskExecutor.class); + SimpleAsyncTaskExecutor taskExecutor = context.getBean("applicationTaskExecutor", + SimpleAsyncTaskExecutor.class); + assertThat(virtualThreadName(taskExecutor)).startsWith("task-"); + }); + } + + @Test + @DisabledForJreRange(max = JRE.JAVA_20) + void whenTaskNamePrefixIsConfiguredThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() { + this.contextRunner + .withPropertyValues("spring.threads.virtual.enabled=true", + "spring.task.execution.thread-name-prefix=custom-") + .run((context) -> { + SimpleAsyncTaskExecutor taskExecutor = context.getBean("applicationTaskExecutor", + SimpleAsyncTaskExecutor.class); + assertThat(virtualThreadName(taskExecutor)).startsWith("custom-"); + }); + } + + @Test + @DisabledForJreRange(max = JRE.JAVA_20) + void whenVirtualThreadsAreAvailableButNotEnabledThenThreadPoolTaskExecutorIsAutoConfigured() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Executor.class).hasBean("applicationTaskExecutor"); + assertThat(context).getBean("applicationTaskExecutor").isInstanceOf(ThreadPoolTaskExecutor.class); + }); + } + + @Test + @DisabledForJreRange(max = JRE.JAVA_20) + void whenTaskDecoratorIsDefinedThenSimpleAsyncTaskExecutorWithVirtualThreadsUsesIt() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withUserConfiguration(TaskDecoratorConfig.class) + .run((context) -> { + SimpleAsyncTaskExecutor executor = context.getBean(SimpleAsyncTaskExecutor.class); + assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class)); + }); + } + @Test void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() { this.contextRunner.withUserConfiguration(CustomTaskExecutorConfig.class).run((context) -> { @@ -117,6 +168,17 @@ void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() { }); } + @Test + @DisabledForJreRange(max = JRE.JAVA_20) + void whenVirtualThreadsAreEnabledAndCustomTaskExecutorIsDefinedThenSimpleAsyncTaskExecutorThatUsesVirtualThreadsBacksOff() { + this.contextRunner.withUserConfiguration(CustomTaskExecutorConfig.class) + .withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> { + assertThat(context).hasSingleBean(Executor.class); + assertThat(context.getBean(Executor.class)).isSameAs(context.getBean("customTaskExecutor")); + }); + } + @Test void taskExecutorBuilderShouldApplyCustomizer() { this.contextRunner.withUserConfiguration(TaskExecutorCustomizerConfig.class).run((context) -> { @@ -159,6 +221,20 @@ private ContextConsumer assertTaskExecutor( }; } + private String virtualThreadName(SimpleAsyncTaskExecutor taskExecutor) throws InterruptedException { + AtomicReference threadReference = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + taskExecutor.execute(() -> { + Thread currentThread = Thread.currentThread(); + threadReference.set(currentThread); + latch.countDown(); + }); + latch.await(30, TimeUnit.SECONDS); + Thread thread = threadReference.get(); + assertThat(thread).extracting("virtual").as("%s is virtual", thread).isEqualTo(true); + return thread.getName(); + } + @Configuration(proxyBeanMethods = false) static class CustomTaskExecutorBuilderConfig { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc index 71c66a419add..543f0dba659d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc @@ -1,6 +1,9 @@ [[features.task-execution-and-scheduling]] == Task Execution and Scheduling -In the absence of an `Executor` bean in the context, Spring Boot auto-configures a `ThreadPoolTaskExecutor` with sensible defaults that can be automatically associated to asynchronous task execution (`@EnableAsync`) and Spring MVC asynchronous request processing. +In the absence of an `Executor` bean in the context, Spring Boot auto-configures an `AsyncTaskExecutor`. +When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskExecutor` that uses virtual threads. +Otherwise, it will be a `ThreadPoolTaskExecutor` with sensible defaults. +In either case, the auto-configured executor will be automatically used for asynchronous task execution (`@EnableAsync`) and Spring MVC asynchronous request processing. [TIP] ==== @@ -10,7 +13,7 @@ Depending on your target arrangement, you could change your `Executor` into a `T The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default. ==== -The thread pool uses 8 core threads that can grow and shrink according to the load. +When a `ThreadPoolTaskExecutor` is auto-configured, the thread pool uses 8 core threads that can grow and shrink according to the load. Those default settings can be fine-tuned using the `spring.task.execution` namespace, as shown in the following example: [source,yaml,indent=0,subs="verbatim",configprops,configblocks] From 8115f8f1465c980000b6eec40fd98612acfc994a Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Wed, 12 Jul 2023 12:49:18 -0700 Subject: [PATCH 0147/1656] Add property for base time unit in OTLP registry Micrometer added a new configuration option to its OTLP registry to enable configuring the base time unit. These changes provide a configuration property to support to it. See gh-36393 --- .../metrics/export/otlp/OtlpProperties.java | 14 ++++++++++++++ .../export/otlp/OtlpPropertiesConfigAdapter.java | 6 ++++++ .../otlp/OtlpPropertiesConfigAdapterTests.java | 14 ++++++++++++++ .../metrics/export/otlp/OtlpPropertiesTests.java | 1 + 4 files changed, 35 insertions(+) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java index 701d45c30896..b39faa557713 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; import java.util.Map; +import java.util.concurrent.TimeUnit; import io.micrometer.registry.otlp.AggregationTemporality; @@ -55,6 +56,11 @@ public class OtlpProperties extends StepRegistryProperties { */ private Map headers; + /** + * Time unit for exported metrics. + */ + private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS; + public String getUrl() { return this.url; } @@ -87,4 +93,12 @@ public void setHeaders(Map headers) { this.headers = headers; } + public TimeUnit getBaseTimeUnit() { + return this.baseTimeUnit; + } + + public void setBaseTimeUnit(TimeUnit baseTimeUnit) { + this.baseTimeUnit = baseTimeUnit; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java index 814298d364e3..e21455e80f44 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; import java.util.Map; +import java.util.concurrent.TimeUnit; import io.micrometer.registry.otlp.AggregationTemporality; import io.micrometer.registry.otlp.OtlpConfig; @@ -60,4 +61,9 @@ public Map headers() { return get(OtlpProperties::getHeaders, OtlpConfig.super::headers); } + @Override + public TimeUnit baseTimeUnit() { + return get(OtlpProperties::getBaseTimeUnit, OtlpConfig.super::baseTimeUnit); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java index d2fc02a7f412..87f527dcd197 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; import java.util.Map; +import java.util.concurrent.TimeUnit; import io.micrometer.registry.otlp.AggregationTemporality; import org.junit.jupiter.api.Test; @@ -67,4 +68,17 @@ void whenPropertiesHeadersIsSetAdapterHeadersReturnsIt() { assertThat(new OtlpPropertiesConfigAdapter(properties).headers()).containsEntry("header", "value"); } + @Test + void whenPropertiesBaseTimeUnitIsNotSetAdapterBaseTimeUnitReturnsMillis() { + OtlpProperties properties = new OtlpProperties(); + assertThat(new OtlpPropertiesConfigAdapter(properties).baseTimeUnit()).isSameAs(TimeUnit.MILLISECONDS); + } + + @Test + void whenPropertiesBaseTimeUnitIsSetAdapterBaseTimeUnitReturnsIt() { + OtlpProperties properties = new OtlpProperties(); + properties.setBaseTimeUnit(TimeUnit.SECONDS); + assertThat(new OtlpPropertiesConfigAdapter(properties).baseTimeUnit()).isSameAs(TimeUnit.SECONDS); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java index 69f945b66ce5..3046e2279dca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java @@ -37,6 +37,7 @@ void defaultValuesAreConsistent() { assertStepRegistryDefaultValues(properties, config); assertThat(properties.getUrl()).isEqualTo(config.url()); assertThat(properties.getAggregationTemporality()).isSameAs(config.aggregationTemporality()); + assertThat(properties.getBaseTimeUnit()).isSameAs(config.baseTimeUnit()); } } From bc2899c1ef67b49f80b28708f6e4078947b9aa8e Mon Sep 17 00:00:00 2001 From: Bernardo Bulgarelli Date: Wed, 5 Jul 2023 20:28:08 -0300 Subject: [PATCH 0148/1656] Deprecate DelegatingApplicationContextInitializer and DelegatingApplicationListener See gh-36251 --- .../config/DelegatingApplicationContextInitializer.java | 3 +++ .../boot/context/config/DelegatingApplicationListener.java | 3 +++ .../config/DelegatingApplicationContextInitializerTests.java | 3 +++ .../context/config/DelegatingApplicationListenerTests.java | 3 +++ 4 files changed, 12 insertions(+) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java index 5f0cf707a7ee..a5bb72cf7ac5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java @@ -38,7 +38,10 @@ * @author Dave Syer * @author Phillip Webb * @since 1.0.0 + * + * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended */ +@Deprecated(since = "3.2.0", forRemoval = true) public class DelegatingApplicationContextInitializer implements ApplicationContextInitializer, Ordered { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java index 634962b0b6fa..793321785139 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java @@ -40,7 +40,10 @@ * @author Dave Syer * @author Phillip Webb * @since 1.0.0 + * + * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended */ +@Deprecated(since = "3.2.0", forRemoval = true) public class DelegatingApplicationListener implements ApplicationListener, Ordered { // NOTE: Similar to org.springframework.web.context.ContextLoader diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java index 748d1e65ced2..2a3eb495ef4c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java @@ -35,7 +35,10 @@ * Tests for {@link DelegatingApplicationContextInitializer}. * * @author Phillip Webb + * + * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended */ +@Deprecated(since = "3.2.0", forRemoval = true) class DelegatingApplicationContextInitializerTests { private final DelegatingApplicationContextInitializer initializer = new DelegatingApplicationContextInitializer(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java index 284c9377d47d..0b63f1a563fa 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java @@ -36,7 +36,10 @@ * Tests for {@link DelegatingApplicationListener}. * * @author Dave Syer + * + * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended */ +@Deprecated(since = "3.2.0", forRemoval = true) class DelegatingApplicationListenerTests { private final DelegatingApplicationListener listener = new DelegatingApplicationListener(); From 60df7e3bce6873a21351e2164e835233c7e3159f Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 17 Jul 2023 13:33:23 +0200 Subject: [PATCH 0149/1656] Polish contribution See gh-36251 --- .../src/docs/asciidoc/howto/application.adoc | 1 - .../config/DelegatingApplicationContextInitializer.java | 6 +++--- .../boot/context/config/DelegatingApplicationListener.java | 6 +++--- .../DelegatingApplicationContextInitializerTests.java | 3 +-- .../context/config/DelegatingApplicationListenerTests.java | 3 +-- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc index 638864778836..26b1da2c2e23 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc @@ -61,7 +61,6 @@ Spring Boot loads a number of such customizations for use internally from `META- There is more than one way to register additional customizations: * Programmatically, per application, by calling the `addListeners` and `addInitializers` methods on `SpringApplication` before you run it. -* Declaratively, per application, by setting the `context.initializer.classes` or `context.listener.classes` properties. * Declaratively, for all applications, by adding a `META-INF/spring.factories` and packaging a jar file that the applications all use as a library. The `SpringApplication` sends some special `ApplicationEvents` to the listeners (some even before the context is created) and then registers the listeners for events published by the `ApplicationContext` as well. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java index a5bb72cf7ac5..3cf1721734bf 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -38,8 +38,8 @@ * @author Dave Syer * @author Phillip Webb * @since 1.0.0 - * - * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended + * @deprecated since 3.2.0 for removal in 3.4.0 as property based initialization is no + * longer recommended */ @Deprecated(since = "3.2.0", forRemoval = true) public class DelegatingApplicationContextInitializer diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java index 793321785139..41c85cc354bc 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/DelegatingApplicationListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -40,8 +40,8 @@ * @author Dave Syer * @author Phillip Webb * @since 1.0.0 - * - * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended + * @deprecated since 3.2.0 for removal in 3.4.0 as property based initialization is no + * longer recommended */ @Deprecated(since = "3.2.0", forRemoval = true) public class DelegatingApplicationListener implements ApplicationListener, Ordered { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java index 2a3eb495ef4c..2436489db8bc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationContextInitializerTests.java @@ -35,10 +35,9 @@ * Tests for {@link DelegatingApplicationContextInitializer}. * * @author Phillip Webb - * - * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended */ @Deprecated(since = "3.2.0", forRemoval = true) +@SuppressWarnings("removal") class DelegatingApplicationContextInitializerTests { private final DelegatingApplicationContextInitializer initializer = new DelegatingApplicationContextInitializer(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java index 0b63f1a563fa..c56460014a8b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java @@ -36,10 +36,9 @@ * Tests for {@link DelegatingApplicationListener}. * * @author Dave Syer - * - * @deprecated since 3.2 for removal in 3.4 as property based initialization is no longer recommended */ @Deprecated(since = "3.2.0", forRemoval = true) +@SuppressWarnings("removal") class DelegatingApplicationListenerTests { private final DelegatingApplicationListener listener = new DelegatingApplicationListener(); From 0dae89e837b83816633dc4160f5e2348a2546364 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Fri, 28 Apr 2023 11:13:59 +0200 Subject: [PATCH 0150/1656] Add auto-configuration for ObservedAspect This adds support for auto-configuring `ObservedAspect` when AspectJ is on the classpath, which enables the usage of `@Observed`. See gh-35191 --- .../ObservationAutoConfiguration.java | 15 +++++++++++++++ .../ObservationAutoConfigurationTests.java | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java index a164afa63e84..b9230b45dd12 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java @@ -27,9 +27,11 @@ import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationPredicate; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; import io.micrometer.tracing.Tracer; import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler; import io.micrometer.tracing.handler.TracingObservationHandler; +import org.aspectj.weaver.Advice; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; @@ -51,6 +53,7 @@ * @author Moritz Halbritter * @author Brian Clozel * @author Jonatan Ivanov + * @author Vedran Pavic * @since 3.0.0 */ @AutoConfiguration(after = { CompositeMeterRegistryAutoConfiguration.class, MicrometerTracingAutoConfiguration.class }) @@ -149,4 +152,16 @@ TracingAwareMeterObservationHandler tracingAwareMeterObserv } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(Advice.class) + static class ObservedAspectConfiguration { + + @Bean + @ConditionalOnMissingBean + ObservedAspect observedAspect(ObservationRegistry observationRegistry) { + return new ObservedAspect(observationRegistry); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index 8060d268908d..3ebf46455062 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java @@ -34,9 +34,11 @@ import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler; import io.micrometer.observation.ObservationPredicate; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; import io.micrometer.tracing.Tracer; import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler; import io.micrometer.tracing.handler.TracingObservationHandler; +import org.aspectj.weaver.Advice; import org.junit.jupiter.api.Test; import org.mockito.Answers; @@ -58,6 +60,7 @@ * * @author Moritz Halbritter * @author Jonatan Ivanov + * @author Vedran Pavic */ class ObservationAutoConfigurationTests { @@ -77,6 +80,7 @@ void beansShouldNotBeSuppliedWhenMicrometerObservationIsNotOnClassPath() { assertThat(context).hasSingleBean(MeterRegistry.class); assertThat(context).doesNotHaveBean(ObservationRegistry.class); assertThat(context).doesNotHaveBean(ObservationHandler.class); + assertThat(context).doesNotHaveBean(ObservedAspect.class); assertThat(context).doesNotHaveBean(ObservationHandlerGrouping.class); }); } @@ -88,6 +92,7 @@ void supplyObservationRegistryWhenMicrometerCoreAndTracingAreNotOnClassPath() { ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test-observation", observationRegistry).stop(); assertThat(context).doesNotHaveBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).doesNotHaveBean(ObservationHandlerGrouping.class); }); } @@ -99,6 +104,7 @@ void supplyMeterHandlerAndGroupingWhenMicrometerCoreIsOnClassPathButTracingIsNot Observation.start("test-observation", observationRegistry).stop(); assertThat(context).hasSingleBean(ObservationHandler.class); assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("metricsObservationHandlerGrouping"); }); @@ -110,6 +116,7 @@ void supplyOnlyTracingObservationHandlerGroupingWhenMicrometerCoreIsNotOnClassPa ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test-observation", observationRegistry).stop(); assertThat(context).doesNotHaveBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("tracingObservationHandlerGrouping"); }); @@ -123,6 +130,7 @@ void supplyMeterHandlerAndGroupingWhenMicrometerCoreAndTracingAreOnClassPath() { // TracingAwareMeterObservationHandler that we don't test here Observation.start("test-observation", observationRegistry); assertThat(context).hasSingleBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(TracingAwareMeterObservationHandler.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping"); @@ -138,6 +146,7 @@ void supplyMeterHandlerAndGroupingWhenMicrometerCoreAndTracingAreOnClassPathButT Observation.start("test-observation", observationRegistry).stop(); assertThat(context).hasSingleBean(ObservationHandler.class); assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); assertThat(context).hasSingleBean(ObservationHandlerGrouping.class); assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping"); }); @@ -155,6 +164,7 @@ void autoConfiguresDefaultMeterObservationHandler() { assertThat(meterRegistry.get("test-observation").timer().count()).isOne(); assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class); assertThat(context).hasSingleBean(ObservationHandler.class); + assertThat(context).hasSingleBean(ObservedAspect.class); }); } @@ -164,6 +174,12 @@ void allowsDefaultMeterObservationHandlerToBeDisabled() { .run((context) -> assertThat(context).doesNotHaveBean(ObservationHandler.class)); } + @Test + void allowsObservedAspectToBeDisabled() { + this.contextRunner.withClassLoader(new FilteredClassLoader(Advice.class)) + .run((context) -> assertThat(context).doesNotHaveBean(ObservedAspect.class)); + } + @Test void autoConfiguresObservationPredicates() { this.contextRunner.withUserConfiguration(ObservationPredicates.class).run((context) -> { From c726a13395e82e63698be25906982a5508e34a32 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 17 Jul 2023 16:06:08 +0200 Subject: [PATCH 0151/1656] Polish "Add auto-configuration for ObservedAspect" See gh-35191 --- .../ObservationAutoConfigurationTests.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index 3ebf46455062..3cb18e2e0acf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java @@ -180,6 +180,14 @@ void allowsObservedAspectToBeDisabled() { .run((context) -> assertThat(context).doesNotHaveBean(ObservedAspect.class)); } + @Test + void allowsObservedAspectToBeCustomized() { + this.contextRunner.withUserConfiguration(CustomObservedAspectConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(ObservedAspect.class) + .getBean(ObservedAspect.class) + .isSameAs(context.getBean("customObservedAspect"))); + } + @Test void autoConfiguresObservationPredicates() { this.contextRunner.withUserConfiguration(ObservationPredicates.class).run((context) -> { @@ -353,6 +361,16 @@ ObservationFilter observationFilterTwo() { } + @Configuration(proxyBeanMethods = false) + static class CustomObservedAspectConfiguration { + + @Bean + ObservedAspect customObservedAspect(ObservationRegistry observationRegistry) { + return new ObservedAspect(observationRegistry); + } + + } + @Configuration(proxyBeanMethods = false) static class CustomGlobalObservationConvention { From 007e3409028b8a7ba45974ad1ad7e785ac604de8 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 17 Jul 2023 19:58:30 +0200 Subject: [PATCH 0152/1656] Upgrade to Dependency Management Plugin 1.1.2 Closes gh-36437 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 549041507fc8..d74f1dd225f8 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -223,7 +223,7 @@ bom { ] } } - library("Dependency Management Plugin", "1.1.1") { + library("Dependency Management Plugin", "1.1.2") { group("io.spring.gradle") { modules = [ "dependency-management-plugin" From c95c65838b4b9c4f9fe95ffe5cf0b9d4be570ef4 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 17 Jul 2023 19:58:35 +0200 Subject: [PATCH 0153/1656] Upgrade to Lettuce 6.2.5.RELEASE Closes gh-36438 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index d74f1dd225f8..ba1e3001a1d3 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -794,7 +794,7 @@ bom { ] } } - library("Lettuce", "6.2.4.RELEASE") { + library("Lettuce", "6.2.5.RELEASE") { group("io.lettuce") { modules = [ "lettuce-core" From 6ed8838209bfe224655f218fa066f1be8609aef5 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 17 Jul 2023 19:58:41 +0200 Subject: [PATCH 0154/1656] Upgrade to Spring AMQP 3.0.6 Closes gh-36439 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index ba1e3001a1d3..fb79aac8bde3 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1364,7 +1364,7 @@ bom { ] } } - library("Spring AMQP", "3.0.5") { + library("Spring AMQP", "3.0.6") { group("org.springframework.amqp") { imports = [ "spring-amqp-bom" From 4659d1bb934c26226c5fce027e3f593e8fba471f Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 17 Jul 2023 19:58:42 +0200 Subject: [PATCH 0155/1656] Upgrade to Spring Kafka 3.0.9 Closes gh-36194 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index fb79aac8bde3..f78f622fe552 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1421,7 +1421,7 @@ bom { ] } } - library("Spring Kafka", "3.0.9-SNAPSHOT") { + library("Spring Kafka", "3.0.9") { group("org.springframework.kafka") { modules = [ "spring-kafka", From 0b4bbe99b3eba8b311444602867ee8b32e6cde8b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 18 Jul 2023 06:24:30 +0200 Subject: [PATCH 0156/1656] Upgrade to Spring Security 6.2.0-M1 Closes gh-36195 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index f78f622fe552..4589803405bb 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1453,7 +1453,7 @@ bom { ] } } - library("Spring Security", "6.2.0-SNAPSHOT") { + library("Spring Security", "6.2.0-M1") { group("org.springframework.security") { imports = [ "spring-security-bom" From d205d10519e3a46010e7611b50ea6b47b584464a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 18 Jul 2023 10:18:37 +0100 Subject: [PATCH 0157/1656] Configure WebFlux's blocking execution to use applicationTaskExecutor Closes gh-36331 --- .../reactive/WebFluxAutoConfiguration.java | 14 ++++ .../WebFluxAutoConfigurationTests.java | 77 +++++++++++++++++++ .../task-execution-and-scheduling.adoc | 6 +- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index 54b1effcb9cb..c3d77d94fad1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; @@ -54,12 +55,14 @@ import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.ClassUtils; import org.springframework.validation.Validator; import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; +import org.springframework.web.reactive.config.BlockingExecutionConfigurer; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.ResourceHandlerRegistration; @@ -184,6 +187,17 @@ public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { this.codecCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configurer)); } + @Override + public void configureBlockingExecution(BlockingExecutionConfigurer configurer) { + if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) { + Object taskExecutor = this.beanFactory + .getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); + if (taskExecutor instanceof AsyncTaskExecutor asyncTaskExecutor) { + configurer.setExecutor(asyncTaskExecutor); + } + } + } + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 15759a908e2d..65be8c0f79ea 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -43,6 +44,7 @@ import org.springframework.aop.support.AopUtils; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ServerProperties; @@ -62,6 +64,7 @@ import org.springframework.core.annotation.Order; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.format.Parser; import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; @@ -79,6 +82,7 @@ import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; +import org.springframework.web.reactive.config.BlockingExecutionConfigurer; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.WebFluxConfigurationSupport; import org.springframework.web.reactive.config.WebFluxConfigurer; @@ -667,6 +671,47 @@ void problemDetailsBacksOffWhenExceptionHandler() { .hasSingleBean(CustomExceptionHandler.class)); } + @Test + void asyncTaskExecutorWithApplicationTaskExecutor() { + this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasSingleBean(AsyncTaskExecutor.class); + assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor") + .isSameAs(context.getBean("applicationTaskExecutor")); + }); + } + + @Test + void asyncTaskExecutorWithNonMatchApplicationTaskExecutorBean() { + this.contextRunner.withUserConfiguration(CustomApplicationTaskExecutorConfig.class) + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + assertThat(context).doesNotHaveBean(AsyncTaskExecutor.class); + assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor") + .isNotSameAs(context.getBean("applicationTaskExecutor")); + }); + } + + @Test + void asyncTaskExecutorWithWebFluxConfigurerCanOverrideExecutor() { + this.contextRunner.withUserConfiguration(CustomAsyncTaskExecutorConfigurer.class) + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> assertThat(context.getBean(RequestMappingHandlerAdapter.class)) + .extracting("scheduler.executor") + .isSameAs(context.getBean(CustomAsyncTaskExecutorConfigurer.class).taskExecutor)); + } + + @Test + void asyncTaskExecutorWithCustomNonApplicationTaskExecutor() { + this.contextRunner.withUserConfiguration(CustomAsyncTaskExecutorConfig.class) + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasSingleBean(AsyncTaskExecutor.class); + assertThat(context.getBean(RequestMappingHandlerAdapter.class)).extracting("scheduler.executor") + .isNotSameAs(context.getBean("customTaskExecutor")); + }); + } + private ContextConsumer assertExchangeWithSession( Consumer exchange) { return (context) -> { @@ -981,4 +1026,36 @@ void exceptionHandlerIntercept(JoinPoint joinPoint, Object returnValue) { } + @Configuration(proxyBeanMethods = false) + static class CustomApplicationTaskExecutorConfig { + + @Bean + Executor applicationTaskExecutor() { + return mock(Executor.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomAsyncTaskExecutorConfig { + + @Bean + AsyncTaskExecutor customTaskExecutor() { + return mock(AsyncTaskExecutor.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomAsyncTaskExecutorConfigurer implements WebFluxConfigurer { + + private final AsyncTaskExecutor taskExecutor = mock(AsyncTaskExecutor.class); + + @Override + public void configureBlockingExecution(BlockingExecutionConfigurer configurer) { + configurer.setExecutor(this.taskExecutor); + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc index 543f0dba659d..5eb6f53468d1 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc @@ -3,12 +3,12 @@ In the absence of an `Executor` bean in the context, Spring Boot auto-configures an `AsyncTaskExecutor`. When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskExecutor` that uses virtual threads. Otherwise, it will be a `ThreadPoolTaskExecutor` with sensible defaults. -In either case, the auto-configured executor will be automatically used for asynchronous task execution (`@EnableAsync`) and Spring MVC asynchronous request processing. +In either case, the auto-configured executor will be automatically used for asynchronous task execution (`@EnableAsync`), Spring MVC asynchronous request processing, and Spring WebFlux blocking execution. [TIP] ==== -If you have defined a custom `Executor` in the context, regular task execution (that is `@EnableAsync`) will use it transparently but the Spring MVC support will not be configured as it requires an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). -Depending on your target arrangement, you could change your `Executor` into a `ThreadPoolTaskExecutor` or define both a `ThreadPoolTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`. +If you have defined a custom `Executor` in the context, regular task execution (that is `@EnableAsync`) will use it transparently but the Spring MVC and Spring WebFlux support will not be configured as they require an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). +Depending on your target arrangement, you could change your `Executor` into an `AsyncTaskExecutor` or define both an `AsyncTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`. The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default. ==== From fb640c04e757eecfcd68fcfe0f0dfb87bcab4c9c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 13 Jul 2023 18:40:02 +0200 Subject: [PATCH 0158/1656] Upgrade to Flyway 9.20.1 Closes gh-36364 Co-authored-by: Andy Wilkinson --- .../spring-boot-autoconfigure/build.gradle | 1 + .../flyway/FlywayAutoConfiguration.java | 87 ++++++++++++++++--- ...va => Flyway91AutoConfigurationTests.java} | 9 +- .../flyway/FlywayAutoConfigurationTests.java | 53 +++++++++-- .../flyway/FlywayPropertiesTests.java | 5 +- .../spring-boot-dependencies/build.gradle | 3 +- 6 files changed, 131 insertions(+), 27 deletions(-) rename spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/{Flyway920AutoConfigurationTests.java => Flyway91AutoConfigurationTests.java} (86%) diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 03e84c83456a..a30895a521d8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -104,6 +104,7 @@ dependencies { exclude group: "commons-logging", module: "commons-logging" } optional("org.flywaydb:flyway-core") + optional("org.flywaydb:flyway-database-oracle") optional("org.flywaydb:flyway-sqlserver") optional("org.freemarker:freemarker") optional("org.glassfish.jersey.containers:jersey-container-servlet-core") 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 d007fb61953d..a4f15eaf61c8 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 @@ -24,6 +24,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; import javax.sql.DataSource; @@ -32,6 +35,8 @@ import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; +import org.flywaydb.core.extensibility.ConfigurationExtension; +import org.flywaydb.database.oracle.OracleConfigurationExtension; import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; import org.springframework.aot.hint.RuntimeHints; @@ -61,6 +66,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.io.ResourceLoader; @@ -71,6 +78,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.util.function.SingletonSupplier; /** * {@link EnableAutoConfiguration Auto-configuration} for Flyway database migrations. @@ -116,6 +124,12 @@ public FlywaySchemaManagementProvider flywayDefaultDdlModeProvider(ObjectProvide @EnableConfigurationProperties(FlywayProperties.class) public static class FlywayConfiguration { + private final FlywayProperties properties; + + FlywayConfiguration(FlywayProperties properties) { + this.properties = properties; + } + @Bean ResourceProviderCustomizer resourceProviderCustomizer() { return new ResourceProviderCustomizer(); @@ -123,21 +137,26 @@ ResourceProviderCustomizer resourceProviderCustomizer() { @Bean @ConditionalOnMissingBean(FlywayConnectionDetails.class) - PropertiesFlywayConnectionDetails flywayConnectionDetails(FlywayProperties properties) { - return new PropertiesFlywayConnectionDetails(properties); + PropertiesFlywayConnectionDetails flywayConnectionDetails() { + return new PropertiesFlywayConnectionDetails(this.properties); + } + + @Bean + @ConditionalOnClass(name = "org.flywaydb.database.oracle.OracleConfigurationExtension") + OracleFlywayConfigurationCustomizer oracleFlywayConfigurationCustomizer() { + return new OracleFlywayConfigurationCustomizer(this.properties); } @Bean - Flyway flyway(FlywayProperties properties, FlywayConnectionDetails connectionDetails, - ResourceLoader resourceLoader, ObjectProvider dataSource, - @FlywayDataSource ObjectProvider flywayDataSource, + Flyway flyway(FlywayConnectionDetails connectionDetails, ResourceLoader resourceLoader, + ObjectProvider dataSource, @FlywayDataSource ObjectProvider flywayDataSource, ObjectProvider fluentConfigurationCustomizers, ObjectProvider javaMigrations, ObjectProvider callbacks, ResourceProviderCustomizer resourceProviderCustomizer) { FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader()); configureDataSource(configuration, flywayDataSource.getIfAvailable(), dataSource.getIfUnique(), connectionDetails); - configureProperties(configuration, properties); + configureProperties(configuration, this.properties); configureCallbacks(configuration, callbacks.orderedStream().toList()); configureJavaMigrations(configuration, javaMigrations.orderedStream().toList()); fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration)); @@ -242,12 +261,6 @@ private void configureProperties(FluentConfiguration configuration, FlywayProper map.from(properties.getDryRunOutput()).to(configuration::dryRunOutput); map.from(properties.getErrorOverrides()).to(configuration::errorOverrides); map.from(properties.getLicenseKey()).to(configuration::licenseKey); - // No method references for Oracle props for compatibility with Flyway 9.20+ - map.from(properties.getOracleSqlplus()).to((oracleSqlplus) -> configuration.oracleSqlplus(oracleSqlplus)); - map.from(properties.getOracleSqlplusWarn()) - .to((oracleSqlplusWarn) -> configuration.oracleSqlplusWarn(oracleSqlplusWarn)); - map.from(properties.getOracleKerberosCacheFile()) - .to((oracleKerberosCacheFile) -> configuration.oracleKerberosCacheFile(oracleKerberosCacheFile)); map.from(properties.getStream()).to(configuration::stream); map.from(properties.getUndoSqlMigrationPrefix()).to(configuration::undoSqlMigrationPrefix); map.from(properties.getCherryPick()).to(configuration::cherryPick); @@ -445,4 +458,54 @@ public String getDriverClassName() { } + @Order(Ordered.HIGHEST_PRECEDENCE) + static final class OracleFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer { + + private final FlywayProperties properties; + + OracleFlywayConfigurationCustomizer(FlywayProperties properties) { + this.properties = properties; + } + + @Override + public void customize(FluentConfiguration configuration) { + ConfigurationExtensionMapper map = new ConfigurationExtensionMapper<>( + PropertyMapper.get().alwaysApplyingWhenNonNull(), () -> { + OracleConfigurationExtension extension = configuration.getPluginRegister() + .getPlugin(OracleConfigurationExtension.class); + Assert.notNull(extension, "Flyway Oracle extension missing"); + return extension; + }); + map.apply(this.properties.getOracleSqlplus(), OracleConfigurationExtension::setSqlplus); + map.apply(this.properties.getOracleSqlplusWarn(), OracleConfigurationExtension::setSqlplusWarn); + map.apply(this.properties.getOracleWalletLocation(), OracleConfigurationExtension::setWalletLocation); + map.apply(this.properties.getOracleKerberosCacheFile(), OracleConfigurationExtension::setKerberosCacheFile); + } + + } + + static class ConfigurationExtensionMapper { + + private final PropertyMapper map; + + private final Supplier extensionProvider; + + ConfigurationExtensionMapper(PropertyMapper map, Supplier extensionProvider) { + this.map = map; + this.extensionProvider = SingletonSupplier.of(extensionProvider); + } + + void apply(V value, BiConsumer mapper) { + this.map.from(value).to(withExtension(mapper)); + } + + private Consumer withExtension(BiConsumer mapper) { + return (value) -> { + T extension = this.extensionProvider.get(); + mapper.accept(extension, value); + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway920AutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway91AutoConfigurationTests.java similarity index 86% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway920AutoConfigurationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway91AutoConfigurationTests.java index 770a618e4725..ee7cb7ff751a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway920AutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway91AutoConfigurationTests.java @@ -23,18 +23,19 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link FlywayAutoConfiguration} with Flyway 9.20. + * Tests for {@link FlywayAutoConfiguration} with Flyway 9.19. * * @author Andy Wilkinson */ -@ClassPathOverrides({ "org.flywaydb:flyway-core:9.20.0", "org.flywaydb:flyway-sqlserver:9.20.0", - "com.h2database:h2:2.1.210" }) -class Flyway920AutoConfigurationTests { +@ClassPathExclusions("flyway-*.jar") +@ClassPathOverrides("org.flywaydb:flyway-core:9.19.4") +class Flyway91AutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index b2149465d8b2..27c947d60901 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -31,8 +31,10 @@ import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.callback.Context; import org.flywaydb.core.api.callback.Event; +import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.internal.license.FlywayTeamsUpgradeRequiredException; +import org.flywaydb.database.oracle.OracleConfigurationExtension; import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform; import org.jooq.DSLContext; @@ -49,6 +51,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.OracleFlywayConfigurationCustomizer; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; @@ -84,6 +87,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -602,18 +606,56 @@ void licenseKeyIsCorrectlyMapped(CapturedOutput output) { + "Enterprise features, download Flyway Teams Edition & Flyway Enterprise Edition")); } + @Test + void oracleExtensionIsNotLoadedByDefault() { + FluentConfiguration configuration = mock(FluentConfiguration.class); + new OracleFlywayConfigurationCustomizer(new FlywayProperties()).customize(configuration); + then(configuration).shouldHaveNoInteractions(); + } + @Test void oracleSqlplusIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-sqlplus=true") - .run(validateFlywayTeamsPropertyOnly("oracle.sqlplus")); + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplus()).isTrue()); + } @Test void oracleSqlplusWarnIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-sqlplus-warn=true") - .run(validateFlywayTeamsPropertyOnly("oracle.sqlplusWarn")); + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplusWarn()).isTrue()); + } + + @Test + void oracleWallerLocationIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle-wallet-location=/tmp/my.wallet") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getWalletLocation()).isEqualTo("/tmp/my.wallet")); + } + + @Test + void oracleKerberosCacheFileIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getKerberosCacheFile()).isEqualTo("/tmp/cache")); } @Test @@ -683,13 +725,6 @@ void kerberosConfigFileIsCorrectlyMapped() { .run(validateFlywayTeamsPropertyOnly("kerberosConfigFile")); } - @Test - void oracleKerberosCacheFileIsCorrectlyMapped() { - this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache") - .run(validateFlywayTeamsPropertyOnly("oracle.kerberosCacheFile")); - } - @Test void outputQueryResultsIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java index 0c7bb4110a2d..b6bca04ce2f3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java @@ -109,6 +109,9 @@ void expectedPropertiesAreManaged() { PropertyAccessorFactory.forBeanPropertyAccess(new ClassicConfiguration())); // Properties specific settings ignoreProperties(properties, "url", "driverClassName", "user", "password", "enabled"); + // Property that moved to a separate Oracle plugin + ignoreProperties(properties, "oracleSqlplus", "oracleSqlplusWarn", "oracleKerberosCacheFile", + "oracleWalletLocation"); // Property that moved to a separate SQL plugin ignoreProperties(properties, "sqlServerKerberosLoginFile"); // High level object we can't set with properties @@ -128,7 +131,7 @@ void expectedPropertiesAreManaged() { // Handled as createSchemas ignoreProperties(configuration, "shouldCreateSchemas"); // Getters for the DataSource settings rather than actual properties - ignoreProperties(configuration, "password", "url", "user"); + ignoreProperties(configuration, "databaseType", "password", "url", "user"); // Properties not exposed by Flyway ignoreProperties(configuration, "failOnMissingTarget"); List configurationKeys = new ArrayList<>(configuration.keySet()); diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 4589803405bb..8a55db136838 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -279,10 +279,11 @@ bom { ] } } - library("Flyway", "9.19.4") { + library("Flyway", "9.20.1") { group("org.flywaydb") { modules = [ "flyway-core", + "flyway-database-oracle", "flyway-firebird", "flyway-mysql", "flyway-sqlserver" From 71406977c24ead037268287922e9a350155c9c4f Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 18 Jul 2023 13:51:42 +0200 Subject: [PATCH 0159/1656] Harmonize configuration of Flyway SQL Server extension Closes gh-36440 --- .../flyway/FlywayAutoConfiguration.java | 43 +++++++++++++------ .../flyway/FlywayAutoConfigurationTests.java | 8 ++++ 2 files changed, 39 insertions(+), 12 deletions(-) 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 a4f15eaf61c8..9b768756ca2b 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 @@ -141,6 +141,12 @@ 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() { @@ -267,10 +273,6 @@ private void configureProperties(FluentConfiguration configuration, FlywayProper map.from(properties.getJdbcProperties()).whenNot(Map::isEmpty).to(configuration::jdbcProperties); map.from(properties.getKerberosConfigFile()).to(configuration::kerberosConfigFile); map.from(properties.getOutputQueryResults()).to(configuration::outputQueryResults); - map.from(properties.getSqlServerKerberosLoginFile()) - .whenNonNull() - .to((sqlServerKerberosLoginFile) -> configureSqlServerKerberosLoginFile(configuration, - sqlServerKerberosLoginFile)); map.from(properties.getSkipExecutingMigrations()).to(configuration::skipExecutingMigrations); map.from(properties.getIgnoreMigrationPatterns()) .whenNot(List::isEmpty) @@ -289,14 +291,6 @@ private void configureExecuteInTransaction(FluentConfiguration configuration, Fl } } - private void configureSqlServerKerberosLoginFile(FluentConfiguration configuration, - String sqlServerKerberosLoginFile) { - SQLServerConfigurationExtension sqlServerConfigurationExtension = configuration.getPluginRegister() - .getPlugin(SQLServerConfigurationExtension.class); - Assert.state(sqlServerConfigurationExtension != null, "Flyway SQL Server extension missing"); - sqlServerConfigurationExtension.getKerberos().getLogin().setFile(sqlServerKerberosLoginFile); - } - private void configureCallbacks(FluentConfiguration configuration, List callbacks) { if (!callbacks.isEmpty()) { configuration.callbacks(callbacks.toArray(new Callback[0])); @@ -484,6 +478,31 @@ public void customize(FluentConfiguration configuration) { } + @Order(Ordered.HIGHEST_PRECEDENCE) + static final class SqlServerFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer { + + private final FlywayProperties properties; + + SqlServerFlywayConfigurationCustomizer(FlywayProperties properties) { + this.properties = properties; + } + + @Override + public void customize(FluentConfiguration configuration) { + ConfigurationExtensionMapper map = new ConfigurationExtensionMapper<>( + PropertyMapper.get().alwaysApplyingWhenNonNull(), () -> { + SQLServerConfigurationExtension extension = configuration.getPluginRegister() + .getPlugin(SQLServerConfigurationExtension.class); + Assert.notNull(extension, "Flyway SQL Server extension missing"); + return extension; + }); + + map.apply(this.properties.getSqlServerKerberosLoginFile(), + (extension, file) -> extension.getKerberos().getLogin().setFile(file)); + } + + } + static class ConfigurationExtensionMapper { private final PropertyMapper map; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 27c947d60901..62c03c62b66e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -52,6 +52,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.OracleFlywayConfigurationCustomizer; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.SqlServerFlywayConfigurationCustomizer; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; @@ -732,6 +733,13 @@ void outputQueryResultsIsCorrectlyMapped() { .run(validateFlywayTeamsPropertyOnly("outputQueryResults")); } + @Test + void sqlServerExtensionIsNotLoadedByDefault() { + FluentConfiguration configuration = mock(FluentConfiguration.class); + new SqlServerFlywayConfigurationCustomizer(new FlywayProperties()).customize(configuration); + then(configuration).shouldHaveNoInteractions(); + } + @Test void sqlServerKerberosLoginFileIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) From 289d458a60d2641a040d4176816e07e85d97600e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 18 Jul 2023 15:04:42 +0200 Subject: [PATCH 0160/1656] Start building against Spring Framework 6.1.0-M3 snapshots See gh-36443 --- gradle.properties | 2 +- .../convert/ConversionServiceParameterValueMapperTests.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 030ee45e65a8..7e5a78d8f27f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 kotlinVersion=1.8.22 nativeBuildToolsVersion=0.9.23 -springFrameworkVersion=6.1.0-M2 +springFrameworkVersion=6.1.0-SNAPSHOT tomcatVersion=10.1.11 kotlin.stdlib.default.dependency=false diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java index 3ba4df37a969..c2c1bc8d6ca2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapperTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; @@ -55,7 +56,7 @@ void mapParameterShouldDelegateToConversionService() { void mapParameterWhenConversionServiceFailsShouldThrowParameterMappingException() { ConversionService conversionService = mock(ConversionService.class); RuntimeException error = new RuntimeException(); - given(conversionService.convert(any(), any())).willThrow(error); + given(conversionService.convert(any(Object.class), eq(Integer.class))).willThrow(error); ConversionServiceParameterValueMapper mapper = new ConversionServiceParameterValueMapper(conversionService); assertThatExceptionOfType(ParameterMappingException.class) .isThrownBy(() -> mapper.mapParameterValue(new TestOperationParameter(Integer.class), "123")) From 8da706603e451cabed8ddde06c9971b4fc7fcc89 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 18 Jul 2023 15:44:38 +0200 Subject: [PATCH 0161/1656] Add support for flyway.postgresql.transactional.lock Closes gh-32629 --- .../flyway/FlywayAutoConfiguration.java | 31 +++++++++++++++++++ .../flyway/FlywayProperties.java | 27 ++++++++++++++++ .../flyway/FlywayAutoConfigurationTests.java | 20 ++++++++++++ .../flyway/FlywayPropertiesTests.java | 2 ++ 4 files changed, 80 insertions(+) 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 9b768756ca2b..a47791944d01 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 @@ -36,6 +36,7 @@ import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.extensibility.ConfigurationExtension; +import org.flywaydb.core.internal.database.postgresql.PostgreSQLConfigurationExtension; import org.flywaydb.database.oracle.OracleConfigurationExtension; import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; @@ -153,6 +154,12 @@ OracleFlywayConfigurationCustomizer oracleFlywayConfigurationCustomizer() { return new OracleFlywayConfigurationCustomizer(this.properties); } + @Bean + @ConditionalOnClass(name = "org.flywaydb.core.internal.database.postgresql.PostgreSQLConfigurationExtension") + PostgresqlFlywayConfigurationCustomizer postgresqlFlywayConfigurationCustomizer() { + return new PostgresqlFlywayConfigurationCustomizer(this.properties); + } + @Bean Flyway flyway(FlywayConnectionDetails connectionDetails, ResourceLoader resourceLoader, ObjectProvider dataSource, @FlywayDataSource ObjectProvider flywayDataSource, @@ -478,6 +485,30 @@ public void customize(FluentConfiguration configuration) { } + @Order(Ordered.HIGHEST_PRECEDENCE) + static final class PostgresqlFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer { + + private final FlywayProperties properties; + + PostgresqlFlywayConfigurationCustomizer(FlywayProperties properties) { + this.properties = properties; + } + + @Override + public void customize(FluentConfiguration configuration) { + ConfigurationExtensionMapper map = new ConfigurationExtensionMapper<>( + PropertyMapper.get().alwaysApplyingWhenNonNull(), () -> { + PostgreSQLConfigurationExtension extension = configuration.getPluginRegister() + .getPlugin(PostgreSQLConfigurationExtension.class); + Assert.notNull(extension, "PostgreSQL extension missing"); + return extension; + }); + map.apply(this.properties.getPostgresql().getTransactionalLock(), + PostgreSQLConfigurationExtension::setTransactionalLock); + } + + } + @Order(Ordered.HIGHEST_PRECEDENCE) static final class SqlServerFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java index b629e7c425f8..b1a1dd7afd4b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -372,6 +372,8 @@ public class FlywayProperties { */ private Boolean detectEncoding; + private final Postgresql postgresql = new Postgresql(); + public boolean isEnabled() { return this.enabled; } @@ -868,4 +870,29 @@ public void setDetectEncoding(final Boolean detectEncoding) { this.detectEncoding = detectEncoding; } + public Postgresql getPostgresql() { + return this.postgresql; + } + + /** + * {@code PostgreSQLConfigurationExtension} properties. + */ + public static class Postgresql { + + /** + * Whether transactional advisory locks should be used. If set to false, + * session-level locks are used instead. + */ + private Boolean transactionalLock; + + public Boolean getTransactionalLock() { + return this.transactionalLock; + } + + public void setTransactionalLock(Boolean transactionalLock) { + this.transactionalLock = transactionalLock; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 62c03c62b66e..52bf67ce14ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -33,6 +33,7 @@ import org.flywaydb.core.api.callback.Event; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; +import org.flywaydb.core.internal.database.postgresql.PostgreSQLConfigurationExtension; import org.flywaydb.core.internal.license.FlywayTeamsUpgradeRequiredException; import org.flywaydb.database.oracle.OracleConfigurationExtension; import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; @@ -52,6 +53,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.OracleFlywayConfigurationCustomizer; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.PostgresqlFlywayConfigurationCustomizer; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.SqlServerFlywayConfigurationCustomizer; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; @@ -733,6 +735,24 @@ void outputQueryResultsIsCorrectlyMapped() { .run(validateFlywayTeamsPropertyOnly("outputQueryResults")); } + @Test + void postgresqlExtensionIsNotLoadedByDefault() { + FluentConfiguration configuration = mock(FluentConfiguration.class); + new PostgresqlFlywayConfigurationCustomizer(new FlywayProperties()).customize(configuration); + then(configuration).shouldHaveNoInteractions(); + } + + @Test + void postgresqlTransactionalLockIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.postgresql.transactional-lock=false") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(PostgreSQLConfigurationExtension.class) + .isTransactionalLock()).isFalse()); + } + @Test void sqlServerExtensionIsNotLoadedByDefault() { FluentConfiguration configuration = mock(FluentConfiguration.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java index b6bca04ce2f3..d36ecc54cc57 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java @@ -112,6 +112,8 @@ void expectedPropertiesAreManaged() { // Property that moved to a separate Oracle plugin ignoreProperties(properties, "oracleSqlplus", "oracleSqlplusWarn", "oracleKerberosCacheFile", "oracleWalletLocation"); + // Postgresql extension + ignoreProperties(properties, "postgresql"); // Property that moved to a separate SQL plugin ignoreProperties(properties, "sqlServerKerberosLoginFile"); // High level object we can't set with properties From c6e47b86d732736666e58e4875f363974863370c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 18 Jul 2023 16:09:26 +0200 Subject: [PATCH 0162/1656] Move Flyway configuration extension properties to dedicated namespace This commit harmonizes the handling of ConfigurationExtension for Flyway. The existing Oracle and SQLServer extensions are now mapped from flway.oracle and flyway.sqlserver, respectively. The existing properties have been deprecated in favor of the new location. Closes gh-36444 --- .../flyway/FlywayAutoConfiguration.java | 12 +- .../flyway/FlywayProperties.java | 156 +++++++++++++----- .../flyway/FlywayAutoConfigurationTests.java | 63 +++++++ .../flyway/FlywayPropertiesTests.java | 12 +- 4 files changed, 194 insertions(+), 49 deletions(-) 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 a47791944d01..1f499261c910 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 @@ -52,6 +52,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition; +import org.springframework.boot.autoconfigure.flyway.FlywayProperties.Oracle; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; @@ -477,10 +478,11 @@ public void customize(FluentConfiguration configuration) { Assert.notNull(extension, "Flyway Oracle extension missing"); return extension; }); - map.apply(this.properties.getOracleSqlplus(), OracleConfigurationExtension::setSqlplus); - map.apply(this.properties.getOracleSqlplusWarn(), OracleConfigurationExtension::setSqlplusWarn); - map.apply(this.properties.getOracleWalletLocation(), OracleConfigurationExtension::setWalletLocation); - map.apply(this.properties.getOracleKerberosCacheFile(), OracleConfigurationExtension::setKerberosCacheFile); + Oracle oracle = this.properties.getOracle(); + map.apply(oracle.getSqlplus(), OracleConfigurationExtension::setSqlplus); + map.apply(oracle.getSqlplusWarn(), OracleConfigurationExtension::setSqlplusWarn); + map.apply(oracle.getWalletLocation(), OracleConfigurationExtension::setWalletLocation); + map.apply(oracle.getKerberosCacheFile(), OracleConfigurationExtension::setKerberosCacheFile); } } @@ -528,7 +530,7 @@ public void customize(FluentConfiguration configuration) { return extension; }); - map.apply(this.properties.getSqlServerKerberosLoginFile(), + map.apply(this.properties.getSqlserver().getKerberosLoginFile(), (extension, file) -> extension.getKerberos().getLogin().setFile(file)); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java index b1a1dd7afd4b..bf1ea687e509 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -28,6 +28,7 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.convert.DurationUnit; /** @@ -295,17 +296,6 @@ public class FlywayProperties { */ private String licenseKey; - /** - * Whether to enable support for Oracle SQL*Plus commands. Requires Flyway Teams. - */ - private Boolean oracleSqlplus; - - /** - * Whether to issue a warning rather than an error when a not-yet-supported Oracle - * SQL*Plus statement is encountered. Requires Flyway Teams. - */ - private Boolean oracleSqlplusWarn; - /** * Whether to stream SQL migrations when executing them. Requires Flyway Teams. */ @@ -332,28 +322,12 @@ public class FlywayProperties { */ private String kerberosConfigFile; - /** - * Path of the Oracle Kerberos cache file. Requires Flyway Teams. - */ - private String oracleKerberosCacheFile; - - /** - * Location of the Oracle Wallet, used to sign in to the database automatically. - * Requires Flyway Teams. - */ - private String oracleWalletLocation; - /** * Whether Flyway should output a table with the results of queries when executing * migrations. Requires Flyway Teams. */ private Boolean outputQueryResults; - /** - * Path to the SQL Server Kerberos login file. Requires Flyway Teams. - */ - private String sqlServerKerberosLoginFile; - /** * Whether Flyway should skip executing the contents of the migrations and only update * the schema history table. Requires Flyway teams. @@ -372,8 +346,12 @@ public class FlywayProperties { */ private Boolean detectEncoding; + private final Oracle oracle = new Oracle(); + private final Postgresql postgresql = new Postgresql(); + private final Sqlserver sqlserver = new Sqlserver(); + public boolean isEnabled() { return this.enabled; } @@ -758,28 +736,37 @@ public void setLicenseKey(String licenseKey) { this.licenseKey = licenseKey; } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.sqlplus") + @Deprecated(since = "3.2.0", forRemoval = true) public Boolean getOracleSqlplus() { - return this.oracleSqlplus; + return getOracle().getSqlplus(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleSqlplus(Boolean oracleSqlplus) { - this.oracleSqlplus = oracleSqlplus; + getOracle().setSqlplus(oracleSqlplus); } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.sqlplus-warn") + @Deprecated(since = "3.2.0", forRemoval = true) public Boolean getOracleSqlplusWarn() { - return this.oracleSqlplusWarn; + return getOracle().getSqlplusWarn(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleSqlplusWarn(Boolean oracleSqlplusWarn) { - this.oracleSqlplusWarn = oracleSqlplusWarn; + getOracle().setSqlplusWarn(oracleSqlplusWarn); } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.wallet-location") + @Deprecated(since = "3.2.0", forRemoval = true) public String getOracleWalletLocation() { - return this.oracleWalletLocation; + return getOracle().getWalletLocation(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleWalletLocation(String oracleWalletLocation) { - this.oracleWalletLocation = oracleWalletLocation; + getOracle().setWalletLocation(oracleWalletLocation); } public Boolean getStream() { @@ -822,12 +809,15 @@ public void setKerberosConfigFile(String kerberosConfigFile) { this.kerberosConfigFile = kerberosConfigFile; } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.oracle.kerberos-cache-file") + @Deprecated(since = "3.2.0", forRemoval = true) public String getOracleKerberosCacheFile() { - return this.oracleKerberosCacheFile; + return getOracle().getKerberosCacheFile(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setOracleKerberosCacheFile(String oracleKerberosCacheFile) { - this.oracleKerberosCacheFile = oracleKerberosCacheFile; + getOracle().setKerberosCacheFile(oracleKerberosCacheFile); } public Boolean getOutputQueryResults() { @@ -838,12 +828,15 @@ public void setOutputQueryResults(Boolean outputQueryResults) { this.outputQueryResults = outputQueryResults; } + @DeprecatedConfigurationProperty(replacement = "spring.flyway.sqlserver.kerberos-login-file") + @Deprecated(since = "3.2.0", forRemoval = true) public String getSqlServerKerberosLoginFile() { - return this.sqlServerKerberosLoginFile; + return getSqlserver().getKerberosLoginFile(); } + @Deprecated(since = "3.2.0", forRemoval = true) public void setSqlServerKerberosLoginFile(String sqlServerKerberosLoginFile) { - this.sqlServerKerberosLoginFile = sqlServerKerberosLoginFile; + getSqlserver().setKerberosLoginFile(sqlServerKerberosLoginFile); } public Boolean getSkipExecutingMigrations() { @@ -870,10 +863,79 @@ public void setDetectEncoding(final Boolean detectEncoding) { this.detectEncoding = detectEncoding; } + public Oracle getOracle() { + return this.oracle; + } + public Postgresql getPostgresql() { return this.postgresql; } + public Sqlserver getSqlserver() { + return this.sqlserver; + } + + /** + * {@code OracleConfigurationExtension} properties. + */ + public static class Oracle { + + /** + * Whether to enable support for Oracle SQL*Plus commands. Requires Flyway Teams. + */ + private Boolean sqlplus; + + /** + * Whether to issue a warning rather than an error when a not-yet-supported Oracle + * SQL*Plus statement is encountered. Requires Flyway Teams. + */ + private Boolean sqlplusWarn; + + /** + * Path of the Oracle Kerberos cache file. Requires Flyway Teams. + */ + private String kerberosCacheFile; + + /** + * Location of the Oracle Wallet, used to sign in to the database automatically. + * Requires Flyway Teams. + */ + private String walletLocation; + + public Boolean getSqlplus() { + return this.sqlplus; + } + + public void setSqlplus(Boolean sqlplus) { + this.sqlplus = sqlplus; + } + + public Boolean getSqlplusWarn() { + return this.sqlplusWarn; + } + + public void setSqlplusWarn(Boolean sqlplusWarn) { + this.sqlplusWarn = sqlplusWarn; + } + + public String getKerberosCacheFile() { + return this.kerberosCacheFile; + } + + public void setKerberosCacheFile(String kerberosCacheFile) { + this.kerberosCacheFile = kerberosCacheFile; + } + + public String getWalletLocation() { + return this.walletLocation; + } + + public void setWalletLocation(String walletLocation) { + this.walletLocation = walletLocation; + } + + } + /** * {@code PostgreSQLConfigurationExtension} properties. */ @@ -895,4 +957,24 @@ public void setTransactionalLock(Boolean transactionalLock) { } + /** + * {@code SQLServerConfigurationExtension} properties. + */ + public static class Sqlserver { + + /** + * Path to the SQL Server Kerberos login file. Requires Flyway Teams. + */ + private String kerberosLoginFile; + + public String getKerberosLoginFile() { + return this.kerberosLoginFile; + } + + public void setKerberosLoginFile(String kerberosLoginFile) { + this.kerberosLoginFile = kerberosLoginFile; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 52bf67ce14ee..c462bc02716a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -618,6 +618,19 @@ void oracleExtensionIsNotLoadedByDefault() { @Test void oracleSqlplusIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle.sqlplus=true") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplus()).isTrue()); + + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleSqlplusIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-sqlplus=true") .run((context) -> assertThat(context.getBean(Flyway.class) @@ -630,6 +643,18 @@ void oracleSqlplusIsCorrectlyMapped() { @Test void oracleSqlplusWarnIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle.sqlplus-warn=true") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getSqlplusWarn()).isTrue()); + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleSqlplusWarnIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-sqlplus-warn=true") .run((context) -> assertThat(context.getBean(Flyway.class) @@ -641,6 +666,18 @@ void oracleSqlplusWarnIsCorrectlyMapped() { @Test void oracleWallerLocationIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle.wallet-location=/tmp/my.wallet") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getWalletLocation()).isEqualTo("/tmp/my.wallet")); + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleWallerLocationIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-wallet-location=/tmp/my.wallet") .run((context) -> assertThat(context.getBean(Flyway.class) @@ -652,6 +689,18 @@ void oracleWallerLocationIsCorrectlyMapped() { @Test void oracleKerberosCacheFileIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.oracle.kerberos-cache-file=/tmp/cache") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(OracleConfigurationExtension.class) + .getKerberosCacheFile()).isEqualTo("/tmp/cache")); + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void oracleKerberosCacheFileIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.oracle-kerberos-cache-file=/tmp/cache") .run((context) -> assertThat(context.getBean(Flyway.class) @@ -762,6 +811,20 @@ void sqlServerExtensionIsNotLoadedByDefault() { @Test void sqlServerKerberosLoginFileIsCorrectlyMapped() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.flyway.sqlserver.kerberos-login-file=/tmp/config") + .run((context) -> assertThat(context.getBean(Flyway.class) + .getConfiguration() + .getPluginRegister() + .getPlugin(SQLServerConfigurationExtension.class) + .getKerberos() + .getLogin() + .getFile()).isEqualTo("/tmp/config")); + } + + @Test + @Deprecated(since = "3.2.0", forRemoval = true) + void sqlServerKerberosLoginFileIsCorrectlyMappedWithDeprecatedProperty() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.sql-server-kerberos-login-file=/tmp/config") .run((context) -> assertThat(context.getBean(Flyway.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java index d36ecc54cc57..94c021b3f9b6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesTests.java @@ -109,13 +109,11 @@ void expectedPropertiesAreManaged() { PropertyAccessorFactory.forBeanPropertyAccess(new ClassicConfiguration())); // Properties specific settings ignoreProperties(properties, "url", "driverClassName", "user", "password", "enabled"); - // Property that moved to a separate Oracle plugin - ignoreProperties(properties, "oracleSqlplus", "oracleSqlplusWarn", "oracleKerberosCacheFile", - "oracleWalletLocation"); - // Postgresql extension - ignoreProperties(properties, "postgresql"); - // Property that moved to a separate SQL plugin - ignoreProperties(properties, "sqlServerKerberosLoginFile"); + // Deprecated properties + ignoreProperties(properties, "oracleKerberosCacheFile", "oracleSqlplus", "oracleSqlplusWarn", + "oracleWalletLocation", "sqlServerKerberosLoginFile"); + // Properties that are managed by specific extensions + ignoreProperties(properties, "oracle", "postgresql", "sqlserver"); // High level object we can't set with properties ignoreProperties(configuration, "callbacks", "classLoader", "dataSource", "javaMigrations", "javaMigrationClassProvider", "pluginRegister", "resourceProvider", "resolvers"); From 283dc37db3bdc4ba08a47ecc93b7057feddb6ece Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 18 Jul 2023 15:32:30 +0100 Subject: [PATCH 0163/1656] Make AnnotatedControllerConfigurer use applicationTaskExecutor Closes gh-36388 --- .../graphql/GraphQlAutoConfiguration.java | 7 +++- .../GraphQlAutoConfigurationTests.java | 32 +++++++++++++++++++ .../task-execution-and-scheduling.adoc | 10 ++++-- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java index 37a2b97c409b..a42ef90a10d1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; import graphql.GraphQL; import graphql.execution.instrumentation.Instrumentation; @@ -33,10 +34,12 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; 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.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.annotation.Bean; @@ -152,10 +155,12 @@ public ExecutionGraphQlService executionGraphQlService(GraphQlSource graphQlSour @Bean @ConditionalOnMissingBean - public AnnotatedControllerConfigurer annotatedControllerConfigurer() { + public AnnotatedControllerConfigurer annotatedControllerConfigurer( + @Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) ObjectProvider executorProvider) { AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer(); controllerConfigurer .addFormatterRegistrar((registry) -> ApplicationConversionService.addBeans(registry, this.beanFactory)); + executorProvider.ifAvailable(controllerConfigurer::setExecutor); return controllerConfigurer; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java index 2719747656e1..100cd9ddda09 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.graphql; import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executor; import graphql.GraphQL; import graphql.execution.instrumentation.ChainedInstrumentation; @@ -34,6 +35,7 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration.GraphQlResourcesRuntimeHints; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -209,6 +211,26 @@ void shouldContributeConnectionTypeDefinitionConfigurer() { }); } + @Test + void whenApplicationTaskExecutorIsDefinedThenAnnotatedControllerConfigurerShouldUseIt() { + this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .run((context) -> { + AnnotatedControllerConfigurer annotatedControllerConfigurer = context + .getBean(AnnotatedControllerConfigurer.class); + assertThat(annotatedControllerConfigurer).extracting("executor") + .isSameAs(context.getBean("applicationTaskExecutor")); + }); + } + + @Test + void whenCustomExecutorIsDefinedThenAnnotatedControllerConfigurerDoesNotUseIt() { + this.contextRunner.withUserConfiguration(CustomExecutorConfiguration.class).run((context) -> { + AnnotatedControllerConfigurer annotatedControllerConfigurer = context + .getBean(AnnotatedControllerConfigurer.class); + assertThat(annotatedControllerConfigurer).extracting("executor").isNull(); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomGraphQlBuilderConfiguration { @@ -294,4 +316,14 @@ public void customize(GraphQlSource.SchemaResourceBuilder builder) { } + @Configuration(proxyBeanMethods = false) + static class CustomExecutorConfiguration { + + @Bean + Executor customExecutor() { + return mock(Executor.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc index 5eb6f53468d1..9ae721385599 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc @@ -3,11 +3,17 @@ In the absence of an `Executor` bean in the context, Spring Boot auto-configures an `AsyncTaskExecutor`. When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskExecutor` that uses virtual threads. Otherwise, it will be a `ThreadPoolTaskExecutor` with sensible defaults. -In either case, the auto-configured executor will be automatically used for asynchronous task execution (`@EnableAsync`), Spring MVC asynchronous request processing, and Spring WebFlux blocking execution. +In either case, the auto-configured executor will be automatically used for: + +- asynchronous task execution (`@EnableAsync`) +- Spring for GraphQL's asynchronous handling of `Callable` return values from controller methods +- Spring MVC's asynchronous request processing +- Spring WebFlux's blocking execution support [TIP] ==== -If you have defined a custom `Executor` in the context, regular task execution (that is `@EnableAsync`) will use it transparently but the Spring MVC and Spring WebFlux support will not be configured as they require an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). +If you have defined a custom `Executor` in the context, both regular task execution (that is `@EnableAsync`) and Spring for GraphQL will use it. +However, the Spring MVC and Spring WebFlux support will only use it if it is an `AsyncTaskExecutor` implementation (named `applicationTaskExecutor`). Depending on your target arrangement, you could change your `Executor` into an `AsyncTaskExecutor` or define both an `AsyncTaskExecutor` and an `AsyncConfigurer` wrapping your custom `Executor`. The auto-configured `TaskExecutorBuilder` allows you to easily create instances that reproduce what the auto-configuration does by default. From 4393a2244ca9e67f84cac74f7a0b17a208b742f2 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Thu, 13 Jul 2023 14:27:14 -0600 Subject: [PATCH 0164/1656] Use Docker CLI context to determine daemon host address for image building Configuration files managed by the Docker CLI are now used to determine the host address of the Docker daemon used when building images using buildpacks when a host address is not configured with environment variables or build tool plugin configuration. Closes gh-36445 --- .../buildpack/platform/docker/DockerApi.java | 4 +- .../configuration/DockerConfiguration.java | 63 +++++- .../DockerConfigurationMetadata.java | 211 ++++++++++++++++++ .../docker/configuration/DockerHost.java | 2 +- .../configuration/ResolvedDockerHost.java | 29 ++- .../docker/transport/HttpTransport.java | 5 +- .../transport/LocalHttpClientTransport.java | 44 ++-- .../platform/build/LifecycleTests.java | 8 +- .../DockerConfigurationMetadataTests.java | 104 +++++++++ .../ResolvedDockerHostTests.java | 70 ++++-- .../docker/transport/HttpTransportTests.java | 10 +- .../LocalHttpClientTransportTests.java | 14 +- .../RemoteHttpClientTransportTests.java | 18 +- .../configuration/with-context/config.json | 3 + .../meta.json | 12 + .../with-default-context/config.json | 3 + .../meta.json | 12 + .../docker/cert.pem | 0 .../docker/key.pem | 0 .../configuration/without-context/config.json | 2 + .../docs/asciidoc/packaging-oci-image.adoc | 12 +- .../gradle/tasks/bundling/DockerSpec.java | 14 +- .../tasks/bundling/DockerSpecTests.java | 36 ++- .../docs/asciidoc/packaging-oci-image.adoc | 12 +- .../springframework/boot/maven/Docker.java | 23 +- .../boot/maven/DockerTests.java | 35 ++- 26 files changed, 660 insertions(+), 86 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/cert.pem create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/key.pem create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index 4ee957b5daea..7365bf267781 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -40,7 +40,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.hc.core5.net.URIBuilder; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport; import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; @@ -97,7 +97,7 @@ public DockerApi() { * @param dockerHost the Docker daemon host information * @since 2.4.0 */ - public DockerApi(DockerHost dockerHost) { + public DockerApi(DockerHostConfiguration dockerHost) { this(HttpTransport.create(dockerHost)); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java index b82a6b28ac22..fa47c349fbd1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -27,7 +27,7 @@ */ public final class DockerConfiguration { - private final DockerHost host; + private final DockerHostConfiguration host; private final DockerRegistryAuthentication builderAuthentication; @@ -39,7 +39,7 @@ public DockerConfiguration() { this(null, null, null, false); } - private DockerConfiguration(DockerHost host, DockerRegistryAuthentication builderAuthentication, + private DockerConfiguration(DockerHostConfiguration host, DockerRegistryAuthentication builderAuthentication, DockerRegistryAuthentication publishAuthentication, boolean bindHostToBuilder) { this.host = host; this.builderAuthentication = builderAuthentication; @@ -47,7 +47,7 @@ private DockerConfiguration(DockerHost host, DockerRegistryAuthentication builde this.bindHostToBuilder = bindHostToBuilder; } - public DockerHost getHost() { + public DockerHostConfiguration getHost() { return this.host; } @@ -65,7 +65,13 @@ public DockerRegistryAuthentication getPublishRegistryAuthentication() { public DockerConfiguration withHost(String address, boolean secure, String certificatePath) { Assert.notNull(address, "Address must not be null"); - return new DockerConfiguration(new DockerHost(address, secure, certificatePath), this.builderAuthentication, + return new DockerConfiguration(DockerHostConfiguration.forAddress(address, secure, certificatePath), + this.builderAuthentication, this.publishAuthentication, this.bindHostToBuilder); + } + + public DockerConfiguration withContext(String context) { + Assert.notNull(context, "Context must not be null"); + return new DockerConfiguration(DockerHostConfiguration.forContext(context), this.builderAuthentication, this.publishAuthentication, this.bindHostToBuilder); } @@ -107,4 +113,51 @@ public DockerConfiguration withEmptyPublishRegistryAuthentication() { new DockerRegistryUserAuthentication("", "", "", ""), this.bindHostToBuilder); } + public static class DockerHostConfiguration { + + private final String address; + + private final String context; + + private final boolean secure; + + private final String certificatePath; + + public DockerHostConfiguration(String address, String context, boolean secure, String certificatePath) { + this.address = address; + this.context = context; + this.secure = secure; + this.certificatePath = certificatePath; + } + + public String getAddress() { + return this.address; + } + + public String getContext() { + return this.context; + } + + public boolean isSecure() { + return this.secure; + } + + public String getCertificatePath() { + return this.certificatePath; + } + + public static DockerHostConfiguration forAddress(String address) { + return new DockerHostConfiguration(address, null, false, null); + } + + public static DockerHostConfiguration forAddress(String address, boolean secure, String certificatePath) { + return new DockerHostConfiguration(address, null, secure, certificatePath); + } + + static DockerHostConfiguration forContext(String context) { + return new DockerHostConfiguration(null, context, false, null); + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java new file mode 100644 index 000000000000..9ab0fef20192 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java @@ -0,0 +1,211 @@ +/* + * Copyright 2012-2023 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.buildpack.platform.docker.configuration; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; + +import org.springframework.boot.buildpack.platform.json.MappedObject; +import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; +import org.springframework.boot.buildpack.platform.system.Environment; + +/** + * Docker configuration stored in metadata files managed by the Docker CLI. + * + * @author Scott Frederick + */ +final class DockerConfigurationMetadata { + + private static final String DOCKER_CONFIG = "DOCKER_CONFIG"; + + private static final String DEFAULT_CONTEXT = "default"; + + private static final String CONFIG_DIR = ".docker"; + + private static final String CONTEXTS_DIR = "contexts"; + + private static final String META_DIR = "meta"; + + private static final String TLS_DIR = "tls"; + + private static final String DOCKER_ENDPOINT = "docker"; + + private static final String CONFIG_FILE_NAME = "config.json"; + + private static final String CONTEXT_FILE_NAME = "meta.json"; + + private final String configLocation; + + private final DockerConfig config; + + private final DockerContext context; + + private DockerConfigurationMetadata(String configLocation, DockerConfig config, DockerContext context) { + this.configLocation = configLocation; + this.config = config; + this.context = context; + } + + DockerConfig getConfiguration() { + return this.config; + } + + DockerContext getContext() { + return this.context; + } + + DockerContext forContext(String context) { + return createDockerContext(this.configLocation, context); + } + + static DockerConfigurationMetadata from(Environment environment) { + String configLocation = (environment.get(DOCKER_CONFIG) != null) ? environment.get(DOCKER_CONFIG) + : Path.of(System.getProperty("user.home"), CONFIG_DIR).toString(); + DockerConfig dockerConfig = createDockerConfig(configLocation); + DockerContext dockerContext = createDockerContext(configLocation, dockerConfig.getCurrentContext()); + return new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext); + } + + private static DockerConfig createDockerConfig(String configLocation) { + Path path = Path.of(configLocation, CONFIG_FILE_NAME); + if (!path.toFile().exists()) { + return DockerConfig.empty(); + } + try { + return DockerConfig.fromJson(readPathContent(path)); + } + catch (JsonProcessingException ex) { + throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex); + } + } + + private static DockerContext createDockerContext(String configLocation, String currentContext) { + if (currentContext == null || DEFAULT_CONTEXT.equals(currentContext)) { + return DockerContext.empty(); + } + Path metaPath = Path.of(configLocation, CONTEXTS_DIR, META_DIR, asHash(currentContext), CONTEXT_FILE_NAME); + Path tlsPath = Path.of(configLocation, CONTEXTS_DIR, TLS_DIR, asHash(currentContext), DOCKER_ENDPOINT); + if (!metaPath.toFile().exists()) { + throw new IllegalArgumentException("Docker context '" + currentContext + "' does not exist"); + } + try { + DockerContext context = DockerContext.fromJson(readPathContent(metaPath)); + if (tlsPath.toFile().isDirectory()) { + return context.withTlsPath(tlsPath.toString()); + } + return context; + } + catch (JsonProcessingException ex) { + throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex); + } + } + + private static String asHash(String currentContext) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(currentContext.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(hash); + } + catch (NoSuchAlgorithmException ex) { + return null; + } + } + + private static String readPathContent(Path path) { + try { + return Files.readString(path); + } + catch (IOException ex) { + throw new IllegalStateException("Error reading Docker configuration file '" + path + "'", ex); + } + } + + static final class DockerConfig extends MappedObject { + + private final String currentContext; + + private DockerConfig(JsonNode node) { + super(node, MethodHandles.lookup()); + this.currentContext = valueAt("/currentContext", String.class); + } + + String getCurrentContext() { + return this.currentContext; + } + + static DockerConfig fromJson(String json) throws JsonProcessingException { + return new DockerConfig(SharedObjectMapper.get().readTree(json)); + } + + static DockerConfig empty() { + return new DockerConfig(NullNode.instance); + } + + } + + static final class DockerContext extends MappedObject { + + private final String dockerHost; + + private final Boolean skipTlsVerify; + + private final String tlsPath; + + private DockerContext(JsonNode node, String tlsPath) { + super(node, MethodHandles.lookup()); + this.dockerHost = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/Host", String.class); + this.skipTlsVerify = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/SkipTLSVerify", Boolean.class); + this.tlsPath = tlsPath; + } + + String getDockerHost() { + return this.dockerHost; + } + + Boolean isTlsVerify() { + return this.skipTlsVerify != null && !this.skipTlsVerify; + } + + String getTlsPath() { + return this.tlsPath; + } + + DockerContext withTlsPath(String tlsPath) { + return new DockerContext(this.getNode(), tlsPath); + } + + static DockerContext fromJson(String json) throws JsonProcessingException { + return new DockerContext(SharedObjectMapper.get().readTree(json), null); + } + + static DockerContext empty() { + return new DockerContext(NullNode.instance, null); + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java index 3db5f5541d57..8d6d381feba4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerHost.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java index 95272b19d0d3..e19d592df08d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHost.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -21,6 +21,8 @@ import com.sun.jna.Platform; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext; import org.springframework.boot.buildpack.platform.system.Environment; /** @@ -43,6 +45,12 @@ public class ResolvedDockerHost extends DockerHost { private static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; + private static final String DOCKER_CONTEXT = "DOCKER_CONTEXT"; + + ResolvedDockerHost(String address) { + super(address); + } + ResolvedDockerHost(String address, boolean secure, String certificatePath) { super(address, secure, certificatePath); } @@ -66,11 +74,20 @@ public boolean isLocalFileReference() { } } - public static ResolvedDockerHost from(DockerHost dockerHost) { + public static ResolvedDockerHost from(DockerHostConfiguration dockerHost) { return from(Environment.SYSTEM, dockerHost); } - static ResolvedDockerHost from(Environment environment, DockerHost dockerHost) { + static ResolvedDockerHost from(Environment environment, DockerHostConfiguration dockerHost) { + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(environment); + if (environment.get(DOCKER_CONTEXT) != null) { + DockerContext context = config.forContext(environment.get(DOCKER_CONTEXT)); + return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); + } + if (dockerHost != null && dockerHost.getContext() != null) { + DockerContext context = config.forContext(dockerHost.getContext()); + return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); + } if (environment.get(DOCKER_HOST) != null) { return new ResolvedDockerHost(environment.get(DOCKER_HOST), isTrue(environment.get(DOCKER_TLS_VERIFY)), environment.get(DOCKER_CERT_PATH)); @@ -79,7 +96,11 @@ static ResolvedDockerHost from(Environment environment, DockerHost dockerHost) { return new ResolvedDockerHost(dockerHost.getAddress(), dockerHost.isSecure(), dockerHost.getCertificatePath()); } - return new ResolvedDockerHost(Platform.isWindows() ? WINDOWS_NAMED_PIPE_PATH : DOMAIN_SOCKET_PATH, false, null); + if (config.getContext().getDockerHost() != null) { + DockerContext context = config.getContext(); + return new ResolvedDockerHost(context.getDockerHost(), context.isTlsVerify(), context.getTlsPath()); + } + return new ResolvedDockerHost(Platform.isWindows() ? WINDOWS_NAMED_PIPE_PATH : DOMAIN_SOCKET_PATH); } private static boolean isTrue(String value) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java index 4a8461fa0907..c428155142d8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.net.URI; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.io.IOConsumer; @@ -93,7 +94,7 @@ public interface HttpTransport { * @param dockerHost the Docker host information * @return a {@link HttpTransport} instance */ - static HttpTransport create(DockerHost dockerHost) { + static HttpTransport create(DockerHostConfiguration dockerHost) { ResolvedDockerHost host = ResolvedDockerHost.from(dockerHost); HttpTransport remote = RemoteHttpClientTransport.createIfPossible(host); return (remote != null) ? remote : LocalHttpClientTransport.create(host); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java index 13241f726c58..a5b1035a1956 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransport.java @@ -20,23 +20,22 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.URISyntaxException; import java.net.UnknownHostException; import com.sun.jna.Platform; import org.apache.hc.client5.http.DnsResolver; -import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.Registry; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; @@ -51,26 +50,22 @@ */ final class LocalHttpClientTransport extends HttpClientTransport { - private static final HttpHost LOCAL_DOCKER_HOST; + private static final String DOCKER_SCHEME = "docker"; - static { - try { - LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost"); - } - catch (URISyntaxException ex) { - throw new RuntimeException("Error creating local Docker host address", ex); - } - } + private static final int DEFAULT_DOCKER_PORT = 2376; - private LocalHttpClientTransport(HttpClient client) { - super(client, LOCAL_DOCKER_HOST); + private static final HttpHost LOCAL_DOCKER_HOST = new HttpHost(DOCKER_SCHEME, "localhost", DEFAULT_DOCKER_PORT); + + private LocalHttpClientTransport(HttpClient client, HttpHost host) { + super(client, host); } static LocalHttpClientTransport create(ResolvedDockerHost dockerHost) { HttpClientBuilder builder = HttpClients.custom(); builder.setConnectionManager(new LocalConnectionManager(dockerHost.getAddress())); - builder.setSchemePortResolver(new LocalSchemePortResolver()); - return new LocalHttpClientTransport(builder.build()); + builder.setRoutePlanner(new LocalRoutePlanner()); + HttpHost host = new HttpHost(DOCKER_SCHEME, dockerHost.getAddress()); + return new LocalHttpClientTransport(builder.build(), host); } /** @@ -84,7 +79,7 @@ private static class LocalConnectionManager extends BasicHttpClientConnectionMan private static Registry getRegistry(String host) { RegistryBuilder builder = RegistryBuilder.create(); - builder.register("docker", new LocalConnectionSocketFactory(host)); + builder.register(DOCKER_SCHEME, new LocalConnectionSocketFactory(host)); return builder.build(); } @@ -139,20 +134,13 @@ public Socket connectSocket(TimeValue connectTimeout, Socket socket, HttpHost ho } /** - * {@link SchemePortResolver} for local Docker. + * {@link HttpRoutePlanner} for local Docker. */ - private static class LocalSchemePortResolver implements SchemePortResolver { - - private static final int DEFAULT_DOCKER_PORT = 2376; + private static class LocalRoutePlanner implements HttpRoutePlanner { @Override - public int resolve(HttpHost host) { - Args.notNull(host, "HTTP host"); - String name = host.getSchemeName(); - if ("docker".equals(name)) { - return DEFAULT_DOCKER_PORT; - } - return -1; + public HttpRoute determineRoute(HttpHost target, HttpContext context) { + return new HttpRoute(LOCAL_DOCKER_HOST); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java index 78ee54874bb0..d8b03407a3d5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java @@ -40,7 +40,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; @@ -246,7 +246,8 @@ void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception { given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(); - createLifecycle(request, ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376"))).execute(); + createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376"))) + .execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json")); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @@ -257,7 +258,8 @@ void executeWithDockerHostAndLocalAddressExecutesPhases() throws Exception { given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); BuildRequest request = getTestRequest(); - createLifecycle(request, ResolvedDockerHost.from(new DockerHost("/var/alt.sock"))).execute(); + createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("/var/alt.sock"))) + .execute(); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json")); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java new file mode 100644 index 000000000000..c619eac31614 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadataTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2023 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.buildpack.platform.docker.configuration; + +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext; +import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link DockerConfigurationMetadata}. + * + * @author Scott Frederick + */ +class DockerConfigurationMetadataTests extends AbstractJsonTests { + + private final Map environment = new LinkedHashMap<>(); + + @Test + void configWithContextIsRead() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("test-context"); + assertThat(config.getContext().getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock"); + assertThat(config.getContext().isTlsVerify()).isFalse(); + assertThat(config.getContext().getTlsPath()).isNull(); + } + + @Test + void configWithoutContextIsRead() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("without-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isNull(); + assertThat(config.getContext().getDockerHost()).isNull(); + assertThat(config.getContext().isTlsVerify()).isFalse(); + assertThat(config.getContext().getTlsPath()).isNull(); + } + + @Test + void configWithDefaultContextIsRead() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("default"); + assertThat(config.getContext().getDockerHost()).isNull(); + assertThat(config.getContext().isTlsVerify()).isFalse(); + assertThat(config.getContext().getTlsPath()).isNull(); + } + + @Test + void configIsReadWithProvidedContext() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + DockerContext context = config.forContext("test-context"); + assertThat(context.getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock"); + assertThat(context.isTlsVerify()).isTrue(); + assertThat(context.getTlsPath()).matches("^.*/with-default-context/contexts/tls/[a-zA-z0-9]*/docker$"); + } + + @Test + void invalidContextThrowsException() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + assertThatIllegalArgumentException() + .isThrownBy(() -> DockerConfigurationMetadata.from(this.environment::get).forContext("invalid-context")) + .withMessageContaining("Docker context 'invalid-context' does not exist"); + } + + @Test + void configIsEmptyWhenConfigFileDoesNotExist() { + this.environment.put("DOCKER_CONFIG", "docker-config-dummy-path"); + DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get); + assertThat(config.getConfiguration().getCurrentContext()).isNull(); + assertThat(config.getContext().getDockerHost()).isNull(); + assertThat(config.getContext().isTlsVerify()).isFalse(); + } + + private String pathToResource(String resource) throws URISyntaxException { + URL url = getClass().getResource(resource); + return Paths.get(url.toURI()).getParent().toAbsolutePath().toString(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java index 30a1c358304b..131299849788 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/ResolvedDockerHostTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -17,8 +17,11 @@ package org.springframework.boot.buildpack.platform.docker.configuration; import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; @@ -28,6 +31,8 @@ import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -41,7 +46,8 @@ class ResolvedDockerHostTests { @Test @DisabledOnOs(OS.WINDOWS) - void resolveWhenDockerHostIsNullReturnsLinuxDefault() { + void resolveWhenDockerHostIsNullReturnsLinuxDefault() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); @@ -50,7 +56,8 @@ void resolveWhenDockerHostIsNullReturnsLinuxDefault() { @Test @EnabledOnOs(OS.WINDOWS) - void resolveWhenDockerHostIsNullReturnsWindowsDefault() { + void resolveWhenDockerHostIsNullReturnsWindowsDefault() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); assertThat(dockerHost.getAddress()).isEqualTo("//./pipe/docker_engine"); assertThat(dockerHost.isSecure()).isFalse(); @@ -59,8 +66,10 @@ void resolveWhenDockerHostIsNullReturnsWindowsDefault() { @Test @DisabledOnOs(OS.WINDOWS) - void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, new DockerHost(null)); + void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + DockerHostConfiguration.forAddress(null)); assertThat(dockerHost.getAddress()).isEqualTo("/var/run/docker.sock"); assertThat(dockerHost.isSecure()).isFalse(); assertThat(dockerHost.getCertificatePath()).isNull(); @@ -70,7 +79,7 @@ void resolveWhenDockerHostAddressIsNullReturnsLinuxDefault() { void resolveWhenDockerHostAddressIsLocalReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost(socketFilePath, false, null)); + DockerHostConfiguration.forAddress(socketFilePath)); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -82,7 +91,7 @@ void resolveWhenDockerHostAddressIsLocalReturnsAddress(@TempDir Path tempDir) th void resolveWhenDockerHostAddressIsLocalWithSchemeReturnsAddress(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("unix://" + socketFilePath, false, null)); + DockerHostConfiguration.forAddress("unix://" + socketFilePath)); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -93,7 +102,7 @@ void resolveWhenDockerHostAddressIsLocalWithSchemeReturnsAddress(@TempDir Path t @Test void resolveWhenDockerHostAddressIsHttpReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("http://docker.example.com", false, null)); + DockerHostConfiguration.forAddress("http://docker.example.com")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("http://docker.example.com"); @@ -104,7 +113,7 @@ void resolveWhenDockerHostAddressIsHttpReturnsAddress() { @Test void resolveWhenDockerHostAddressIsHttpsReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("https://docker.example.com", true, "/cert-path")); + DockerHostConfiguration.forAddress("https://docker.example.com", true, "/cert-path")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("https://docker.example.com"); @@ -115,7 +124,7 @@ void resolveWhenDockerHostAddressIsHttpsReturnsAddress() { @Test void resolveWhenDockerHostAddressIsTcpReturnsAddress() { ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("tcp://192.168.99.100:2376", true, "/cert-path")); + DockerHostConfiguration.forAddress("tcp://192.168.99.100:2376", true, "/cert-path")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); @@ -128,7 +137,7 @@ void resolveWhenEnvironmentAddressIsLocalReturnsAddress(@TempDir Path tempDir) t String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); this.environment.put("DOCKER_HOST", socketFilePath); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("/unused", true, "/unused")); + DockerHostConfiguration.forAddress("/unused")); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -141,7 +150,7 @@ void resolveWhenEnvironmentAddressIsLocalWithSchemeReturnsAddress(@TempDir Path String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); this.environment.put("DOCKER_HOST", "unix://" + socketFilePath); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("/unused", true, "/unused")); + DockerHostConfiguration.forAddress("/unused")); assertThat(dockerHost.isLocalFileReference()).isTrue(); assertThat(dockerHost.isRemote()).isFalse(); assertThat(dockerHost.getAddress()).isEqualTo(socketFilePath); @@ -155,7 +164,7 @@ void resolveWhenEnvironmentAddressIsTcpReturnsAddress() { this.environment.put("DOCKER_TLS_VERIFY", "1"); this.environment.put("DOCKER_CERT_PATH", "/cert-path"); ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, - new DockerHost("tcp://1.1.1.1", false, "/unused")); + DockerHostConfiguration.forAddress("tcp://1.1.1.1")); assertThat(dockerHost.isLocalFileReference()).isFalse(); assertThat(dockerHost.isRemote()).isTrue(); assertThat(dockerHost.getAddress()).isEqualTo("tcp://192.168.99.100:2376"); @@ -163,4 +172,39 @@ void resolveWhenEnvironmentAddressIsTcpReturnsAddress() { assertThat(dockerHost.getCertificatePath()).isEqualTo("/cert-path"); } + @Test + void resolveWithDockerHostContextReturnsAddress() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json")); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, + DockerHostConfiguration.forContext("test-context")); + assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); + assertThat(dockerHost.isSecure()).isTrue(); + assertThat(dockerHost.getCertificatePath()).isNotNull(); + } + + @Test + void resolveWithDockerConfigMetadataContextReturnsAddress() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); + assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + @Test + void resolveWhenEnvironmentHasAddressAndContextPrefersContext() throws Exception { + this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json")); + this.environment.put("DOCKER_CONTEXT", "test-context"); + this.environment.put("DOCKER_HOST", "notused"); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(this.environment::get, null); + assertThat(dockerHost.getAddress()).isEqualTo("/home/user/.docker/docker.sock"); + assertThat(dockerHost.isSecure()).isFalse(); + assertThat(dockerHost.getCertificatePath()).isNull(); + } + + private String pathToResource(String resource) throws URISyntaxException { + URL url = getClass().getResource(resource); + return Paths.get(url.toURI()).getParent().toAbsolutePath().toString(); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java index c04cd5e719db..a383d0240ec9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/HttpTransportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import static org.assertj.core.api.Assertions.assertThat; @@ -37,21 +37,21 @@ class HttpTransportTests { @Test void createWhenDockerHostVariableIsAddressReturnsRemote() { - HttpTransport transport = HttpTransport.create(new DockerHost("tcp://192.168.1.0")); + HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress("tcp://192.168.1.0")); assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsFileReturnsLocal(@TempDir Path tempDir) throws IOException { String dummySocketFilePath = Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString(); - HttpTransport transport = HttpTransport.create(new DockerHost(dummySocketFilePath)); + HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } @Test void createWhenDockerHostVariableIsUnixSchemePrefixedFileReturnsLocal(@TempDir Path tempDir) throws IOException { String dummySocketFilePath = "unix://" + Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath(); - HttpTransport transport = HttpTransport.create(new DockerHost(dummySocketFilePath)); + HttpTransport transport = HttpTransport.create(DockerHostConfiguration.forAddress(dummySocketFilePath)); assertThat(transport).isInstanceOf(LocalHttpClientTransport.class); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java index 78ff1d0c71fe..81cd780c5b04 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/LocalHttpClientTransportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import static org.assertj.core.api.Assertions.assertThat; @@ -39,24 +39,28 @@ class LocalHttpClientTransportTests { @Test void createWhenDockerHostIsFileReturnsTransport(@TempDir Path tempDir) throws IOException { String socketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath().toString(); - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(socketFilePath)); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(socketFilePath)); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); + assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath); } @Test void createWhenDockerHostIsFileThatDoesNotExistReturnsTransport(@TempDir Path tempDir) { String socketFilePath = Paths.get(tempDir.toString(), "dummy").toAbsolutePath().toString(); - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(socketFilePath)); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(socketFilePath)); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); + assertThat(transport.getHost().toHostString()).isEqualTo(socketFilePath); } @Test void createWhenDockerHostIsAddressReturnsTransport() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")); LocalHttpClientTransport transport = LocalHttpClientTransport.create(dockerHost); assertThat(transport).isNotNull(); + assertThat(transport.getHost().toHostString()).isEqualTo("tcp://192.168.1.2:2376"); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java index a56373709eff..529709d5cc38 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransportTests.java @@ -23,7 +23,7 @@ import org.apache.hc.core5.http.HttpHost; import org.junit.jupiter.api.Test; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory; @@ -49,28 +49,31 @@ void createIfPossibleWhenDockerHostIsNotSetReturnsNull() { @Test void createIfPossibleWhenDockerHostIsDefaultReturnsNull() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost(null)); + ResolvedDockerHost dockerHost = ResolvedDockerHost.from(DockerHostConfiguration.forAddress(null)); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test void createIfPossibleWhenDockerHostIsFileReturnsNull() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("unix:///var/run/socket.sock")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("unix:///var/run/socket.sock")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNull(); } @Test void createIfPossibleWhenDockerHostIsAddressReturnsTransport() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport).isNotNull(); } @Test void createIfPossibleWhenNoTlsVerifyUsesHttp() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376")); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost); assertThat(transport.getHost()).satisfies(hostOf("http", "192.168.1.2", 2376)); } @@ -80,14 +83,15 @@ void createIfPossibleWhenTlsVerifyUsesHttps() throws Exception { SslContextFactory sslContextFactory = mock(SslContextFactory.class); given(sslContextFactory.forDirectory("/test-cert-path")).willReturn(SSLContext.getDefault()); ResolvedDockerHost dockerHost = ResolvedDockerHost - .from(new DockerHost("tcp://192.168.1.2:2376", true, "/test-cert-path")); + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376", true, "/test-cert-path")); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(dockerHost, sslContextFactory); assertThat(transport.getHost()).satisfies(hostOf("https", "192.168.1.2", 2376)); } @Test void createIfPossibleWhenTlsVerifyWithMissingCertPathThrowsException() { - ResolvedDockerHost dockerHost = ResolvedDockerHost.from(new DockerHost("tcp://192.168.1.2:2376", true, null)); + ResolvedDockerHost dockerHost = ResolvedDockerHost + .from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376", true, null)); assertThatIllegalArgumentException().isThrownBy(() -> RemoteHttpClientTransport.createIfPossible(dockerHost)) .withMessageContaining("Docker host TLS verification requires trust material"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json new file mode 100644 index 000000000000..7e3fa77f5bfe --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/config.json @@ -0,0 +1,3 @@ +{ + "currentContext": "test-context" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json new file mode 100644 index 000000000000..fa4655b1a026 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json @@ -0,0 +1,12 @@ +{ + "Name": "test-context", + "Metadata": { + "Description": "A context for testing" + }, + "Endpoints": { + "docker": { + "Host": "unix:///home/user/.docker/docker.sock", + "SkipTLSVerify": true + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json new file mode 100644 index 000000000000..6eaf50253da3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/config.json @@ -0,0 +1,3 @@ +{ + "currentContext": "default" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json new file mode 100644 index 000000000000..f072aa2647e2 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/meta/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/meta.json @@ -0,0 +1,12 @@ +{ + "Name": "test-context", + "Metadata": { + "Description": "A context for testing" + }, + "Endpoints": { + "docker": { + "Host": "unix:///home/user/.docker/docker.sock", + "SkipTLSVerify": false + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/cert.pem b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/cert.pem new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/key.pem b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/with-default-context/contexts/tls/ea1b2003cc8155cb8af43960c89a4c1e28777d6fd848ff3422cf375329c2626d/docker/key.pem new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/configuration/without-context/config.json @@ -0,0 +1,2 @@ +{ +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc index c5e429729335..824c79ee0049 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -13,7 +13,8 @@ The task is automatically created when the `java` or `war` plugin is applied and [[build-image.docker-daemon]] == Docker Daemon The `bootBuildImage` task requires access to a Docker daemon. -By default, it will communicate with a Docker daemon over a local connection. +The task will inspect local Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] to determine the current https://docs.docker.com/engine/context/working-with-contexts/[context] and use the context connection information to communicate with a Docker daemon. +If the current context can not be determined or the context does not have connection information, then the task will use a default local connection. This works with https://docs.docker.com/install/[Docker Engine] on all supported platforms without configuration. Environment variables can be set to configure the `bootBuildImage` task to use an alternative local or remote connection. @@ -22,6 +23,12 @@ The following table shows the environment variables and their values: |=== | Environment variable | Description +| DOCKER_CONFIG +| Location of Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] used to determine the current context (defaults to `$HOME/.docker`) + +| DOCKER_CONTEXT +| Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI configuration files (overrides `DOCKER_HOST`) + | DOCKER_HOST | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` @@ -38,6 +45,9 @@ The following table summarizes the available properties: |=== | Property | Description +| `context` +| Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] + | `host` | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java index ce3907a4db16..ffed3ddba17c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/DockerSpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -54,6 +54,10 @@ public DockerSpec(ObjectFactory objects) { this.publishRegistry = publishRegistry; } + @Input + @Optional + public abstract Property getContext(); + @Input @Optional public abstract Property getHost(); @@ -124,7 +128,15 @@ DockerConfiguration asDockerConfiguration() { } private DockerConfiguration customizeHost(DockerConfiguration dockerConfiguration) { + String context = getContext().getOrNull(); String host = getHost().getOrNull(); + if (context != null && host != null) { + throw new GradleException( + "Invalid Docker configuration, either context or host can be provided but not both"); + } + if (context != null) { + return dockerConfiguration.withContext(context); + } if (host != null) { return dockerConfiguration.withHost(host, getTlsVerify().get(), getCertPath().getOrNull()); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java index 7cce2758b0ad..3252cedb2da0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/DockerSpecTests.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -68,10 +68,11 @@ void asDockerConfigurationWithHostConfiguration() { this.dockerSpec.getTlsVerify().set(true); this.dockerSpec.getCertPath().set("/tmp/ca-cert"); DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isTrue(); assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert"); + assertThat(host.getContext()).isNull(); assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) @@ -85,10 +86,11 @@ void asDockerConfigurationWithHostConfiguration() { void asDockerConfigurationWithHostConfigurationNoTlsVerify() { this.dockerSpec.getHost().set("docker.example.com"); DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isFalse(); assertThat(host.getCertificatePath()).isNull(); + assertThat(host.getContext()).isNull(); assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) @@ -98,12 +100,38 @@ void asDockerConfigurationWithHostConfigurationNoTlsVerify() { .contains("\"serveraddress\" : \"\""); } + @Test + void asDockerConfigurationWithContextConfiguration() { + this.dockerSpec.getContext().set("test-context"); + DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); + DockerHostConfiguration host = dockerConfiguration.getHost(); + assertThat(host.getContext()).isEqualTo("test-context"); + assertThat(host.getAddress()).isNull(); + assertThat(host.isSecure()).isFalse(); + assertThat(host.getCertificatePath()).isNull(); + assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); + assertThat(this.dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); + assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) + .contains("\"username\" : \"\"") + .contains("\"password\" : \"\"") + .contains("\"email\" : \"\"") + .contains("\"serveraddress\" : \"\""); + } + + @Test + void asDockerConfigurationWithHostAndContextFails() { + this.dockerSpec.getContext().set("test-context"); + this.dockerSpec.getHost().set("docker.example.com"); + assertThatExceptionOfType(GradleException.class).isThrownBy(this.dockerSpec::asDockerConfiguration) + .withMessageContaining("Invalid Docker configuration"); + } + @Test void asDockerConfigurationWithBindHostToBuilder() { this.dockerSpec.getHost().set("docker.example.com"); this.dockerSpec.getBindHostToBuilder().set(true); DockerConfiguration dockerConfiguration = this.dockerSpec.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isFalse(); assertThat(host.getCertificatePath()).isNull(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc index f9b084ce7eea..09e85abb8209 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -28,7 +28,8 @@ The `spring-boot-devtools` and `spring-boot-docker-compose` modules are automati [[build-image.docker-daemon]] == Docker Daemon The `build-image` goal requires access to a Docker daemon. -By default, it will communicate with a Docker daemon over a local connection. +The goal will inspect local Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] to determine the current https://docs.docker.com/engine/context/working-with-contexts/[context] and use the context connection information to communicate with a Docker daemon. +If the current context can not be determined or the context does not have connection information, then the goal will use a default local connection. This works with https://docs.docker.com/install/[Docker Engine] on all supported platforms without configuration. Environment variables can be set to configure the `build-image` goal to use an alternative local or remote connection. @@ -37,6 +38,12 @@ The following table shows the environment variables and their values: |=== | Environment variable | Description +| DOCKER_CONFIG +| Location of Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] used to determine the current context (defaults to `$HOME/.docker`) + +| DOCKER_CONTEXT +| Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI configuration files (overrides `DOCKER_HOST`) + | DOCKER_HOST | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` @@ -53,6 +60,9 @@ The following table summarizes the available parameters: |=== | Parameter | Description +| `context` +| Name of a https://docs.docker.com/engine/context/working-with-contexts/[context] that should be used to retrieve host information from Docker CLI https://docs.docker.com/engine/reference/commandline/cli/#configuration-files[configuration files] + | `host` | URL containing the host and port for the Docker daemon - for example `tcp://192.168.99.100:2376` diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java index 78e8b5b89b98..53618609d4d7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Docker.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -29,6 +29,8 @@ public class Docker { private String host; + private String context; + private boolean tlsVerify; private String certPath; @@ -51,6 +53,18 @@ void setHost(String host) { this.host = host; } + /** + * The Docker context to use to retrieve host configuration. + * @return the Docker context + */ + public String getContext() { + return this.context; + } + + public void setContext(String context) { + this.context = context; + } + /** * Whether the Docker daemon requires TLS communication. * @return {@code true} to enable TLS @@ -138,6 +152,13 @@ DockerConfiguration asDockerConfiguration() { } private DockerConfiguration customizeHost(DockerConfiguration dockerConfiguration) { + if (this.context != null && this.host != null) { + throw new IllegalArgumentException( + "Invalid Docker configuration, either context or host can be provided but not both"); + } + if (this.context != null) { + return dockerConfiguration.withContext(this.context); + } if (this.host != null) { return dockerConfiguration.withHost(this.host, this.tlsVerify, this.certPath); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java index 1fd381ee28de..f2258b915dc3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/DockerTests.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; -import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost; +import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration.DockerHostConfiguration; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -54,10 +54,11 @@ void asDockerConfigurationWithHostConfiguration() { docker.setTlsVerify(true); docker.setCertPath("/tmp/ca-cert"); DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isTrue(); assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert"); + assertThat(host.getContext()).isNull(); assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); assertThat(docker.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) @@ -67,6 +68,34 @@ void asDockerConfigurationWithHostConfiguration() { .contains("\"serveraddress\" : \"\""); } + @Test + void asDockerConfigurationWithContextConfiguration() { + Docker docker = new Docker(); + docker.setContext("test-context"); + DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(); + DockerHostConfiguration host = dockerConfiguration.getHost(); + assertThat(host.getContext()).isEqualTo("test-context"); + assertThat(host.getAddress()).isNull(); + assertThat(host.isSecure()).isFalse(); + assertThat(host.getCertificatePath()).isNull(); + assertThat(dockerConfiguration.isBindHostToBuilder()).isFalse(); + assertThat(docker.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull(); + assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())) + .contains("\"username\" : \"\"") + .contains("\"password\" : \"\"") + .contains("\"email\" : \"\"") + .contains("\"serveraddress\" : \"\""); + } + + @Test + void asDockerConfigurationWithHostAndContextFails() { + Docker docker = new Docker(); + docker.setContext("test-context"); + docker.setHost("docker.example.com"); + assertThatIllegalArgumentException().isThrownBy(docker::asDockerConfiguration) + .withMessageContaining("Invalid Docker configuration"); + } + @Test void asDockerConfigurationWithBindHostToBuilder() { Docker docker = new Docker(); @@ -75,7 +104,7 @@ void asDockerConfigurationWithBindHostToBuilder() { docker.setCertPath("/tmp/ca-cert"); docker.setBindHostToBuilder(true); DockerConfiguration dockerConfiguration = docker.asDockerConfiguration(); - DockerHost host = dockerConfiguration.getHost(); + DockerHostConfiguration host = dockerConfiguration.getHost(); assertThat(host.getAddress()).isEqualTo("docker.example.com"); assertThat(host.isSecure()).isTrue(); assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert"); From 9efbe5b2eaed42bb2183157ea9ff9bf231f3338e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 19 Jul 2023 07:44:39 +0200 Subject: [PATCH 0165/1656] Upgrade to Spring GraphQL 1.2.2 Closes gh-36191 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 8a55db136838..fd169b6c10ce 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1400,7 +1400,7 @@ bom { ] } } - library("Spring GraphQL", "1.2.2-SNAPSHOT") { + library("Spring GraphQL", "1.2.2") { group("org.springframework.graphql") { modules = [ "spring-graphql", From 0a365acf9594d1f78975b0bcd5523f38d4bd404b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 19 Jul 2023 07:45:15 +0200 Subject: [PATCH 0166/1656] Upgrade to Spring Integration 6.2.0-M1 Closes gh-36193 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index fd169b6c10ce..aa3cce4e2adc 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1415,7 +1415,7 @@ bom { ] } } - library("Spring Integration", "6.2.0-SNAPSHOT") { + library("Spring Integration", "6.2.0-M1") { group("org.springframework.integration") { imports = [ "spring-integration-bom" From 858cadc8e26da3270c0767315cc07459983d8eaf Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 19 Jul 2023 07:46:08 +0200 Subject: [PATCH 0167/1656] Upgrade to Spring LDAP 3.2.0-M1 Closes gh-36299 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index aa3cce4e2adc..5c1a3e103360 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1430,7 +1430,7 @@ bom { ] } } - library("Spring LDAP", "3.2.0-SNAPSHOT") { + library("Spring LDAP", "3.2.0-M1") { group("org.springframework.ldap") { modules = [ "spring-ldap-core", From 2506172d91c14005d080b3a440ef588201196382 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 19 Jul 2023 07:46:39 +0200 Subject: [PATCH 0168/1656] Upgrade to Spring Session 3.2.0-M1 Closes gh-36196 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 5c1a3e103360..08d12e4cbfb6 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1461,7 +1461,7 @@ bom { ] } } - library("Spring Session", "3.2.0-SNAPSHOT") { + library("Spring Session", "3.2.0-M1") { prohibit { startsWith(["Apple-", "Bean-", "Corn-", "Dragonfruit-"]) because "Spring Session switched to numeric version numbers" From abebc396c0b6f41d47be5eafe020ecdd2bba32b0 Mon Sep 17 00:00:00 2001 From: kitbolourchi <74569356+KitBolourchi@users.noreply.github.com> Date: Sat, 24 Jun 2023 15:32:57 +0100 Subject: [PATCH 0169/1656] Change B3 extraction format to single See gh-36061 --- .../tracing/CompositePropagationFactory.java | 2 +- .../tracing/BraveAutoConfigurationTests.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java index 0ec3db30c2b7..2c653b362d4e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/CompositePropagationFactory.java @@ -124,7 +124,7 @@ Propagation.Factory map(PropagationType type) { * @return the B3 propagation factory */ private Propagation.Factory b3Single() { - return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE).build(); } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java index f97f4c8c8c8a..372166ed2555 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java @@ -17,7 +17,9 @@ package org.springframework.boot.actuate.autoconfigure.tracing; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import brave.Span; @@ -31,6 +33,7 @@ import brave.propagation.CurrentTraceContext.ScopeDecorator; import brave.propagation.Propagation; import brave.propagation.Propagation.Factory; +import brave.propagation.TraceContext; import brave.sampler.Sampler; import io.micrometer.tracing.brave.bridge.BraveBaggageManager; import io.micrometer.tracing.brave.bridge.BraveSpanCustomizer; @@ -152,6 +155,31 @@ void shouldSupplyB3PropagationFactoryViaProperty() { }); } + @Test + void shouldUseB3SingleWithParentWhenPropagationTypeIsB3() { + this.contextRunner + .withPropertyValues("management.tracing.propagation.type=B3", "management.tracing.sampling.probability=1.0") + .run((context) -> { + Propagation propagation = context.getBean(Factory.class).get(); + Tracer tracer = context.getBean(Tracing.class).tracer(); + Span child; + Span parent = tracer.nextSpan().name("parent"); + try (Tracer.SpanInScope ignored = tracer.withSpanInScope(parent.start())) { + child = tracer.nextSpan().name("child"); + child.start().finish(); + } + finally { + parent.finish(); + } + + Map map = new HashMap<>(); + TraceContext childContext = child.context(); + propagation.injector(this::injectToMap).inject(childContext, map); + assertThat(map).containsExactly(Map.entry("b3", "%s-%s-1-%s".formatted(childContext.traceIdString(), + childContext.spanIdString(), childContext.parentIdString()))); + }); + } + @Test void shouldNotSupplyCorrelationScopeDecoratorIfBaggageDisabled() { this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") @@ -313,6 +341,10 @@ void compositeSpanHandlerUsesFilterPredicateAndReportersInOrder() { }); } + private void injectToMap(Map map, String key, String value) { + map.put(key, value); + } + private List getInjectors(Factory factory) { assertThat(factory).as("factory").isNotNull(); if (factory instanceof CompositePropagationFactory compositePropagationFactory) { From 0fd3d992b5c7a2266c74a2be9fec2d03b2fdb116 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 19 Jul 2023 20:00:42 +0200 Subject: [PATCH 0170/1656] Upgrade to Spring Framework 6.1.0-M3 Closes gh-36443 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7e5a78d8f27f..aa7abc2f7484 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 kotlinVersion=1.8.22 nativeBuildToolsVersion=0.9.23 -springFrameworkVersion=6.1.0-SNAPSHOT +springFrameworkVersion=6.1.0-M3 tomcatVersion=10.1.11 kotlin.stdlib.default.dependency=false From 4a4b29fcbf6ce803cd30ea21e3b7ebd96f230f48 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 19 Jul 2023 20:01:06 +0200 Subject: [PATCH 0171/1656] Upgrade to Spring Batch 5.1.0-M1 Closes gh-36189 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 08d12e4cbfb6..9b911da4835c 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1379,7 +1379,7 @@ bom { ] } } - library("Spring Batch", "5.1.0-SNAPSHOT") { + library("Spring Batch", "5.1.0-M1") { group("org.springframework.batch") { imports = [ "spring-batch-bom" From 2029117999886d60b1940d516778d3e9ee56ed28 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 19 Jul 2023 20:27:41 +0100 Subject: [PATCH 0172/1656] Upgrade to Kotlin 1.9.0 Closes gh-36362 --- gradle.properties | 2 +- .../KotlinPluginActionIntegrationTests.java | 42 ++++++++----------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/gradle.properties b/gradle.properties index aa7abc2f7484..c0cea14c2595 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.caching=true org.gradle.parallel=true org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 -kotlinVersion=1.8.22 +kotlinVersion=1.9.0 nativeBuildToolsVersion=0.9.23 springFrameworkVersion=6.1.0-M3 tomcatVersion=10.1.11 diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java index cfb35aa41c71..54d8807735da 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java @@ -23,11 +23,11 @@ import java.util.Set; import org.gradle.testkit.runner.BuildResult; -import org.gradle.util.GradleVersion; -import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; +import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -36,43 +36,40 @@ * * @author Andy Wilkinson */ -@GradleCompatibility +@ExtendWith(GradleBuildExtension.class) class KotlinPluginActionIntegrationTests { - GradleBuild gradleBuild; + GradleBuild gradleBuild = new GradleBuild(); - @TestTemplate + @Test void noKotlinVersionPropertyWithoutKotlinPlugin() { assertThat(this.gradleBuild.build("kotlinVersion").getOutput()).contains("Kotlin version: none"); } - @TestTemplate + @Test void kotlinVersionPropertyIsSet() { - String output = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1") - .build("kotlinVersion", "dependencies", "--configuration", "compileClasspath") + String output = this.gradleBuild.build("kotlinVersion", "dependencies", "--configuration", "compileClasspath") .getOutput(); assertThat(output).containsPattern("Kotlin version: [0-9]\\.[0-9]\\.[0-9]+"); } - @TestTemplate + @Test void kotlinCompileTasksUseJavaParametersFlagByDefault() { - assertThat(this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1") - .build("kotlinCompileTasksJavaParameters") - .getOutput()).contains("compileKotlin java parameters: true") + assertThat(this.gradleBuild.build("kotlinCompileTasksJavaParameters").getOutput()) + .contains("compileKotlin java parameters: true") .contains("compileTestKotlin java parameters: true"); } - @TestTemplate + @Test void kotlinCompileTasksCanOverrideDefaultJavaParametersFlag() { - assertThat(this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1") - .build("kotlinCompileTasksJavaParameters") - .getOutput()).contains("compileKotlin java parameters: false") + assertThat(this.gradleBuild.build("kotlinCompileTasksJavaParameters").getOutput()) + .contains("compileKotlin java parameters: false") .contains("compileTestKotlin java parameters: false"); } - @TestTemplate + @Test void taskConfigurationIsAvoided() throws IOException { - BuildResult result = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.1-rc-1").build("help"); + BuildResult result = this.gradleBuild.build("help"); String output = result.getOutput(); BufferedReader reader = new BufferedReader(new StringReader(output)); String line; @@ -82,12 +79,7 @@ void taskConfigurationIsAvoided() throws IOException { configured.add(line.substring("Configuring :".length())); } } - if (GradleVersion.version(this.gradleBuild.getGradleVersion()).compareTo(GradleVersion.version("7.3.3")) < 0) { - assertThat(configured).containsExactly("help"); - } - else { - assertThat(configured).containsExactlyInAnyOrder("help", "clean"); - } + assertThat(configured).containsExactlyInAnyOrder("help", "clean"); } } From a1db6cd63054d3ee28517b7d835bdacddd44d900 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 20 Jul 2023 08:37:40 +0100 Subject: [PATCH 0173/1656] Upgrade to MySQL 8.1.0 Closes gh-36470 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 9b911da4835c..388f9f213e59 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1018,7 +1018,7 @@ bom { ] } } - library("MySQL", "8.0.33") { + library("MySQL", "8.1.0") { group("com.mysql") { modules = [ "mysql-connector-j" { From 8100aba33230fee5a29925bf70062f24db3ef804 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 20 Jul 2023 08:37:45 +0100 Subject: [PATCH 0174/1656] Upgrade to R2DBC MSSQL 1.0.2.RELEASE Closes gh-36471 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 388f9f213e59..fe282c569c8c 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1139,7 +1139,7 @@ bom { ] } } - library("R2DBC MSSQL", "1.0.1.RELEASE") { + library("R2DBC MSSQL", "1.0.2.RELEASE") { group ("io.r2dbc") { modules = [ "r2dbc-mssql" From 34d02659a829892e9d83247a8723b4d34be7f629 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 20 Jul 2023 08:37:50 +0100 Subject: [PATCH 0175/1656] Upgrade to R2DBC Pool 1.0.1.RELEASE Closes gh-36472 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index fe282c569c8c..6dd420394d8a 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1153,7 +1153,7 @@ bom { ] } } - library("R2DBC Pool", "1.0.0.RELEASE") { + library("R2DBC Pool", "1.0.1.RELEASE") { group("io.r2dbc") { modules = [ "r2dbc-pool" From 7e344c0ef6d8caad58368376439dae722ebed502 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 20 Jul 2023 08:37:55 +0100 Subject: [PATCH 0176/1656] Upgrade to R2DBC Postgresql 1.0.2.RELEASE Closes gh-36473 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 6dd420394d8a..c704aaa9efd1 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1160,7 +1160,7 @@ bom { ] } } - library("R2DBC Postgresql", "1.0.1.RELEASE") { + library("R2DBC Postgresql", "1.0.2.RELEASE") { group("org.postgresql") { modules = [ "r2dbc-postgresql" From 90a38b6b0bdfb4d27b79933bbfacc38b8e73c7f3 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 20 Jul 2023 08:37:55 +0100 Subject: [PATCH 0177/1656] Upgrade to Spring HATEOAS 2.2.0-M2 Closes gh-36456 --- spring-boot-project/spring-boot-dependencies/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index c704aaa9efd1..e2d22cde5fe9 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1408,7 +1408,7 @@ bom { ] } } - library("Spring HATEOAS", "2.2.0-M1") { + library("Spring HATEOAS", "2.2.0-M2") { group("org.springframework.hateoas") { modules = [ "spring-hateoas" From 1b34292c21baf01a9b192392efe68b40c5e142b6 Mon Sep 17 00:00:00 2001 From: Spring Builds Date: Thu, 20 Jul 2023 07:42:07 +0000 Subject: [PATCH 0178/1656] Next development version (v2.7.15-SNAPSHOT) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9ac52daf53b2..a870cbd5c344 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=2.7.14-SNAPSHOT +version=2.7.15-SNAPSHOT org.gradle.caching=true org.gradle.parallel=true From 54e99d68fabbf6a810789a74abc4af03b25158d3 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 30 Jun 2023 11:54:31 +0200 Subject: [PATCH 0179/1656] Auto-configure ObservationRegistry on ScheduledTaskRegistrar The TaskSchedulingAutoConfiguration.taskScheduler auto-configuration now no longer backs off on SchedulingConfigurer beans. Closes gh-36119 --- ...edTasksObservabilityAutoConfiguration.java | 63 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...ksObservabilityAutoConfigurationTests.java | 64 +++++++++++++++++++ .../task/TaskSchedulingAutoConfiguration.java | 5 +- .../TaskSchedulingAutoConfigurationTests.java | 11 ---- 5 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfiguration.java new file mode 100644 index 000000000000..a4014d2d3eb5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.scheduling; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +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.context.annotation.Bean; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +/** + * {@link EnableAutoConfiguration Auto-configuration} to enable observability for + * scheduled tasks. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@AutoConfiguration(after = ObservationAutoConfiguration.class) +@ConditionalOnBean(ObservationRegistry.class) +@ConditionalOnClass(ThreadPoolTaskScheduler.class) +public class ScheduledTasksObservabilityAutoConfiguration { + + @Bean + ObservabilitySchedulingConfigurer observabilitySchedulingConfigurer(ObservationRegistry observationRegistry) { + return new ObservabilitySchedulingConfigurer(observationRegistry); + } + + static final class ObservabilitySchedulingConfigurer implements SchedulingConfigurer { + + private final ObservationRegistry observationRegistry; + + ObservabilitySchedulingConfigurer(ObservationRegistry observationRegistry) { + this.observationRegistry = observationRegistry; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setObservationRegistry(this.observationRegistry); + } + + } + +} 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 affbe7607f9e..f2b80c4ffb40 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 @@ -93,6 +93,7 @@ org.springframework.boot.actuate.autoconfigure.r2dbc.ConnectionFactoryHealthCont org.springframework.boot.actuate.autoconfigure.data.redis.RedisHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.data.redis.RedisReactiveHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksObservabilityAutoConfiguration org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration org.springframework.boot.actuate.autoconfigure.session.SessionsEndpointAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfigurationTests.java new file mode 100644 index 000000000000..60ef5d14c5cc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksObservabilityAutoConfigurationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 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.actuate.autoconfigure.scheduling; + +import java.util.List; + +import io.micrometer.observation.ObservationRegistry; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksObservabilityAutoConfiguration.ObservabilitySchedulingConfigurer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ScheduledTasksObservabilityAutoConfiguration}. + * + * @author Moritz Halbritter + */ +class ScheduledTasksObservabilityAutoConfigurationTests { + + private final ApplicationContextRunner runner = new ApplicationContextRunner().withConfiguration(AutoConfigurations + .of(ObservationAutoConfiguration.class, ScheduledTasksObservabilityAutoConfiguration.class)); + + @Test + void shouldProvideObservabilitySchedulingConfigurer() { + this.runner.run((context) -> assertThat(context).hasSingleBean(ObservabilitySchedulingConfigurer.class)); + } + + @Test + void observabilitySchedulingConfigurerShouldConfigureObservationRegistry() { + ObservationRegistry observationRegistry = ObservationRegistry.create(); + ObservabilitySchedulingConfigurer configurer = new ObservabilitySchedulingConfigurer(observationRegistry); + ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar(); + configurer.configureTasks(registrar); + assertThat(registrar.getObservationRegistry()).isEqualTo(observationRegistry); + } + + @Test + void isRegisteredInAutoConfigurationsFile() { + List configurations = ImportCandidates.load(AutoConfiguration.class, null).getCandidates(); + assertThat(configurations).contains(ScheduledTasksObservabilityAutoConfiguration.class.getName()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java index a5dd93bf4f44..d7969608de36 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -31,7 +31,6 @@ import org.springframework.boot.task.TaskSchedulerCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.TaskManagementConfigUtils; @@ -48,7 +47,7 @@ public class TaskSchedulingAutoConfiguration { @Bean @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) - @ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class }) + @ConditionalOnMissingBean({ TaskScheduler.class, ScheduledExecutorService.class }) public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) { return builder.build(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java index 990e8cb6dbe8..8d42ccd05171 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java @@ -122,17 +122,6 @@ void enableSchedulingWithExistingScheduledExecutorServiceBacksOff() { }); } - @Test - void enableSchedulingWithConfigurerBacksOff() { - this.contextRunner.withUserConfiguration(SchedulingConfiguration.class, SchedulingConfigurerConfiguration.class) - .run((context) -> { - assertThat(context).doesNotHaveBean(TaskScheduler.class); - TestBean bean = context.getBean(TestBean.class); - assertThat(bean.latch.await(30, TimeUnit.SECONDS)).isTrue(); - assertThat(bean.threadNames).containsExactly("test-1"); - }); - } - @Test void enableSchedulingWithLazyInitializationInvokeScheduledMethods() { List threadNames = new ArrayList<>(); From cd91dbe62be5cd779154a85df70b651fcfa15cdb Mon Sep 17 00:00:00 2001 From: Spring Builds Date: Thu, 20 Jul 2023 09:48:54 +0000 Subject: [PATCH 0180/1656] Next development version (v3.0.10-SNAPSHOT) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9911e17fab53..e4c1e507f9c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=3.0.9-SNAPSHOT +version=3.0.10-SNAPSHOT org.gradle.caching=true org.gradle.parallel=true From ade0c65f13bd46c0631c751147ba188ab4d0d094 Mon Sep 17 00:00:00 2001 From: Spring Builds Date: Thu, 20 Jul 2023 10:37:00 +0000 Subject: [PATCH 0181/1656] Next development version (v3.1.3-SNAPSHOT) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c49004089b08..f64782697eba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=3.1.2-SNAPSHOT +version=3.1.3-SNAPSHOT org.gradle.caching=true org.gradle.parallel=true From 63121dd08a7af4203b8c563b1d0433d8eb2f8e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 19 Apr 2023 10:34:19 -0600 Subject: [PATCH 0182/1656] Add service connection for Testcontainers ActiveMQ See gh-35080 --- .../activemq/ActiveMQAutoConfiguration.java | 36 ++++++++ .../activemq/ActiveMQConnectionDetails.java | 35 ++++++++ ...ctiveMQConnectionFactoryConfiguration.java | 22 +++-- .../ActiveMQConnectionFactoryFactory.java | 21 ++--- .../jms/activemq/ActiveMQProperties.java | 10 +++ ...iveMQXAConnectionFactoryConfiguration.java | 13 +-- .../ActiveMQAutoConfigurationTests.java | 47 ++++++++++ .../jms/activemq/ActiveMQPropertiesTests.java | 8 +- .../src/docs/asciidoc/features/testing.adoc | 3 + .../spring-boot-testcontainers/build.gradle | 2 + ...veMQContainerConnectionDetailsFactory.java | 79 ++++++++++++++++ .../connection/activemq/package-info.java | 20 +++++ .../main/resources/META-INF/spring.factories | 1 + ...nectionDetailsFactoryIntegrationTests.java | 90 +++++++++++++++++++ .../build.gradle | 2 + .../activemq/SampleActiveMqTests.java | 17 ++-- 16 files changed, 367 insertions(+), 39 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java index da642a7f8689..44c3cebe3082 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; /** @@ -35,6 +36,7 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Eddú Meléndez * @since 3.1.0 */ @AutoConfiguration(before = JmsAutoConfiguration.class, after = JndiConnectionFactoryAutoConfiguration.class) @@ -44,4 +46,38 @@ @Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class }) public class ActiveMQAutoConfiguration { + @Bean + @ConditionalOnMissingBean(ActiveMQConnectionDetails.class) + ActiveMQConnectionDetails activemqConnectionDetails(ActiveMQProperties properties) { + return new PropertiesActiveMQConnectionDetails(properties); + } + + /** + * Adapts {@link ActiveMQProperties} to {@link ActiveMQConnectionDetails}. + */ + static class PropertiesActiveMQConnectionDetails implements ActiveMQConnectionDetails { + + private final ActiveMQProperties properties; + + PropertiesActiveMQConnectionDetails(ActiveMQProperties properties) { + this.properties = properties; + } + + @Override + public String getBrokerUrl() { + return this.properties.determineBrokerUrl(); + } + + @Override + public String getUser() { + return this.properties.getUser(); + } + + @Override + public String getPassword() { + return this.properties.getPassword(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java new file mode 100644 index 000000000000..b139215095f8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.activemq; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an ActiveMQ service. + * + * @author Eddú Meléndez + * @since 3.1.0 + */ +public interface ActiveMQConnectionDetails extends ConnectionDetails { + + String getBrokerUrl(); + + String getUser(); + + String getPassword(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java index a55337e58a2c..a4d242600e51 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java @@ -39,6 +39,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Aurélien Leboulanger + * @author Eddú Meléndez */ @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(ConnectionFactory.class) @@ -52,13 +53,16 @@ static class SimpleConnectionFactoryConfiguration { @Bean @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return createJmsConnectionFactory(properties, factoryCustomizers); + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + return createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails); } private static ActiveMQConnectionFactory createJmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList(), + connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); } @@ -70,10 +74,11 @@ static class CachingConnectionFactoryConfiguration { @Bean CachingConnectionFactory jmsConnectionFactory(JmsProperties jmsProperties, ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { JmsProperties.Cache cacheProperties = jmsProperties.getCache(); CachingConnectionFactory connectionFactory = new CachingConnectionFactory( - createJmsConnectionFactory(properties, factoryCustomizers)); + createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails)); connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); connectionFactory.setCacheProducers(cacheProperties.isProducers()); connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); @@ -91,9 +96,10 @@ static class PooledConnectionFactoryConfiguration { @Bean(destroyMethod = "stop") @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true") JmsPoolConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, - factoryCustomizers.orderedStream().toList()) + factoryCustomizers.orderedStream().toList(), connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); return new JmsPoolConnectionFactoryFactory(properties.getPool()) .createPooledConnectionFactory(connectionFactory); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java index b571860491f0..67768c0363ae 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java @@ -32,20 +32,22 @@ * * @author Phillip Webb * @author Venil Noronha + * @author Eddú Meléndez */ class ActiveMQConnectionFactoryFactory { - private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; - private final ActiveMQProperties properties; private final List factoryCustomizers; + private final ActiveMQConnectionDetails connectionDetails; + ActiveMQConnectionFactoryFactory(ActiveMQProperties properties, - List factoryCustomizers) { + List factoryCustomizers, ActiveMQConnectionDetails connectionDetails) { Assert.notNull(properties, "Properties must not be null"); this.properties = properties; this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList(); + this.connectionDetails = connectionDetails; } T createConnectionFactory(Class factoryClass) { @@ -79,9 +81,9 @@ private T doCreateConnectionFactory(Class< private T createConnectionFactoryInstance(Class factoryClass) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { - String brokerUrl = determineBrokerUrl(); - String user = this.properties.getUser(); - String password = this.properties.getPassword(); + String brokerUrl = this.connectionDetails.getBrokerUrl(); + String user = this.connectionDetails.getUser(); + String password = this.connectionDetails.getPassword(); if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) { return factoryClass.getConstructor(String.class, String.class, String.class) .newInstance(user, password, brokerUrl); @@ -95,11 +97,4 @@ private void customize(ActiveMQConnectionFactory connectionFactory) { } } - String determineBrokerUrl() { - if (this.properties.getBrokerUrl() != null) { - return this.properties.getBrokerUrl(); - } - return DEFAULT_NETWORK_BROKER_URL; - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java index 48b72e88935c..2877479a08e6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java @@ -31,11 +31,14 @@ * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Venil Noronha + * @author Eddú Meléndez * @since 3.1.0 */ @ConfigurationProperties(prefix = "spring.activemq") public class ActiveMQProperties { + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; + /** * URL of the ActiveMQ broker. Auto-generated by default. */ @@ -128,6 +131,13 @@ public Packages getPackages() { return this.packages; } + String determineBrokerUrl() { + if (this.brokerUrl != null) { + return this.brokerUrl; + } + return DEFAULT_NETWORK_BROKER_URL; + } + public static class Packages { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java index 4a7cbd214cea..6458c5824ae3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java @@ -36,6 +36,7 @@ * * @author Phillip Webb * @author Aurélien Leboulanger + * @author Eddú Meléndez */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(TransactionManager.class) @@ -46,10 +47,10 @@ class ActiveMQXAConnectionFactoryConfiguration { @Primary @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers, XAConnectionFactoryWrapper wrapper) - throws Exception { + ObjectProvider factoryCustomizers, XAConnectionFactoryWrapper wrapper, + ActiveMQConnectionDetails connectionDetails) throws Exception { ActiveMQXAConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, - factoryCustomizers.orderedStream().toList()) + factoryCustomizers.orderedStream().toList(), connectionDetails) .createConnectionFactory(ActiveMQXAConnectionFactory.class); return wrapper.wrapConnectionFactory(connectionFactory); } @@ -58,8 +59,10 @@ ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", matchIfMissing = true) ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList(), + connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java index 0edd4270c7ee..3e1b0980d88f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java @@ -40,6 +40,7 @@ * @author Andy Wilkinson * @author Aurélien Leboulanger * @author Stephane Nicoll + * @author Eddú Meléndez */ class ActiveMQAutoConfigurationTests { @@ -233,6 +234,27 @@ void cachingConnectionFactoryNotOnTheClasspathAndCacheEnabledThenSimpleConnectio .doesNotHaveBean("jmsConnectionFactory")); } + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner.run((context) -> assertThat(context) + .hasSingleBean(ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails.class)); + } + + @Test + void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CachingConnectionFactory.class)) + .withPropertyValues("spring.activemq.pool.enabled=false", "spring.jms.cache.enabled=false") + .withUserConfiguration(TestConnectionDetailsConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ActiveMQConnectionDetails.class) + .doesNotHaveBean(ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails.class); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(connectionFactory.getBrokerURL()).isEqualTo("tcp://localhost:12345"); + assertThat(connectionFactory.getUserName()).isEqualTo("springuser"); + assertThat(connectionFactory.getPassword()).isEqualTo("spring"); + }); + } + @Configuration(proxyBeanMethods = false) static class EmptyConfiguration { @@ -261,4 +283,29 @@ ActiveMQConnectionFactoryCustomizer activeMQConnectionFactoryCustomizer() { } + @Configuration(proxyBeanMethods = false) + static class TestConnectionDetailsConfiguration { + + @Bean + ActiveMQConnectionDetails activemqConnectionDetails() { + return new ActiveMQConnectionDetails() { + @Override + public String getBrokerUrl() { + return "tcp://localhost:12345"; + } + + @Override + public String getUser() { + return "springuser"; + } + + @Override + public String getPassword() { + return "spring"; + } + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java index d6b66bc12390..07d84e900e7a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java @@ -29,6 +29,7 @@ * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Venil Noronha + * @author Eddú Meléndez */ class ActiveMQPropertiesTests { @@ -38,13 +39,13 @@ class ActiveMQPropertiesTests { @Test void getBrokerUrlIsLocalhostByDefault() { - assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); } @Test void getBrokerUrlUseExplicitBrokerUrl() { this.properties.setBrokerUrl("tcp://activemq.example.com:71717"); - assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); + assertThat(this.properties.determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); } @Test @@ -66,7 +67,8 @@ void setTrustedPackages() { } private ActiveMQConnectionFactoryFactory createFactory(ActiveMQProperties properties) { - return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList()); + return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList(), + new ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails(properties)); } } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc index 436deb91f7dd..57ded5512638 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc @@ -946,6 +946,9 @@ The following service connection factories are provided in the `spring-boot-test |=== | Connection Details | Matched on +| `ActiveMQConnectionDetails` +| Containers named "symptoma/activemq" + | `CassandraConnectionDetails` | Containers of type `CassandraContainer` diff --git a/spring-boot-project/spring-boot-testcontainers/build.gradle b/spring-boot-project/spring-boot-testcontainers/build.gradle index 9cfb9709582e..2d20062409a8 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation("ch.qos.logback:logback-classic") + testImplementation("org.apache.activemq:activemq-client-jakarta") testImplementation("org.assertj:assertj-core") testImplementation("org.awaitility:awaitility") testImplementation("org.influxdb:influxdb-java") @@ -45,6 +46,7 @@ dependencies { testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.springframework:spring-core-test") + testImplementation("org.springframework:spring-jms") testImplementation("org.springframework:spring-r2dbc") testImplementation("org.springframework.amqp:spring-rabbit") testImplementation("org.springframework.kafka:spring-kafka") diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..a81469645a30 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 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.testcontainers.service.connection.activemq; + +import java.util.Map; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link ActiveMQConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} + * using the {@code "symptoma/activemq"} image. + * + * @author Eddú Meléndez + */ +class ActiveMQContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { + + ActiveMQContainerConnectionDetailsFactory() { + super("symptoma/activemq"); + } + + @Override + protected ActiveMQConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + return new ActiveMQContainerConnectionDetails(source); + } + + private static final class ActiveMQContainerConnectionDetails extends ContainerConnectionDetails + implements ActiveMQConnectionDetails { + + private final String brokerUrl; + + private final Map envVars; + + private ActiveMQContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + this.brokerUrl = "tcp://" + source.getContainer().getHost() + ":" + + source.getContainer().getFirstMappedPort(); + this.envVars = source.getContainer().getEnvMap(); + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getUser() { + return this.envVars.get("ACTIVEMQ_USERNAME"); + } + + @Override + public String getPassword() { + return this.envVars.get("ACTIVEMQ_PASSWORD"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java new file mode 100644 index 000000000000..0981f1bf9b21 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 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. + */ + +/** + * Support for testcontainers ActiveMQ service connections. + */ +package org.springframework.boot.testcontainers.service.connection.activemq; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 2cfe37359c38..f26cc7230f68 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -8,6 +8,7 @@ org.springframework.boot.testcontainers.service.connection.ServiceConnectionCont # Connection Details Factories org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ +org.springframework.boot.testcontainers.service.connection.activemq.ActiveMQContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.amqp.RabbitContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.cassandra.CassandraContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseContainerConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..647b4861d086 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2023 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.testcontainers.service.connection.activemq; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.testcontainers.ActiveMQContainer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.jms.core.JmsMessagingTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class ActiveMQContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final ActiveMQContainer activemq = new ActiveMQContainer(); + + @Autowired + private JmsMessagingTemplate jmsTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToActiveMQContainer() { + this.jmsTemplate.convertAndSend("sample.queue", "message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("message")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class }) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @JmsListener(destination = "sample.queue") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle index 5ad092f62cb1..f250b84c075d 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle @@ -8,6 +8,8 @@ description = "Spring Boot Actuator ActiveMQ smoke test" dependencies { implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-activemq")) + testImplementation("org.awaitility:awaitility") + testImplementation("org.testcontainers:testcontainers") testImplementation("org.testcontainers:junit-jupiter") testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) testImplementation(project(":spring-boot-project:spring-boot-testcontainers")) diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java index 7637a28f8869..9d68eda78d2b 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java @@ -16,6 +16,9 @@ package smoketest.activemq; +import java.time.Duration; + +import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.junit.jupiter.Container; @@ -25,9 +28,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.ActiveMQContainer; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; import static org.assertj.core.api.Assertions.assertThat; @@ -43,21 +45,16 @@ class SampleActiveMqTests { @Container + @ServiceConnection private static final ActiveMQContainer container = new ActiveMQContainer(); - @DynamicPropertySource - static void activeMqProperties(DynamicPropertyRegistry registry) { - registry.add("spring.activemq.broker-url", container::getBrokerUrl); - } - @Autowired private Producer producer; @Test - void sendSimpleMessage(CapturedOutput output) throws InterruptedException { + void sendSimpleMessage(CapturedOutput output) { this.producer.send("Test message"); - Thread.sleep(1000L); - assertThat(output).contains("Test message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(output).contains("Test message")); } } From 311fa6272dd3f2ee63358f4ab09db3cddf9c2181 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 17 Jul 2023 16:51:46 +0200 Subject: [PATCH 0183/1656] Polish "Add service connection for Testcontainers ActiveMQ" This also adds support for Docker Compose. See gh-35080 --- .../activemq/ActiveMQConnectionDetails.java | 15 +++- ...DockerComposeConnectionDetailsFactory.java | 79 +++++++++++++++++++ .../activemq/ActiveMQEnvironment.java | 45 +++++++++++ .../connection/activemq/package-info.java | 20 +++++ .../main/resources/META-INF/spring.factories | 1 + ...nectionDetailsFactoryIntegrationTests.java | 45 +++++++++++ .../activemq/ActiveMQEnvironmentTests.java | 57 +++++++++++++ .../connection/activemq/activemq-compose.yaml | 8 ++ .../asciidoc/features/docker-compose.adoc | 3 + ...veMQContainerConnectionDetailsFactory.java | 19 ++--- .../build.gradle | 1 - 11 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactory.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironment.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/package-info.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironmentTests.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-compose.yaml diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java index b139215095f8..9c095cfda901 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java @@ -22,14 +22,27 @@ * Details required to establish a connection to an ActiveMQ service. * * @author Eddú Meléndez - * @since 3.1.0 + * @author Stephane Nicoll + * @since 3.2.0 */ public interface ActiveMQConnectionDetails extends ConnectionDetails { + /** + * Broker URL to use. + * @return the url of the broker + */ String getBrokerUrl(); + /** + * Login user to authenticate to the broker. + * @return the login user to authenticate to the broker or {@code null} + */ String getUser(); + /** + * Login to authenticate against the broker. + * @return the login to authenticate against the broker or {@code null} + */ String getPassword(); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..ac3809d8da21 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 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.docker.compose.service.connection.activemq; + +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create + * {@link ActiveMQConnectionDetails} for an {@code activemq} service. + * + * @author Stephane Nicoll + */ +class ActiveMQDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int ACTIVEMQ_PORT = 61616; + + protected ActiveMQDockerComposeConnectionDetailsFactory() { + super("symptoma/activemq"); + } + + @Override + protected ActiveMQConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ActiveMQDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link RabbitConnectionDetails} backed by a {@code rabbitmq} + * {@link RunningService}. + */ + static class ActiveMQDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements ActiveMQConnectionDetails { + + private final ActiveMQEnvironment environment; + + private final String brokerUrl; + + protected ActiveMQDockerComposeConnectionDetails(RunningService service) { + super(service); + this.environment = new ActiveMQEnvironment(service.env()); + this.brokerUrl = "tcp://" + service.host() + ":" + service.ports().get(ACTIVEMQ_PORT); + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getUser() { + return this.environment.getUser(); + } + + @Override + public String getPassword() { + return this.environment.getPassword(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironment.java new file mode 100644 index 000000000000..742389e80a7e --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironment.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 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.docker.compose.service.connection.activemq; + +import java.util.Map; + +/** + * ActiveMQ environment details. + * + * @author Stephane Nicoll + */ +class ActiveMQEnvironment { + + private final String user; + + private final String password; + + ActiveMQEnvironment(Map env) { + this.user = env.get("ACTIVEMQ_USERNAME"); + this.password = env.get("ACTIVEMQ_PASSWORD"); + } + + String getUser() { + return this.user; + } + + String getPassword() { + return this.password; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/package-info.java new file mode 100644 index 000000000000..5cb2e75cf5b4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 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. + */ + +/** + * Auto-configuration for docker compose ActiveMQ service connections. + */ +package org.springframework.boot.docker.compose.service.connection.activemq; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index 7c6623fbe5c9..cd5dc75bb4cb 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -5,6 +5,7 @@ org.springframework.boot.docker.compose.service.connection.DockerComposeServiceC # Connection Details Factories org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ +org.springframework.boot.docker.compose.service.connection.activemq.ActiveMQDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.cassandra.CassandraDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..82f26133aa9f --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 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.docker.compose.service.connection.activemq; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ActiveMQDockerComposeConnectionDetailsFactory}. + * + * @author Stephane Nicoll + */ +class ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { + + ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests() { + super("activemq-compose.yaml"); + } + + @Test + void runCreatesConnectionDetails() { + ActiveMQConnectionDetails connectionDetails = run(ActiveMQConnectionDetails.class); + assertThat(connectionDetails.getBrokerUrl()).isNotNull().startsWith("tcp://"); + assertThat(connectionDetails.getUser()).isEqualTo("root"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironmentTests.java new file mode 100644 index 000000000000..04ee5929788f --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ActiveMQEnvironmentTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2023 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.docker.compose.service.connection.activemq; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQEnvironment}. + * + * @author Stephane Nicoll + */ +class ActiveMQEnvironmentTests { + + @Test + void getUserWhenHasNoActiveMqUser() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Collections.emptyMap()); + assertThat(environment.getUser()).isNull(); + } + + @Test + void getUserWhenHasActiveMqUser() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Map.of("ACTIVEMQ_USERNAME", "me")); + assertThat(environment.getUser()).isEqualTo("me"); + } + + @Test + void getPasswordWhenHasNoActiveMqPassword() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Collections.emptyMap()); + assertThat(environment.getPassword()).isNull(); + } + + @Test + void getPasswordWhenHasActiveMqPassword() { + ActiveMQEnvironment environment = new ActiveMQEnvironment(Map.of("ACTIVEMQ_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-compose.yaml new file mode 100644 index 000000000000..7c005eb6dafc --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/activemq/activemq-compose.yaml @@ -0,0 +1,8 @@ +services: + activemq: + image: 'symptoma/activemq:5.18.0' + ports: + - '61616' + environment: + ACTIVEMQ_USERNAME: 'root' + ACTIVEMQ_PASSWORD: 'secret' diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc index 329bfe408e30..cd8ed4ad45f2 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc @@ -58,6 +58,9 @@ The following service connections are currently supported: |=== | Connection Details | Matched on +| `ActiveMQConnectionDetails` +| Containers named "symptoma/activemq" + | `CassandraConnectionDetails` | Containers named "cassandra" diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java index a81469645a30..82324da487ee 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java @@ -16,8 +16,6 @@ package org.springframework.boot.testcontainers.service.connection.activemq; -import java.util.Map; - import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; @@ -34,7 +32,7 @@ * @author Eddú Meléndez */ class ActiveMQContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory> { + extends ContainerConnectionDetailsFactory, ActiveMQConnectionDetails> { ActiveMQContainerConnectionDetailsFactory() { super("symptoma/activemq"); @@ -45,33 +43,26 @@ protected ActiveMQConnectionDetails getContainerConnectionDetails(ContainerConne return new ActiveMQContainerConnectionDetails(source); } - private static final class ActiveMQContainerConnectionDetails extends ContainerConnectionDetails + private static final class ActiveMQContainerConnectionDetails extends ContainerConnectionDetails> implements ActiveMQConnectionDetails { - private final String brokerUrl; - - private final Map envVars; - private ActiveMQContainerConnectionDetails(ContainerConnectionSource> source) { super(source); - this.brokerUrl = "tcp://" + source.getContainer().getHost() + ":" - + source.getContainer().getFirstMappedPort(); - this.envVars = source.getContainer().getEnvMap(); } @Override public String getBrokerUrl() { - return this.brokerUrl; + return "tcp://" + getContainer().getHost() + ":" + getContainer().getFirstMappedPort(); } @Override public String getUser() { - return this.envVars.get("ACTIVEMQ_USERNAME"); + return getContainer().getEnvMap().get("ACTIVEMQ_USERNAME"); } @Override public String getPassword() { - return this.envVars.get("ACTIVEMQ_PASSWORD"); + return getContainer().getEnvMap().get("ACTIVEMQ_PASSWORD"); } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle index f250b84c075d..963f63814ac7 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle @@ -9,7 +9,6 @@ dependencies { implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-activemq")) testImplementation("org.awaitility:awaitility") - testImplementation("org.testcontainers:testcontainers") testImplementation("org.testcontainers:junit-jupiter") testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) testImplementation(project(":spring-boot-project:spring-boot-testcontainers")) From 5022e120c17ca832dc2b07e6059f7b431aeab7ca Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 20 Jul 2023 13:16:44 +0200 Subject: [PATCH 0184/1656] Configure bomr to filter GraphQL "snapshot" versions See gh-33817 --- spring-boot-project/spring-boot-dependencies/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 435e97be8a95..34856ae4a7a8 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -322,6 +322,10 @@ bom { } } library("GraphQL Java", "20.4") { + prohibit { + startsWith(["2018-", "2019-", "2020-", "2021-", "230521-"]) + because "These are snapshots that we don't want to see" + } group("com.graphql-java") { modules = [ "graphql-java" From 3affb3342e3ba6c37d62b23fd4f03fb76276c434 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 20 Jul 2023 12:25:01 +0100 Subject: [PATCH 0185/1656] Deprecate auto-configuration for InfluxDB Closes gh-35190 --- ...fluxDbHealthContributorAutoConfiguration.java | 7 ++++++- ...bHealthContributorAutoConfigurationTests.java | 2 ++ .../actuate/influx/InfluxDbHealthIndicator.java | 6 +++++- .../influx/InfluxDbHealthIndicatorTests.java | 2 ++ .../influx/InfluxDbAutoConfiguration.java | 5 +++++ .../autoconfigure/influx/InfluxDbCustomizer.java | 6 +++++- .../InfluxDbOkHttpClientBuilderProvider.java | 6 +++++- .../autoconfigure/influx/InfluxDbProperties.java | 10 +++++++++- .../influx/InfluxDbAutoConfigurationTests.java | 3 +++ .../src/docs/asciidoc/data/nosql.adoc | 16 ++++++---------- 10 files changed, 48 insertions(+), 15 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.java index 7f93279fde82..2a9b13603f90 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -37,11 +37,16 @@ * * @author Eddú Meléndez * @since 2.0.0 + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the + * new client and its own + * Spring Boot integration. */ +@SuppressWarnings("removal") @AutoConfiguration(after = InfluxDbAutoConfiguration.class) @ConditionalOnClass(InfluxDB.class) @ConditionalOnBean(InfluxDB.class) @ConditionalOnEnabledHealthIndicator("influxdb") +@Deprecated(since = "3.2.0", forRemoval = true) public class InfluxDbHealthContributorAutoConfiguration extends CompositeHealthContributorConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java index 4ed923d5041f..64d2757f26dd 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java @@ -32,6 +32,8 @@ * * @author Eddú Meléndez */ +@SuppressWarnings("removal") +@Deprecated(since = "3.2.0", forRemoval = true) class InfluxDbHealthContributorAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java index 4896ff6094e2..f58586fb925c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -29,7 +29,11 @@ * * @author Eddú Meléndez * @since 2.0.0 + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the + * new client and its own + * Spring Boot integration. */ +@Deprecated(since = "3.2.0", forRemoval = true) public class InfluxDbHealthIndicator extends AbstractHealthIndicator { private final InfluxDB influxDb; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java index 367b1ec99113..f874582108fd 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java @@ -36,6 +36,8 @@ * * @author Eddú Meléndez */ +@SuppressWarnings("removal") +@Deprecated(since = "3.2.0", forRemoval = true) class InfluxDbHealthIndicatorTests { @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java index 5641d5182184..904541755a4b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java @@ -39,11 +39,16 @@ * @author Andy Wilkinson * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the + * new client and its own + * Spring Boot integration. */ @AutoConfiguration @ConditionalOnClass(InfluxDB.class) @EnableConfigurationProperties(InfluxDbProperties.class) @ConditionalOnProperty("spring.influx.url") +@Deprecated(since = "3.2.0", forRemoval = true) +@SuppressWarnings("removal") public class InfluxDbAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java index 9e46dd17fa3e..62f9b0df9998 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -24,8 +24,12 @@ * * @author Eddú Meléndez * @since 2.5.0 + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the + * new client and its own + * Spring Boot integration. */ @FunctionalInterface +@Deprecated(since = "3.2.0", forRemoval = true) public interface InfluxDbCustomizer { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.java index 67dc383089de..14995dba425f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2023 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. @@ -27,8 +27,12 @@ * * @author Stephane Nicoll * @since 2.1.0 + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the + * new client and its own + * Spring Boot integration. */ @FunctionalInterface +@Deprecated(since = "3.2.0", forRemoval = true) public interface InfluxDbOkHttpClientBuilderProvider extends Supplier { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java index d8a4c07d5b68..e4fd9d26b18d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.influx; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * Configuration properties for InfluxDB. @@ -24,7 +25,11 @@ * @author Sergey Kuptsov * @author Stephane Nicoll * @since 2.0.0 + * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the + * new InfluxDB Java + * client and its own Spring Boot integration. */ +@Deprecated(since = "3.2.0", forRemoval = true) @ConfigurationProperties(prefix = "spring.influx") public class InfluxDbProperties { @@ -43,6 +48,7 @@ public class InfluxDbProperties { */ private String password; + @DeprecatedConfigurationProperty(reason = "the new InfluxDb Java client provides Spring Boot integration") public String getUrl() { return this.url; } @@ -51,6 +57,7 @@ public void setUrl(String url) { this.url = url; } + @DeprecatedConfigurationProperty(reason = "the new InfluxDb Java client provides Spring Boot integration") public String getUser() { return this.user; } @@ -59,6 +66,7 @@ public void setUser(String user) { this.user = user; } + @DeprecatedConfigurationProperty(reason = "the new InfluxDb Java client provides Spring Boot integration") public String getPassword() { return this.password; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java index 7d37ee994cc4..fade2e3b2816 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java @@ -42,6 +42,8 @@ * @author Andy Wilkinson * @author Phillip Webb */ +@SuppressWarnings("removal") +@Deprecated(since = "3.2.0", forRemoval = true) class InfluxDbAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -102,6 +104,7 @@ private int getReadTimeoutProperty(AssertableApplicationContext context) { static class CustomOkHttpClientBuilderProviderConfig { @Bean + @SuppressWarnings("removal") InfluxDbOkHttpClientBuilderProvider influxDbOkHttpClientBuilderProvider() { return () -> new OkHttpClient.Builder().readTimeout(40, TimeUnit.SECONDS); } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc index be74814f2651..2ffbe9da428a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc @@ -11,7 +11,8 @@ Spring Data provides additional projects that help you access a variety of NoSQL * {spring-data-couchbase}[Couchbase] * {spring-data-ldap}[LDAP] -Spring Boot provides auto-configuration for Redis, MongoDB, Neo4j, Elasticsearch, Cassandra, Couchbase, LDAP and InfluxDB. +Spring Boot provides auto-configuration for Redis, MongoDB, Neo4j, Elasticsearch, Cassandra, Couchbase, LDAP. +Auto-configuration for InfluxDB is also provided but it is deprecated in favor of https://github.com/influxdata/influxdb-client-java[the new InfluxDB Java client] that provides its own Spring Boot integration. Additionally, {spring-boot-for-apache-geode}[Spring Boot for Apache Geode] provides {spring-boot-for-apache-geode-docs}#geode-repositories[auto-configuration for Apache Geode]. You can make use of the other projects, but you must configure them yourself. See the appropriate reference documentation at {spring-data}. @@ -637,22 +638,17 @@ If you have custom attributes, you can use configprop:spring.ldap.embedded.valid [[data.nosql.influxdb]] === InfluxDB +WARNING: Auto-configuration for InfluxDB is deprecated and scheduled for removal in Spring Boot 3.4 in favor of https://github.com/influxdata/influxdb-client-java[the new InfluxDB Java client] that provides its own Spring Boot integration. + https://www.influxdata.com/[InfluxDB] is an open-source time series database optimized for fast, high-availability storage and retrieval of time series data in fields such as operations monitoring, application metrics, Internet-of-Things sensor data, and real-time analytics. [[data.nosql.influxdb.connecting]] ==== Connecting to InfluxDB -Spring Boot auto-configures an `InfluxDB` instance, provided the `influxdb-java` client is on the classpath and the URL of the database is set, as shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - influx: - url: "https://172.0.0.1:8086" ----- +Spring Boot auto-configures an `InfluxDB` instance, provided the `influxdb-java` client is on the classpath and the URL of the database is set using configprop:spring.influx.url[deprecated]. -If the connection to InfluxDB requires a user and password, you can set the `spring.influx.user` and `spring.influx.password` properties accordingly. +If the connection to InfluxDB requires a user and password, you can set the configprop:spring.influx.user[deprecated] and configprop:spring.influx.password[deprecated] properties accordingly. InfluxDB relies on OkHttp. If you need to tune the http client `InfluxDB` uses behind the scenes, you can register an `InfluxDbOkHttpClientBuilderProvider` bean. From 1effd3723fc2d0d5175e305c17107b4d5a768845 Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Thu, 20 Jul 2023 22:12:57 +0900 Subject: [PATCH 0186/1656] Correct description of overrides for spring.redis.url See gh-36477 --- .../boot/autoconfigure/data/redis/RedisProperties.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index 89cb977a62cb..b767cfa5184d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -41,7 +41,7 @@ public class RedisProperties { private int database = 0; /** - * Connection URL. Overrides host, port, and password. User is ignored. Example: + * Connection URL. Overrides host, port, username, and password. Example: * redis://user:password@example.com:6379 */ private String url; From 5a0f1bbe9b10099ab39a4d972d10c7acd1f0d60f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 21 Jul 2023 14:47:54 +0100 Subject: [PATCH 0187/1656] Polish "Correct description of overrides for spring.redis.url" See gh-36477 --- .../boot/autoconfigure/data/redis/RedisProperties.java | 2 +- .../data/redis/RedisAutoConfigurationTests.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index b767cfa5184d..8b707c038a02 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index c779d02b5ef6..a0d3ed0f4ae4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -139,8 +139,9 @@ void testRedisUrlConfiguration() { @Test void testOverrideUrlRedisConfiguration() { this.contextRunner - .withPropertyValues("spring.redis.host:foo", "spring.redis.password:xyz", "spring.redis.port:1000", - "spring.redis.ssl:false", "spring.redis.url:rediss://user:password@example:33") + .withPropertyValues("spring.redis.host:foo", "spring.redis.user:alice", "spring.redis.password:xyz", + "spring.redis.port:1000", "spring.redis.ssl:false", + "spring.redis.url:rediss://user:password@example:33") .run((context) -> { LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class); assertThat(cf.getHostName()).isEqualTo("example"); From 343c9c6f7ec03ecaa400329c44bbff6dd9e606a2 Mon Sep 17 00:00:00 2001 From: Christoph Dreis Date: Thu, 18 May 2023 17:20:08 +0200 Subject: [PATCH 0188/1656] Remove references to Atomikos and Bitronix See gh-35562 --- ...itional-spring-configuration-metadata.json | 161 ------ .../docs/asciidoc/anchor-rewrite.properties | 1 - ...itional-spring-configuration-metadata.json | 479 ------------------ 3 files changed, 641 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index be821293afaa..8bfa50b648c1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1584,167 +1584,6 @@ "name": "spring.jpa.open-in-view", "defaultValue": true }, - { - "name": "spring.jta.bitronix.properties.allow-multiple-lrc", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.asynchronous2-pc", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.background-recovery-interval", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.background-recovery-interval-seconds", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.current-node-only-recovery", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.debug-zero-resource-transaction", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.default-transaction-timeout", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.disable-jmx", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.exception-analyzer", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.filter-log-status", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.force-batching-enabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.forced-write-enabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.graceful-shutdown-interval", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.jndi-transaction-synchronization-registry-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.jndi-user-transaction-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.journal", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.log-part1-filename", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.log-part2-filename", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.max-log-size-in-mb", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.resource-configuration-filename", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.server-id", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.skip-corrupted-logs", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.properties.warn-about-zero-resource-transaction", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, { "name": "spring.jta.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties index 44164b239812..fce3906da371 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties @@ -306,7 +306,6 @@ boot-features-webclient-customization=features.webclient.customization boot-features-validation=features.validation boot-features-email=features.email boot-features-jta=features.jta -boot-features-jta-atomikos=features.jta.atomikos boot-features-jta-javaee=features.jta.javaee boot-features-jta-mixed-jms=features.jta.mixing-xa-and-non-xa-connections boot-features-jta-supporting-alternative-embedded=features.jta.supporting-alternative-embedded-transaction-manager diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index d4bd08721b86..5bc1a19d991f 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -361,485 +361,6 @@ "description": "Whether to defer DataSource initialization until after any EntityManagerFactory beans have been created and initialized.", "defaultValue": false }, - { - "name": "spring.jta.atomikos.connectionfactory.borrow-connection-timeout", - "type": "java.lang.Integer", - "description": "Timeout, in seconds, for borrowing connections from the pool.", - "defaultValue": 30 - }, - { - "name": "spring.jta.atomikos.connectionfactory.ignore-session-transacted-flag", - "type": "java.lang.Boolean", - "description": "Whether to ignore the transacted flag when creating session.", - "defaultValue": true - }, - { - "name": "spring.jta.atomikos.connectionfactory.local-transaction-mode", - "type": "java.lang.Boolean", - "description": "Whether local transactions are desired.", - "defaultValue": false - }, - { - "name": "spring.jta.atomikos.connectionfactory.maintenance-interval", - "type": "java.lang.Integer", - "description": "Time, in seconds, between runs of the pool's maintenance thread.", - "defaultValue": 60 - }, - { - "name": "spring.jta.atomikos.connectionfactory.max-idle-time", - "type": "java.lang.Integer", - "description": "Time, in seconds, after which connections are cleaned up from the pool.", - "defaultValue": 60 - }, - { - "name": "spring.jta.atomikos.connectionfactory.max-lifetime", - "type": "java.lang.Integer", - "description": "Time, in seconds, that a connection can be pooled for before being destroyed. 0 denotes no limit.", - "defaultValue": 0 - }, - { - "name": "spring.jta.atomikos.connectionfactory.max-pool-size", - "type": "java.lang.Integer", - "description": "Maximum size of the pool.", - "defaultValue": 1 - }, - { - "name": "spring.jta.atomikos.connectionfactory.min-pool-size", - "type": "java.lang.Integer", - "description": "Minimum size of the pool.", - "defaultValue": 1 - }, - { - "name": "spring.jta.atomikos.connectionfactory.reap-timeout", - "type": "java.lang.Integer", - "description": "Reap timeout, in seconds, for borrowed connections. 0 denotes no limit.", - "defaultValue": 0 - }, - { - "name": "spring.jta.atomikos.connectionfactory.unique-resource-name", - "type": "java.lang.String", - "description": "Unique name used to identify the resource during recovery.", - "defaultValue": "jmsConnectionFactory" - }, - { - "name": "spring.jta.atomikos.connectionfactory.xa-connection-factory-class-name", - "type": "java.lang.String", - "description": "Vendor-specific implementation of XAConnectionFactory." - }, - { - "name": "spring.jta.atomikos.connectionfactory.xa-properties", - "type": "java.util.Properties", - "description": "Vendor-specific XA properties." - }, - { - "name": "spring.jta.atomikos.datasource.borrow-connection-timeout", - "type": "java.lang.Integer", - "description": "Timeout, in seconds, for borrowing connections from the pool.", - "defaultValue": 30 - }, - { - "name": "spring.jta.atomikos.datasource.concurrent-connection-validation", - "type": "java.lang.Boolean", - "description": "Whether to use concurrent connection validation.", - "defaultValue": true - }, - { - "name": "spring.jta.atomikos.datasource.default-isolation-level", - "type": "java.lang.Integer", - "description": "Default isolation level of connections provided by the pool." - }, - { - "name": "spring.jta.atomikos.datasource.login-timeout", - "type": "java.lang.Integer", - "description": "Timeout, in seconds, for establishing a database connection.", - "defaultValue": 0 - }, - { - "name": "spring.jta.atomikos.datasource.maintenance-interval", - "type": "java.lang.Integer", - "description": "Time, in seconds, between runs of the pool's maintenance thread.", - "defaultValue": 60 - }, - { - "name": "spring.jta.atomikos.datasource.max-idle-time", - "type": "java.lang.Integer", - "description": "Time, in seconds, after which connections are cleaned up from the pool.", - "defaultValue": 60 - }, - { - "name": "spring.jta.atomikos.datasource.max-lifetime", - "type": "java.lang.Integer", - "description": "Time, in seconds, that a connection can be pooled for before being destroyed. 0 denotes no limit.", - "defaultValue": 0 - }, - { - "name": "spring.jta.atomikos.datasource.max-pool-size", - "type": "java.lang.Integer", - "description": "Maximum size of the pool.", - "defaultValue": 1 - }, - { - "name": "spring.jta.atomikos.datasource.min-pool-size", - "type": "java.lang.Integer", - "description": "Minimum size of the pool.", - "defaultValue": 1 - }, - { - "name": "spring.jta.atomikos.datasource.reap-timeout", - "type": "java.lang.Integer", - "description": "Reap timeout, in seconds, for borrowed connections. 0 denotes no limit.", - "defaultValue": 0 - }, - { - "name": "spring.jta.atomikos.datasource.test-query", - "type": "java.lang.String", - "description": "SQL query or statement used to validate a connection before returning it." - }, - { - "name": "spring.jta.atomikos.datasource.unique-resource-name", - "type": "java.lang.String", - "description": "Unique name used to identify the resource during recovery.", - "defaultValue": "dataSource" - }, - { - "name": "spring.jta.atomikos.datasource.xa-data-source-class-name", - "type": "java.lang.String", - "description": "Vendor-specific implementation of XAConnectionFactory." - }, - { - "name": "spring.jta.atomikos.datasource.xa-properties", - "type": "java.util.Properties", - "description": "Vendor-specific XA properties." - }, - { - "name": "spring.jta.bitronix.connectionfactory.acquire-increment", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.acquisition-interval", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.acquisition-timeout", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.allow-local-transactions", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.apply-transaction-timeout", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.automatic-enlisting-enabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.cache-producers-consumers", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.class-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.defer-connection-release", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.disabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.driver-properties", - "type": "java.util.Properties", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.ignore-recovery-failures", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.max-idle-time", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.max-pool-size", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.min-pool-size", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.password", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.share-transaction-connections", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.test-connections", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.two-pc-ordering-position", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.unique-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.use-tm-join", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.connectionfactory.user", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.acquire-increment", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.acquisition-interval", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.acquisition-timeout", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.allow-local-transactions", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.apply-transaction-timeout", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.automatic-enlisting-enabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.class-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.cursor-holdability", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.defer-connection-release", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.disabled", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.driver-properties", - "type": "java.util.Properties", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.enable-jdbc4-connection-test", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.ignore-recovery-failures", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.isolation-level", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.local-auto-commit", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.login-timeout", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.max-idle-time", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.max-pool-size", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.min-pool-size", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.prepared-statement-cache-size", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.share-transaction-connections", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.test-query", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.two-pc-ordering-position", - "type": "java.lang.Integer", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.unique-name", - "type": "java.lang.String", - "deprecation": { - "level": "error" - } - }, - { - "name": "spring.jta.bitronix.datasource.use-tm-join", - "type": "java.lang.Boolean", - "deprecation": { - "level": "error" - } - }, { "name": "spring.main.allow-bean-definition-overriding", "type": "java.lang.Boolean", From 12254b11ce9f14fd03cfb37ed2095abce9a27e15 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 21 Jul 2023 16:24:32 +0100 Subject: [PATCH 0189/1656] Don't run KotlinPluginActionITs on JVMs not support by Gradle 7.6 --- .../boot/gradle/plugin/KotlinPluginActionIntegrationTests.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java index 54d8807735da..ab32302702fc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/KotlinPluginActionIntegrationTests.java @@ -24,6 +24,8 @@ import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; @@ -36,6 +38,7 @@ * * @author Andy Wilkinson */ +@DisabledForJreRange(min = JRE.JAVA_20) @ExtendWith(GradleBuildExtension.class) class KotlinPluginActionIntegrationTests { From 0308de167263d55b5c7ac89a6a4f95266c357e3e Mon Sep 17 00:00:00 2001 From: Urs Keller Date: Wed, 10 May 2023 10:21:24 +0200 Subject: [PATCH 0190/1656] Don't run process-aot or process-test-aot on reactor projects See gh-35377 --- .../java/org/springframework/boot/maven/AbstractAotMojo.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java index 89287a2985ca..1c5397d3776c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java @@ -97,6 +97,10 @@ public abstract class AbstractAotMojo extends AbstractDependencyFilterMojo { @Override public void execute() throws MojoExecutionException, MojoFailureException { + if (this.project.getPackaging().equals("pom")) { + getLog().debug("process-*aot goals could not be applied to pom project."); + return; + } if (this.skip) { getLog().debug("Skipping AOT execution as per configuration"); return; From 84c1c5ccaa8abded69f411db8c4a63391590b839 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 21 Jul 2023 16:39:50 +0100 Subject: [PATCH 0191/1656] Polish "Don't run process-aot or process-test-aot on reactor projects" See gh-35377 --- .../org/springframework/boot/maven/AbstractAotMojo.java | 4 ---- .../java/org/springframework/boot/maven/ProcessAotMojo.java | 6 +++++- .../org/springframework/boot/maven/ProcessTestAotMojo.java | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java index 1c5397d3776c..89287a2985ca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java @@ -97,10 +97,6 @@ public abstract class AbstractAotMojo extends AbstractDependencyFilterMojo { @Override public void execute() throws MojoExecutionException, MojoFailureException { - if (this.project.getPackaging().equals("pom")) { - getLog().debug("process-*aot goals could not be applied to pom project."); - return; - } if (this.skip) { getLog().debug("Skipping AOT execution as per configuration"); return; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java index 314f94510bf7..9f75cf1e5a37 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -88,6 +88,10 @@ public class ProcessAotMojo extends AbstractAotMojo { @Override protected void executeAot() throws Exception { + if (this.project.getPackaging().equals("pom")) { + getLog().debug("process-aot goal could not be applied to pom project."); + return; + } String applicationClass = (this.mainClass != null) ? this.mainClass : SpringBootApplicationClassFinder.findSingleClass(this.classesDirectory); URL[] classPath = getClassPath(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java index ea888c599230..a1b93ac3c259 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java @@ -118,6 +118,10 @@ public class ProcessTestAotMojo extends AbstractAotMojo { @Override protected void executeAot() throws Exception { + if (this.project.getPackaging().equals("pom")) { + getLog().debug("process-test-aot goal could not be applied to pom project."); + return; + } if (Boolean.getBoolean("skipTests") || Boolean.getBoolean("maven.test.skip")) { getLog().info("Skipping AOT test processing since tests are skipped"); return; From 9112b581d6ad797d6485a2f3af940b89009b394c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 12:39:04 +0200 Subject: [PATCH 0192/1656] Upgrade Java 8 version in CI image and .sdkmanrc Closes gh-36461 --- .sdkmanrc | 2 +- ci/images/get-jdk-url.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.sdkmanrc b/.sdkmanrc index efa0e43ec434..7a4fc046f16a 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=8.0.372-librca +java=8.0.382-librca diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index 0c030b44371e..156fa4c357da 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,7 +3,7 @@ set -e case "$1" in java8) - echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/8u382+6/bellsoft-jdk8u382+6-linux-amd64.tar.gz" ;; java11) echo "https://github.com/bell-sw/Liberica/releases/download/11.0.19+7/bellsoft-jdk11.0.19+7-linux-amd64.tar.gz" From c0c0b9a0f294ee9a1eb3d81ef798a1e3d16374ef Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 12:40:32 +0200 Subject: [PATCH 0193/1656] Upgrade Java 11 version in CI image Closes gh-36462 --- ci/images/get-jdk-url.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index 156fa4c357da..221ca1f24c40 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -6,7 +6,7 @@ case "$1" in echo "https://github.com/bell-sw/Liberica/releases/download/8u382+6/bellsoft-jdk8u382+6-linux-amd64.tar.gz" ;; java11) - echo "https://github.com/bell-sw/Liberica/releases/download/11.0.19+7/bellsoft-jdk11.0.19+7-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/11.0.20+8/bellsoft-jdk11.0.20+8-linux-amd64.tar.gz" ;; java17) echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz" From 33669909bdc4c88455e63fcfb5951a70ed4287e1 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 12:41:04 +0200 Subject: [PATCH 0194/1656] Upgrade Java 17 version in CI image Closes gh-36460 --- ci/images/get-jdk-url.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index 221ca1f24c40..a3331dc165e3 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -9,7 +9,7 @@ case "$1" in echo "https://github.com/bell-sw/Liberica/releases/download/11.0.20+8/bellsoft-jdk11.0.20+8-linux-amd64.tar.gz" ;; java17) - echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/17.0.8+7/bellsoft-jdk17.0.8+7-linux-amd64.tar.gz" ;; java20) echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz" From eb467f64bb9ffac66d0a111fbcc809077a60b4f2 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 12:42:55 +0200 Subject: [PATCH 0195/1656] Upgrade Java 17 version in CI image and .sdkmanrc Closes gh-36457 --- .sdkmanrc | 2 +- ci/images/get-jdk-url.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.sdkmanrc b/.sdkmanrc index 93afdeb201db..326fad3a9204 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=17.0.7-librca +java=17.0.8-librca diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index ec2505e5b13d..aaba8c5178f5 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,7 +3,7 @@ set -e case "$1" in java17) - echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/17.0.8+7/bellsoft-jdk17.0.8+7-linux-amd64.tar.gz" ;; java20) echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz" From e869a9dafeee179adc7f392f94c62711a0a8442c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 12:46:00 +0200 Subject: [PATCH 0196/1656] Upgrade Java 17 version in CI image and .sdkmanrc Closes gh-36458 --- .sdkmanrc | 2 +- ci/images/get-jdk-url.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.sdkmanrc b/.sdkmanrc index 93afdeb201db..326fad3a9204 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=17.0.7-librca +java=17.0.8-librca diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index ec2505e5b13d..aaba8c5178f5 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,7 +3,7 @@ set -e case "$1" in java17) - echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/17.0.8+7/bellsoft-jdk17.0.8+7-linux-amd64.tar.gz" ;; java20) echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz" From fe95c26a149927794e25e3c5a01572bccb5714a8 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 12:46:49 +0200 Subject: [PATCH 0197/1656] Upgrade Java 17 version in CI image and .sdkmanrc Closes gh-36459 --- .sdkmanrc | 2 +- ci/images/get-jdk-url.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.sdkmanrc b/.sdkmanrc index 93afdeb201db..326fad3a9204 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=17.0.7-librca +java=17.0.8-librca diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index 6dc041e1d64e..80ca4cdb1a96 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,7 +3,7 @@ set -e case "$1" in java17) - echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/17.0.8+7/bellsoft-jdk17.0.8+7-linux-amd64.tar.gz" ;; java20) echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz" From e67bca121bd3ba74594a9df0c7f13f88e6339505 Mon Sep 17 00:00:00 2001 From: fzyzcjy <5236035+fzyzcjy@users.noreply.github.com> Date: Sun, 23 Jul 2023 17:04:53 +0800 Subject: [PATCH 0198/1656] Fix description of started and ready time metrics See gh-36507 --- .../actuate/metrics/startup/StartupTimeMetricsListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/startup/StartupTimeMetricsListener.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/startup/StartupTimeMetricsListener.java index 63ba53fdb53c..b8cd0e2063d2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/startup/StartupTimeMetricsListener.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/startup/StartupTimeMetricsListener.java @@ -103,12 +103,12 @@ public void onApplicationEvent(ApplicationEvent event) { } private void onApplicationStarted(ApplicationStartedEvent event) { - registerGauge(this.startedTimeMetricName, "Time taken (ms) to start the application", event.getTimeTaken(), + registerGauge(this.startedTimeMetricName, "Time taken to start the application", event.getTimeTaken(), event.getSpringApplication()); } private void onApplicationReady(ApplicationReadyEvent event) { - registerGauge(this.readyTimeMetricName, "Time taken (ms) for the application to be ready to service requests", + registerGauge(this.readyTimeMetricName, "Time taken for the application to be ready to service requests", event.getTimeTaken(), event.getSpringApplication()); } From d59cec9e01dd8c0c541fea3b8c87e44ac1c84010 Mon Sep 17 00:00:00 2001 From: elevne Date: Thu, 20 Jul 2023 10:37:36 +0900 Subject: [PATCH 0199/1656] Harmonize use of Stream in ConfigDataLocationBindHandler.onSuccess See gh-36463 --- .../config/ConfigDataLocationBindHandler.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java index 2ccf982a046c..5f612d44f089 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java @@ -16,6 +16,7 @@ package org.springframework.boot.context.config; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -44,23 +45,16 @@ public Object onSuccess(ConfigurationPropertyName name, Bindable target, Bind return withOrigin(context, (ConfigDataLocation) result); } if (result instanceof List) { - List list = ((List) result).stream().filter(Objects::nonNull).collect(Collectors.toList()); - for (int i = 0; i < list.size(); i++) { - Object element = list.get(i); - if (element instanceof ConfigDataLocation) { - list.set(i, withOrigin(context, (ConfigDataLocation) element)); - } - } - return list; + return ((List) result).stream() + .filter(Objects::nonNull) + .map(e -> (e instanceof ConfigDataLocation) ? withOrigin(context, (ConfigDataLocation) e) : e) + .collect(Collectors.toCollection(ArrayList::new)); } if (result instanceof ConfigDataLocation[]) { - ConfigDataLocation[] locations = Arrays.stream((ConfigDataLocation[]) result) + return Arrays.stream((ConfigDataLocation[]) result) .filter(Objects::nonNull) + .map(e -> withOrigin(context, e)) .toArray(ConfigDataLocation[]::new); - for (int i = 0; i < locations.length; i++) { - locations[i] = withOrigin(context, locations[i]); - } - return locations; } return result; } From cc77b8ace1d6c168e6bc115c43531eb5c21dde4c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 13:14:01 +0200 Subject: [PATCH 0200/1656] Polish contribution See gh-36463 --- .../boot/context/config/ConfigDataLocationBindHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java index 5f612d44f089..58b8e74d992e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java @@ -47,13 +47,14 @@ public Object onSuccess(ConfigurationPropertyName name, Bindable target, Bind if (result instanceof List) { return ((List) result).stream() .filter(Objects::nonNull) - .map(e -> (e instanceof ConfigDataLocation) ? withOrigin(context, (ConfigDataLocation) e) : e) + .map((element) -> (element instanceof ConfigDataLocation) + ? withOrigin(context, (ConfigDataLocation) element) : element) .collect(Collectors.toCollection(ArrayList::new)); } if (result instanceof ConfigDataLocation[]) { return Arrays.stream((ConfigDataLocation[]) result) .filter(Objects::nonNull) - .map(e -> withOrigin(context, e)) + .map((element) -> withOrigin(context, element)) .toArray(ConfigDataLocation[]::new); } return result; From 3889b1e8558157f484dbdad3f73d163a1019ab0c Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Tue, 11 Jan 2022 23:19:11 +0100 Subject: [PATCH 0201/1656] Improve project setup for IntelliJ IDEA This commit improves project setup for IntelliJ IDEA by adding code style and inspection profile project files to version control. See gh-29446 --- .gitignore | 5 +- .idea/codeStyles/Project.xml | 121 +++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/inspectionProfiles/Project_Default.xml | 6 + 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.gitignore b/.gitignore index 6edbdcda3124..e7a9b8440fcb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,10 @@ .classpath .factorypath .gradle -.idea +!.idea/ +.idea/* +!.idea/codeStyles +!.idea/inspectionProfiles .metadata .project .recommenders diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000000..fda7def5b943 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,121 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000000..79ee123c2b23 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000000..80a42643de08 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file From 5663aaa7ec97aab133b709f79665764d961ff2fa Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 24 Jul 2023 14:35:13 +0200 Subject: [PATCH 0202/1656] Polish "Improve project setup for IntelliJ IDEA" See gh-29446 --- .gitignore | 4 - .idea/.gitignore | 10 ++ .idea/codeStyles/Project.xml | 4 +- .idea/copyright/java.xml | 6 + .idea/copyright/profiles_settings.xml | 7 + .idea/inspectionProfiles/Project_Default.xml | 2 +- .idea/scopes/java.xml | 3 + idea/codeStyleConfig.xml | 128 ------------------- 8 files changed, 29 insertions(+), 135 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/copyright/java.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/scopes/java.xml delete mode 100644 idea/codeStyleConfig.xml diff --git a/.gitignore b/.gitignore index e7a9b8440fcb..a5256ecef4fa 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,6 @@ .classpath .factorypath .gradle -!.idea/ -.idea/* -!.idea/codeStyles -!.idea/inspectionProfiles .metadata .project .recommenders diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000000..f1e07ef8c39f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +.name +*.xml +/modules/ +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index fda7def5b943..854b5bf05230 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -6,7 +6,7 @@ -