Skip to content

Commit f967f6f

Browse files
committed
Update contribution
Closes gh-33220
1 parent 5e9308e commit f967f6f

File tree

3 files changed

+66
-51
lines changed

3 files changed

+66
-51
lines changed

spring-web/src/main/java/org/springframework/web/service/invoker/AbstractNamedValueArgumentResolver.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ private NamedValueInfo getNamedValueInfo(MethodParameter parameter, HttpRequestV
129129
*/
130130
@Nullable
131131
protected NamedValueInfo createNamedValueInfo(
132-
MethodParameter parameter, HttpRequestValues.Metadata requestValues) {
132+
MethodParameter parameter, HttpRequestValues.Metadata metadata) {
133133

134134
return createNamedValueInfo(parameter);
135135
}
@@ -246,6 +246,8 @@ protected static class NamedValueInfo {
246246
* @param required whether it is marked as required
247247
* @param defaultValue fallback value, possibly {@link ValueConstants#DEFAULT_NONE}
248248
* @param label how it should appear in error messages, e.g. "path variable", "request header"
249+
* @param multiValued whether this argument resolver supports sending multiple values;
250+
* if not, then multiple values are formatted as a String value
249251
*/
250252
public NamedValueInfo(
251253
String name, boolean required, @Nullable String defaultValue, String label, boolean multiValued) {

spring-web/src/main/java/org/springframework/web/service/invoker/RequestParamArgumentResolver.java

+48-30
Original file line numberDiff line numberDiff line change
@@ -55,61 +55,79 @@
5555
*/
5656
public class RequestParamArgumentResolver extends AbstractNamedValueArgumentResolver {
5757

58-
private boolean formatAsSingleValue = true;
58+
private boolean favorSingleValue;
5959

6060

6161
public RequestParamArgumentResolver(ConversionService conversionService) {
6262
super(conversionService);
6363
}
6464

65-
public RequestParamArgumentResolver(ConversionService conversionService, boolean formatAsSingleValue) {
66-
super(conversionService);
67-
this.formatAsSingleValue = formatAsSingleValue;
68-
}
69-
7065

71-
@Override
72-
@Nullable
73-
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter, HttpRequestValues.Metadata requestValues) {
74-
MediaType contentType = requestValues.getContentType();
75-
if (contentType != null && isMultiValueFormContentType(contentType)) {
76-
this.formatAsSingleValue = true;
77-
}
66+
/**
67+
* Whether to format multiple values (e.g. collection, array) as a single
68+
* String value through the configured {@link ConversionService} unless the
69+
* content type is form data, or it is a multipart request.
70+
* <p>By default, this is {@code false} in which case formatting is not applied,
71+
* and a separate parameter with the same name is created for each value.
72+
* @since 6.2
73+
*/
74+
public void setFavorSingleValue(boolean favorSingleValue) {
75+
this.favorSingleValue = favorSingleValue;
76+
}
7877

79-
return createNamedValueInfo(parameter);
78+
/**
79+
* Return the setting for {@link #setFavorSingleValue favorSingleValue}.
80+
* @since 6.2
81+
*/
82+
public boolean isFavorSingleValue() {
83+
return this.favorSingleValue;
8084
}
8185

86+
8287
@Override
8388
@Nullable
84-
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
89+
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter, HttpRequestValues.Metadata metadata) {
8590
RequestParam annot = parameter.getParameterAnnotation(RequestParam.class);
8691
if (annot == null) {
8792
return null;
8893
}
89-
90-
return (annot == null ? null :
91-
new NamedValueInfo(annot.name(), annot.required(), annot.defaultValue(),
92-
"request parameter", this.formatAsSingleValue));
94+
return new NamedValueInfo(
95+
annot.name(), annot.required(), annot.defaultValue(), "request parameter",
96+
supportsMultipleValues(parameter, metadata));
9397
}
9498

9599
@Override
96-
protected void addRequestValue(
97-
String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
98-
99-
requestValues.addRequestParameter(name, (String) value);
100+
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
101+
// Shouldn't be called since we override createNamedValueInfo with HttpRequestValues.Metadata
102+
throw new UnsupportedOperationException();
100103
}
101104

102-
protected boolean isFormatAsSingleValue() {
103-
return this.formatAsSingleValue;
105+
/**
106+
* Determine whether the resolver should send multi-value request parameters
107+
* as individual values. If not, they are formatted to a single String value.
108+
* The default implementation uses {@link #isFavorSingleValue()} to decide
109+
* unless the content type is form data, or it is a multipart request.
110+
* @since 6.2
111+
*/
112+
protected boolean supportsMultipleValues(MethodParameter parameter, HttpRequestValues.Metadata metadata) {
113+
return (!isFavorSingleValue() || isFormOrMultipartContent(metadata));
104114
}
105115

106-
protected void setFormatAsSingleValue(boolean formatAsSingleValue) {
107-
this.formatAsSingleValue = formatAsSingleValue;
116+
/**
117+
* Whether the content type is form data, or it is a multipart request.
118+
* @since 6.2
119+
*/
120+
protected boolean isFormOrMultipartContent(HttpRequestValues.Metadata metadata) {
121+
MediaType mediaType = metadata.getContentType();
122+
return (mediaType != null && (mediaType.getType().equals("multipart") ||
123+
mediaType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)));
108124
}
109125

110-
protected boolean isMultiValueFormContentType(MediaType contentType) {
111-
return contentType.equals(MediaType.APPLICATION_FORM_URLENCODED)
112-
|| contentType.getType().equals("multipart");
126+
@Override
127+
protected void addRequestValue(
128+
String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
129+
130+
requestValues.addRequestParameter(name, (String) value);
113131
}
114132

115133
}

spring-web/src/test/java/org/springframework/web/service/invoker/RequestParamArgumentResolverTests.java

+15-20
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@
1616

1717
package org.springframework.web.service.invoker;
1818

19-
import java.util.HashMap;
2019
import java.util.List;
20+
import java.util.Map;
2121

2222
import org.junit.jupiter.api.Test;
2323

24-
import org.springframework.core.convert.ConversionService;
2524
import org.springframework.core.convert.support.DefaultConversionService;
2625
import org.springframework.util.MultiValueMap;
2726
import org.springframework.web.bind.annotation.RequestParam;
2827
import org.springframework.web.service.annotation.GetExchange;
2928
import org.springframework.web.service.annotation.PostExchange;
29+
import org.springframework.web.util.UriComponents;
30+
import org.springframework.web.util.UriComponentsBuilder;
3031

3132
import static org.assertj.core.api.Assertions.assertThat;
3233

@@ -62,24 +63,18 @@ void requestParam() {
6263
}
6364

6465
@Test
65-
@SuppressWarnings("unchecked")
6666
void requestParamWithDisabledFormattingCollectionValue() {
67-
ConversionService conversionService = new DefaultConversionService();
68-
boolean formatAsSingleValue = false;
69-
Service service = builder.customArgumentResolver(
70-
new RequestParamArgumentResolver(conversionService, formatAsSingleValue))
71-
.build()
72-
.createClient(Service.class);
73-
List<String> collectionParams = List.of("1", "2", "3");
74-
service.getForm("value 1", collectionParams);
75-
76-
Object uriVariables = this.client.getRequestValues().getUriVariables();
77-
assertThat(uriVariables).isNotInstanceOf(MultiValueMap.class).isInstanceOf(HashMap.class);
78-
assertThat((HashMap<String, String>) uriVariables).hasSize(4)
79-
.containsEntry("queryParam0", "param1")
80-
.containsEntry("queryParam0[0]", "value 1")
81-
.containsEntry("queryParam1", "param2")
82-
.containsEntry("queryParam1[0]", String.join(",", collectionParams));
67+
RequestParamArgumentResolver resolver = new RequestParamArgumentResolver(new DefaultConversionService());
68+
resolver.setFavorSingleValue(true);
69+
70+
Service service = builder.customArgumentResolver(resolver).build().createClient(Service.class);
71+
service.getWithParams("value 1", List.of("1", "2", "3"));
72+
73+
HttpRequestValues values = this.client.getRequestValues();
74+
String uriTemplate = values.getUriTemplate();
75+
Map<String, String> uriVariables = values.getUriVariables();
76+
UriComponents uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode();
77+
assertThat(uri.getQuery()).isEqualTo("param1=value%201&param2=1,2,3");
8378
}
8479

8580
private interface Service {
@@ -88,7 +83,7 @@ private interface Service {
8883
void postForm(@RequestParam String param1, @RequestParam String param2);
8984

9085
@GetExchange
91-
void getForm(@RequestParam String param1, @RequestParam List<String> param2);
86+
void getWithParams(@RequestParam String param1, @RequestParam List<String> param2);
9287
}
9388

9489
}

0 commit comments

Comments
 (0)