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 a249b4d921a..e54ac378151 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 @@ -87,7 +87,6 @@ public abstract class DocumentConfigurationProperties extends DefaultTask { config.accept("spring.autoconfigure"); config.accept("spring.banner"); config.accept("spring.beaninfo"); - config.accept("spring.codec"); config.accept("spring.config"); config.accept("spring.info"); config.accept("spring.jmx"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/codec/CodecProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/codec/CodecProperties.java index d6420b121fb..9f8403908e8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/codec/CodecProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/codec/CodecProperties.java @@ -16,7 +16,9 @@ package org.springframework.boot.autoconfigure.codec; +import org.springframework.boot.autoconfigure.http.codec.HttpCodecsProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.util.unit.DataSize; /** @@ -24,8 +26,10 @@ import org.springframework.util.unit.DataSize; * * @author Brian Clozel * @since 2.2.1 + * @deprecated since 3.5.0 for removal in 4.0.0 in favor of {@link HttpCodecsProperties} */ @ConfigurationProperties("spring.codec") +@Deprecated(since = "3.5.0", forRemoval = true) public class CodecProperties { /** @@ -41,6 +45,7 @@ public class CodecProperties { */ private DataSize maxInMemorySize; + @DeprecatedConfigurationProperty(since = "3.5.0", replacement = "spring.http.codec.log-request-details") public boolean isLogRequestDetails() { return this.logRequestDetails; } @@ -49,6 +54,7 @@ public class CodecProperties { this.logRequestDetails = logRequestDetails; } + @DeprecatedConfigurationProperty(since = "3.5.0", replacement = "spring.http.codec.max-in-memory-size") public DataSize getMaxInMemorySize() { return this.maxInMemorySize; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfiguration.java index b8f70389710..ace3402974a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfiguration.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.codec.CodecProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -29,7 +28,9 @@ import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.web.codec.CodecCustomizer; 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.core.env.Environment; import org.springframework.http.codec.CodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; @@ -47,7 +48,6 @@ import org.springframework.web.reactive.function.client.WebClient; */ @AutoConfiguration(after = JacksonAutoConfiguration.class) @ConditionalOnClass({ CodecConfigurer.class, WebClient.class }) -@EnableConfigurationProperties(CodecProperties.class) public class CodecsAutoConfiguration { private static final MimeType[] EMPTY_MIME_TYPES = {}; @@ -69,21 +69,48 @@ public class CodecsAutoConfiguration { } + @SuppressWarnings("removal") @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties({ org.springframework.boot.autoconfigure.codec.CodecProperties.class, + HttpCodecsProperties.class }) static class DefaultCodecsConfiguration { @Bean - @Order(0) - CodecCustomizer defaultCodecCustomizer(CodecProperties codecProperties) { - return (configurer) -> { + DefaultCodecCustomizer defaultCodecCustomizer( + org.springframework.boot.autoconfigure.codec.CodecProperties codecProperties, + HttpCodecsProperties httpCodecProperties, Environment environment) { + return new DefaultCodecCustomizer( + httpCodecProperties.isLogRequestDetails(codecProperties::isLogRequestDetails), + httpCodecProperties.getMaxInMemorySize(codecProperties::getMaxInMemorySize)); + } + + static final class DefaultCodecCustomizer implements CodecCustomizer, Ordered { + + private final boolean logRequestDetails; + + private final DataSize maxInMemorySize; + + DefaultCodecCustomizer(boolean logRequestDetails, DataSize maxInMemorySize) { + this.logRequestDetails = logRequestDetails; + this.maxInMemorySize = maxInMemorySize; + } + + @Override + public void customize(CodecConfigurer configurer) { PropertyMapper map = PropertyMapper.get(); CodecConfigurer.DefaultCodecs defaultCodecs = configurer.defaultCodecs(); - defaultCodecs.enableLoggingRequestDetails(codecProperties.isLogRequestDetails()); - map.from(codecProperties.getMaxInMemorySize()) + defaultCodecs.enableLoggingRequestDetails(this.logRequestDetails); + map.from(this.maxInMemorySize) .whenNonNull() .asInt(DataSize::toBytes) .to(defaultCodecs::maxInMemorySize); - }; + } + + @Override + public int getOrder() { + return 0; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/codec/HttpCodecsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/codec/HttpCodecsProperties.java new file mode 100644 index 00000000000..2320016a7ee --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/codec/HttpCodecsProperties.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.http.codec; + +import java.util.function.Supplier; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.unit.DataSize; + +/** + * {@link ConfigurationProperties Properties} for reactive HTTP codecs. + * + * @author Brian Clozel + * @author Andy Wilkinson + * @since 3.5.0 + */ +@ConfigurationProperties("spring.http.codecs") +public class HttpCodecsProperties { + + /** + * Whether to log form data at DEBUG level, and headers at TRACE level. + */ + private boolean logRequestDetails; + + @Deprecated(since = "3.5.0", forRemoval = true) + private boolean logRequestDetailsBound = false; + + /** + * Limit on the number of bytes that can be buffered whenever the input stream needs + * to be aggregated. This applies only to the auto-configured WebFlux server and + * WebClient instances. By default this is not set, in which case individual codec + * defaults apply. Most codecs are limited to 256K by default. + */ + private DataSize maxInMemorySize; + + @Deprecated(since = "3.5.0", forRemoval = true) + private boolean maxInMemorySizeBound = false; + + public boolean isLogRequestDetails() { + return this.logRequestDetails; + } + + boolean isLogRequestDetails(Supplier fallback) { + return this.logRequestDetailsBound ? this.logRequestDetails : fallback.get(); + } + + public void setLogRequestDetails(boolean logRequestDetails) { + this.logRequestDetails = logRequestDetails; + this.logRequestDetailsBound = true; + } + + public DataSize getMaxInMemorySize() { + return this.maxInMemorySize; + } + + DataSize getMaxInMemorySize(Supplier fallback) { + return this.maxInMemorySizeBound ? this.maxInMemorySize : fallback.get(); + } + + public void setMaxInMemorySize(DataSize maxInMemorySize) { + this.maxInMemorySize = maxInMemorySize; + this.maxInMemorySizeBound = true; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfigurationTests.java index a32ee3da12e..21364bd062d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/codec/CodecsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,21 @@ package org.springframework.boot.autoconfigure.http.codec; -import java.lang.reflect.Method; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.codec.CodecProperties; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.Ordered; import org.springframework.http.codec.CodecConfigurer; +import org.springframework.http.codec.CodecConfigurer.DefaultCodecs; import org.springframework.http.codec.support.DefaultClientCodecConfigurer; -import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -48,34 +47,58 @@ class CodecsAutoConfigurationTests { @Test void autoConfigShouldProvideALoggingRequestDetailsCustomizer() { - this.contextRunner.run((context) -> { - CodecCustomizer customizer = context.getBean(CodecCustomizer.class); - CodecConfigurer configurer = new DefaultClientCodecConfigurer(); - customizer.customize(configurer); - assertThat(configurer.defaultCodecs()).hasFieldOrPropertyWithValue("enableLoggingRequestDetails", false); - }); - + this.contextRunner.run((context) -> assertThat(defaultCodecs(context)) + .hasFieldOrPropertyWithValue("enableLoggingRequestDetails", false)); } @Test - void loggingRequestDetailsCustomizerShouldUseHttpProperties() { - this.contextRunner.withPropertyValues("spring.codec.log-request-details=true").run((context) -> { - CodecCustomizer customizer = context.getBean(CodecCustomizer.class); - CodecConfigurer configurer = new DefaultClientCodecConfigurer(); - customizer.customize(configurer); - assertThat(configurer.defaultCodecs()).hasFieldOrPropertyWithValue("enableLoggingRequestDetails", true); - }); + void loggingRequestDetailsCustomizerShouldUseCodecProperties() { + this.contextRunner.withPropertyValues("spring.codec.log-request-details=true") + .run((context) -> assertThat(defaultCodecs(context)) + .hasFieldOrPropertyWithValue("enableLoggingRequestDetails", true)); + } + + @Test + void loggingRequestDetailsCustomizerShouldUseHttpCodecsProperties() { + this.contextRunner.withPropertyValues("spring.http.codecs.log-request-details=true") + .run((context) -> assertThat(defaultCodecs(context)) + .hasFieldOrPropertyWithValue("enableLoggingRequestDetails", true)); + } + + @Test + void logRequestDetailsShouldGivePriorityToHttpCodecProperty() { + this.contextRunner + .withPropertyValues("spring.http.codecs.log-request-details=true", "spring.codec.log-request-details=false") + .run((context) -> assertThat(defaultCodecs(context)) + .hasFieldOrPropertyWithValue("enableLoggingRequestDetails", true)); + } + + @Test + void maxInMemorySizeShouldUseCodecProperties() { + this.contextRunner.withPropertyValues("spring.codec.max-in-memory-size=64KB") + .run((context) -> assertThat(defaultCodecs(context)).hasFieldOrPropertyWithValue("maxInMemorySize", + 64 * 1024)); + } + + @Test + void maxInMemorySizeShouldUseHttpCodecProperties() { + this.contextRunner.withPropertyValues("spring.http.codecs.max-in-memory-size=64KB") + .run((context) -> assertThat(defaultCodecs(context)).hasFieldOrPropertyWithValue("maxInMemorySize", + 64 * 1024)); + } + + @Test + void maxInMemorySizeShouldGivePriorityToHttpCodecProperty() { + this.contextRunner + .withPropertyValues("spring.http.codecs.max-in-memory-size=64KB", "spring.codec.max-in-memory-size=32KB") + .run((context) -> assertThat(defaultCodecs(context)).hasFieldOrPropertyWithValue("maxInMemorySize", + 64 * 1024)); } @Test void defaultCodecCustomizerBeanShouldHaveOrderZero() { - this.contextRunner.run((context) -> { - Method customizerMethod = ReflectionUtils.findMethod( - CodecsAutoConfiguration.DefaultCodecsConfiguration.class, "defaultCodecCustomizer", - CodecProperties.class); - Integer order = new TestAnnotationAwareOrderComparator().findOrder(customizerMethod); - assertThat(order).isZero(); - }); + this.contextRunner + .run((context) -> assertThat(context.getBean("defaultCodecCustomizer", Ordered.class).getOrder()).isZero()); } @Test @@ -101,21 +124,16 @@ class CodecsAutoConfigurationTests { @Test void maxInMemorySizeEnforcedInDefaultCodecs() { - this.contextRunner.withPropertyValues("spring.codec.max-in-memory-size=1MB").run((context) -> { - CodecCustomizer customizer = context.getBean(CodecCustomizer.class); - CodecConfigurer configurer = new DefaultClientCodecConfigurer(); - customizer.customize(configurer); - assertThat(configurer.defaultCodecs()).hasFieldOrPropertyWithValue("maxInMemorySize", 1048576); - }); + this.contextRunner.withPropertyValues("spring.codec.max-in-memory-size=1MB") + .run((context) -> assertThat(defaultCodecs(context)).hasFieldOrPropertyWithValue("maxInMemorySize", + 1048576)); } - static class TestAnnotationAwareOrderComparator extends AnnotationAwareOrderComparator { - - @Override - public Integer findOrder(Object obj) { - return super.findOrder(obj); - } - + private DefaultCodecs defaultCodecs(AssertableWebApplicationContext context) { + CodecCustomizer customizer = context.getBean(CodecCustomizer.class); + CodecConfigurer configurer = new DefaultClientCodecConfigurer(); + customizer.customize(configurer); + return configurer.defaultCodecs(); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/devtools.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/devtools.adoc index 1aff1677794..d1499cc6a23 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/devtools.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/using/devtools.adoc @@ -78,7 +78,7 @@ NOTE: If you do not want property defaults to be applied you can set configprop: Because you need more information about web requests while developing Spring MVC and Spring WebFlux applications, developer tools suggests you to enable `DEBUG` logging for the `web` logging group. This will give you information about the incoming request, which handler is processing it, the response outcome, and other details. -If you wish to log all request details (including potentially sensitive information), you can turn on the configprop:spring.mvc.log-request-details[] or configprop:spring.codec.log-request-details[] configuration properties. +If you wish to log all request details (including potentially sensitive information), you can turn on the configprop:spring.mvc.log-request-details[] or configprop:spring.http.codecs.log-request-details[] configuration properties. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc index 85277085b8b..ef92b27b99c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc @@ -87,7 +87,7 @@ When not configured, the following defaults are used: Spring WebFlux uses the javadoc:org.springframework.http.codec.HttpMessageReader[] and javadoc:org.springframework.http.codec.HttpMessageWriter[] interfaces to convert HTTP requests and responses. They are configured with javadoc:org.springframework.http.codec.CodecConfigurer[] to have sensible defaults by looking at the libraries available in your classpath. -Spring Boot provides dedicated configuration properties for codecs, `+spring.codec.*+`. +Spring Boot provides dedicated configuration properties for codecs, `+spring.http.codecs.*+`. It also applies further customization by using javadoc:org.springframework.boot.web.codec.CodecCustomizer[] instances. For example, `+spring.jackson.*+` configuration keys are applied to the Jackson codec.