Merge branch '6.0.x'
This commit is contained in:
commit
b3f5d20ad8
|
@ -3,11 +3,18 @@
|
||||||
|
|
||||||
[.small]#xref:web/webflux/config.adoc#webflux-config-message-codecs[See equivalent in the Reactive stack]#
|
[.small]#xref:web/webflux/config.adoc#webflux-config-message-codecs[See equivalent in the Reactive stack]#
|
||||||
|
|
||||||
You can customize `HttpMessageConverter` in Java configuration by overriding
|
You can set the `HttpMessageConverter` instances to use in Java configuration,
|
||||||
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#configureMessageConverters-java.util.List-[`configureMessageConverters()`]
|
replacing the ones used by default, by overriding
|
||||||
(to replace the default converters created by Spring MVC) or by overriding
|
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#configureMessageConverters-java.util.List-[`configureMessageConverters()`].
|
||||||
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#extendMessageConverters-java.util.List-[`extendMessageConverters()`]
|
You can also customize the list of configured message converters at the end by overriding
|
||||||
(to customize the default converters or add additional converters to the default ones).
|
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#extendMessageConverters-java.util.List-[`extendMessageConverters()`].
|
||||||
|
|
||||||
|
TIP: In a Spring Boot application, the `WebMvcAutoConfiguration` adds any
|
||||||
|
`HttpMessageConverter` beans it detects, in addition to default converters. Hence, in a
|
||||||
|
Boot application, prefer to use the
|
||||||
|
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/message-converters.html[HttpMessageConverters]
|
||||||
|
mechanism. Or alternatively, use `extendMessageConverters` to modify message converters
|
||||||
|
at the end.
|
||||||
|
|
||||||
The following example adds XML and Jackson JSON converters with a customized
|
The following example adds XML and Jackson JSON converters with a customized
|
||||||
`ObjectMapper` instead of the default ones:
|
`ObjectMapper` instead of the default ones:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -389,31 +389,30 @@ public final class HttpRequestValues {
|
||||||
Map<String, String> uriVars = (this.uriVars != null ? new HashMap<>(this.uriVars) : Collections.emptyMap());
|
Map<String, String> uriVars = (this.uriVars != null ? new HashMap<>(this.uriVars) : Collections.emptyMap());
|
||||||
|
|
||||||
Object bodyValue = this.bodyValue;
|
Object bodyValue = this.bodyValue;
|
||||||
|
if (this.multipartBuilder != null) {
|
||||||
|
Assert.isTrue(bodyValue == null && this.body == null, "Expected body or request parts, not both");
|
||||||
|
bodyValue = this.multipartBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(this.requestParams)) {
|
if (!CollectionUtils.isEmpty(this.requestParams)) {
|
||||||
|
if (hasContentType(MediaType.APPLICATION_FORM_URLENCODED)) {
|
||||||
boolean isFormData = (this.headers != null &&
|
Assert.isTrue(this.multipartBuilder == null, "Cannot add parts to form data request");
|
||||||
MediaType.APPLICATION_FORM_URLENCODED.equals(this.headers.getContentType()));
|
Assert.isTrue(bodyValue == null && this.body == null, "Cannot set body of form data request");
|
||||||
|
|
||||||
if (isFormData) {
|
|
||||||
Assert.isTrue(bodyValue == null && this.body == null, "Expected body or request params, not both");
|
|
||||||
bodyValue = new LinkedMultiValueMap<>(this.requestParams);
|
bodyValue = new LinkedMultiValueMap<>(this.requestParams);
|
||||||
}
|
}
|
||||||
else if (uri != null) {
|
else if (uri != null) {
|
||||||
|
// insert into prepared URI
|
||||||
uri = UriComponentsBuilder.fromUri(uri)
|
uri = UriComponentsBuilder.fromUri(uri)
|
||||||
.queryParams(UriUtils.encodeQueryParams(this.requestParams))
|
.queryParams(UriUtils.encodeQueryParams(this.requestParams))
|
||||||
.build(true)
|
.build(true)
|
||||||
.toUri();
|
.toUri();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// append to URI template
|
||||||
uriVars = (uriVars.isEmpty() ? new HashMap<>() : uriVars);
|
uriVars = (uriVars.isEmpty() ? new HashMap<>() : uriVars);
|
||||||
uriTemplate = appendQueryParams(uriTemplate, uriVars, this.requestParams);
|
uriTemplate = appendQueryParams(uriTemplate, uriVars, this.requestParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (this.multipartBuilder != null) {
|
|
||||||
Assert.isTrue(bodyValue == null && this.body == null, "Expected body or request parts, not both");
|
|
||||||
bodyValue = this.multipartBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpHeaders headers = HttpHeaders.EMPTY;
|
HttpHeaders headers = HttpHeaders.EMPTY;
|
||||||
if (this.headers != null) {
|
if (this.headers != null) {
|
||||||
|
@ -432,6 +431,10 @@ public final class HttpRequestValues {
|
||||||
bodyValue, this.body, this.bodyElementType);
|
bodyValue, this.body, this.bodyElementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasContentType(MediaType mediaType) {
|
||||||
|
return (this.headers != null && mediaType.equals(this.headers.getContentType()));
|
||||||
|
}
|
||||||
|
|
||||||
private String appendQueryParams(
|
private String appendQueryParams(
|
||||||
String uriTemplate, Map<String, String> uriVars, MultiValueMap<String, String> requestParams) {
|
String uriTemplate, Map<String, String> uriVars, MultiValueMap<String, String> requestParams) {
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class HttpRequestValuesTests {
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(strings = {"POST", "PUT", "PATCH"})
|
@ValueSource(strings = {"POST", "PUT", "PATCH"})
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void requestParamAsFormData(String httpMethod) {
|
void formData(String httpMethod) {
|
||||||
|
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.valueOf(httpMethod))
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.valueOf(httpMethod))
|
||||||
.setContentType(MediaType.APPLICATION_FORM_URLENCODED)
|
.setContentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
|
@ -65,7 +65,7 @@ class HttpRequestValuesTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void requestParamAsQueryParamsInUriTemplate() {
|
void queryParamsWithUriTemplate() {
|
||||||
|
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.POST)
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.POST)
|
||||||
.setUriTemplate("/path")
|
.setUriTemplate("/path")
|
||||||
|
@ -99,23 +99,25 @@ class HttpRequestValuesTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void requestParamAsQueryParamsInUri() {
|
void queryParamsWithPreparedUri() {
|
||||||
|
|
||||||
|
URI uri = URI.create("/my%20path");
|
||||||
|
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.POST)
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.POST)
|
||||||
.setUri(URI.create("/path"))
|
.setUri(uri)
|
||||||
.addRequestParameter("param1", "1st value")
|
.addRequestParameter("param1", "1st value")
|
||||||
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
.addRequestParameter("param2", "2nd value A", "2nd value B")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(requestValues.getUri().toString())
|
assertThat(requestValues.getUri().toString())
|
||||||
.isEqualTo("/path?param1=1st%20value¶m2=2nd%20value%20A¶m2=2nd%20value%20B");
|
.isEqualTo("/my%20path?param1=1st%20value¶m2=2nd%20value%20A¶m2=2nd%20value%20B");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void requestPart() {
|
void requestPart() {
|
||||||
HttpHeaders entityHeaders = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
entityHeaders.add("foo", "bar");
|
headers.add("foo", "bar");
|
||||||
HttpEntity<String> entity = new HttpEntity<>("body", entityHeaders);
|
HttpEntity<String> entity = new HttpEntity<>("body", headers);
|
||||||
|
|
||||||
HttpRequestValues requestValues = HttpRequestValues.builder()
|
HttpRequestValues requestValues = HttpRequestValues.builder()
|
||||||
.addRequestPart("form field", "form value")
|
.addRequestPart("form field", "form value")
|
||||||
|
@ -129,4 +131,24 @@ class HttpRequestValuesTests {
|
||||||
assertThat(map.getFirst("entity")).isEqualTo(entity);
|
assertThat(map.getFirst("entity")).isEqualTo(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requestPartAndRequestParam() {
|
||||||
|
|
||||||
|
HttpRequestValues requestValues = HttpRequestValues.builder()
|
||||||
|
.setUriTemplate("/path")
|
||||||
|
.addRequestPart("form field", "form value")
|
||||||
|
.addRequestParameter("query param", "query value")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String uriTemplate = requestValues.getUriTemplate();
|
||||||
|
assertThat(uriTemplate).isNotNull();
|
||||||
|
|
||||||
|
assertThat(uriTemplate).isEqualTo("/path?{queryParam0}={queryParam0[0]}");
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
MultiValueMap<String, HttpEntity<?>> map = (MultiValueMap<String, HttpEntity<?>>) requestValues.getBodyValue();
|
||||||
|
assertThat(map).hasSize(1);
|
||||||
|
assertThat(map.getFirst("form field").getBody()).isEqualTo("form value");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,9 +65,9 @@ class RequestPartArgumentResolverTests {
|
||||||
|
|
||||||
Object body = this.client.getRequestValues().getBodyValue();
|
Object body = this.client.getRequestValues().getBodyValue();
|
||||||
assertThat(body).isInstanceOf(MultiValueMap.class);
|
assertThat(body).isInstanceOf(MultiValueMap.class);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
MultiValueMap<String, HttpEntity<?>> map = (MultiValueMap<String, HttpEntity<?>>) body;
|
MultiValueMap<String, HttpEntity<?>> map = (MultiValueMap<String, HttpEntity<?>>) body;
|
||||||
|
|
||||||
assertThat(map.getFirst("part1").getBody()).isEqualTo("part 1");
|
assertThat(map.getFirst("part1").getBody()).isEqualTo("part 1");
|
||||||
assertThat(map.getFirst("part2")).isEqualTo(part2);
|
assertThat(map.getFirst("part2")).isEqualTo(part2);
|
||||||
assertThat(((Mono<?>) map.getFirst("part3").getBody()).block()).isEqualTo("part 3");
|
assertThat(((Mono<?>) map.getFirst("part3").getBody()).block()).isEqualTo("part 3");
|
||||||
|
|
|
@ -175,7 +175,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa
|
||||||
}
|
}
|
||||||
|
|
||||||
// For ProblemDetail, fall back on RFC 7807 format
|
// For ProblemDetail, fall back on RFC 7807 format
|
||||||
if (bestMediaType == null && elementType.toClass().equals(ProblemDetail.class)) {
|
if (bestMediaType == null && ProblemDetail.class.isAssignableFrom(elementType.toClass())) {
|
||||||
bestMediaType = selectMediaType(exchange, () -> getMediaTypesFor(elementType), this.problemMediaTypes);
|
bestMediaType = selectMediaType(exchange, () -> getMediaTypesFor(elementType), this.problemMediaTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,9 +139,9 @@ public class ResponseBodyResultHandlerTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testProblemDetailMediaType(MockServerWebExchange exchange, MediaType expectedMediaType) {
|
private void testProblemDetailMediaType(MockServerWebExchange exchange, MediaType expectedMediaType) {
|
||||||
ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
|
MyProblemDetail problemDetail = new MyProblemDetail(HttpStatus.BAD_REQUEST);
|
||||||
|
|
||||||
Method method = on(TestRestController.class).returning(ProblemDetail.class).resolveMethod();
|
Method method = on(TestRestController.class).returning(MyProblemDetail.class).resolveMethod();
|
||||||
HandlerResult result = getHandlerResult(new TestRestController(), problemDetail, method);
|
HandlerResult result = getHandlerResult(new TestRestController(), problemDetail, method);
|
||||||
|
|
||||||
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
|
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
|
||||||
|
@ -196,7 +196,7 @@ public class ResponseBodyResultHandlerTests {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProblemDetail handleToProblemDetail() {
|
public MyProblemDetail handleToProblemDetail() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,4 +217,13 @@ public class ResponseBodyResultHandlerTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class MyProblemDetail extends ProblemDetail {
|
||||||
|
|
||||||
|
public MyProblemDetail(HttpStatus status) {
|
||||||
|
super(status.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -166,10 +166,14 @@ public interface WebMvcConfigurer {
|
||||||
* <p>By default, all built-in converters are configured as long as the
|
* <p>By default, all built-in converters are configured as long as the
|
||||||
* corresponding 3rd party libraries such Jackson JSON, JAXB2, and others
|
* corresponding 3rd party libraries such Jackson JSON, JAXB2, and others
|
||||||
* are present on the classpath.
|
* are present on the classpath.
|
||||||
* <p><strong>Note</strong> use of this method turns off default converter
|
* <p>Note that use of this method turns off default converter
|
||||||
* registration. Alternatively, use
|
* registration. However, in a Spring Boot application the
|
||||||
* {@link #extendMessageConverters(java.util.List)} to modify that default
|
* {@code WebMvcAutoConfiguration} adds any {@code HttpMessageConverter}
|
||||||
* list of converters.
|
* beans as well as default converters. Hence, in a Boot application use
|
||||||
|
* <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.servlet.spring-mvc.message-converters">HttpMessageConverters</a>.
|
||||||
|
* Alternatively, for any scenario, use
|
||||||
|
* {@link #extendMessageConverters(java.util.List)} to modify the configured
|
||||||
|
* list of message converters.
|
||||||
* @param converters initially an empty list of converters
|
* @param converters initially an empty list of converters
|
||||||
*/
|
*/
|
||||||
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
`/*
|
||||||
* Copyright 2002-2023 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -374,7 +374,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testProblemDetailMediaType(String expectedContentType) throws Exception {
|
private void testProblemDetailMediaType(String expectedContentType) throws Exception {
|
||||||
ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
|
MyProblemDetail problemDetail = new MyProblemDetail(HttpStatus.BAD_REQUEST);
|
||||||
|
|
||||||
this.servletRequest.setRequestURI("/path");
|
this.servletRequest.setRequestURI("/path");
|
||||||
|
|
||||||
|
@ -805,7 +805,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
ProblemDetail handleAndReturnProblemDetail() {
|
MyProblemDetail handleAndReturnProblemDetail() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,6 +821,15 @@ class RequestResponseBodyMethodProcessorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class MyProblemDetail extends ProblemDetail {
|
||||||
|
|
||||||
|
public MyProblemDetail(HttpStatus status) {
|
||||||
|
super(status.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static abstract class MyParameterizedController<DTO extends Identifiable> {
|
private static abstract class MyParameterizedController<DTO extends Identifiable> {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
|
Loading…
Reference in New Issue