Skip to content

Commit 5e9308e

Browse files
doljaerstoyanchev
authored andcommitted
Flexible collection handling in RequestParamArgumentResolver
See gh-33220
1 parent dbc6928 commit 5e9308e

File tree

2 files changed

+68
-5
lines changed

2 files changed

+68
-5
lines changed

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

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import org.springframework.core.MethodParameter;
2020
import org.springframework.core.convert.ConversionService;
21+
import org.springframework.http.MediaType;
2122
import org.springframework.lang.Nullable;
2223
import org.springframework.web.bind.annotation.RequestParam;
2324

@@ -54,18 +55,41 @@
5455
*/
5556
public class RequestParamArgumentResolver extends AbstractNamedValueArgumentResolver {
5657

58+
private boolean formatAsSingleValue = true;
59+
5760

5861
public RequestParamArgumentResolver(ConversionService conversionService) {
5962
super(conversionService);
6063
}
6164

65+
public RequestParamArgumentResolver(ConversionService conversionService, boolean formatAsSingleValue) {
66+
super(conversionService);
67+
this.formatAsSingleValue = formatAsSingleValue;
68+
}
69+
70+
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+
}
78+
79+
return createNamedValueInfo(parameter);
80+
}
6281

6382
@Override
6483
@Nullable
6584
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
6685
RequestParam annot = parameter.getParameterAnnotation(RequestParam.class);
86+
if (annot == null) {
87+
return null;
88+
}
89+
6790
return (annot == null ? null :
68-
new NamedValueInfo(annot.name(), annot.required(), annot.defaultValue(), "request parameter", true));
91+
new NamedValueInfo(annot.name(), annot.required(), annot.defaultValue(),
92+
"request parameter", this.formatAsSingleValue));
6993
}
7094

7195
@Override
@@ -75,4 +99,17 @@ protected void addRequestValue(
7599
requestValues.addRequestParameter(name, (String) value);
76100
}
77101

102+
protected boolean isFormatAsSingleValue() {
103+
return this.formatAsSingleValue;
104+
}
105+
106+
protected void setFormatAsSingleValue(boolean formatAsSingleValue) {
107+
this.formatAsSingleValue = formatAsSingleValue;
108+
}
109+
110+
protected boolean isMultiValueFormContentType(MediaType contentType) {
111+
return contentType.equals(MediaType.APPLICATION_FORM_URLENCODED)
112+
|| contentType.getType().equals("multipart");
113+
}
114+
78115
}

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

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

19+
import java.util.HashMap;
1920
import java.util.List;
2021

2122
import org.junit.jupiter.api.Test;
2223

24+
import org.springframework.core.convert.ConversionService;
25+
import org.springframework.core.convert.support.DefaultConversionService;
2326
import org.springframework.util.MultiValueMap;
2427
import org.springframework.web.bind.annotation.RequestParam;
28+
import org.springframework.web.service.annotation.GetExchange;
2529
import org.springframework.web.service.annotation.PostExchange;
2630

2731
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,14 +45,14 @@ class RequestParamArgumentResolverTests {
4145

4246
private final TestExchangeAdapter client = new TestExchangeAdapter();
4347

44-
private final Service service =
45-
HttpServiceProxyFactory.builderFor(this.client).build().createClient(Service.class);
48+
private final HttpServiceProxyFactory.Builder builder = HttpServiceProxyFactory.builderFor(this.client);
4649

4750

4851
@Test
4952
@SuppressWarnings("unchecked")
5053
void requestParam() {
51-
this.service.postForm("value 1", "value 2");
54+
Service service = builder.build().createClient(Service.class);
55+
service.postForm("value 1", "value 2");
5256

5357
Object body = this.client.getRequestValues().getBodyValue();
5458
assertThat(body).isInstanceOf(MultiValueMap.class);
@@ -57,12 +61,34 @@ void requestParam() {
5761
.containsEntry("param2", List.of("value 2"));
5862
}
5963

64+
@Test
65+
@SuppressWarnings("unchecked")
66+
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));
83+
}
6084

6185
private interface Service {
6286

6387
@PostExchange(contentType = "application/x-www-form-urlencoded")
6488
void postForm(@RequestParam String param1, @RequestParam String param2);
6589

90+
@GetExchange
91+
void getForm(@RequestParam String param1, @RequestParam List<String> param2);
6692
}
6793

6894
}

0 commit comments

Comments
 (0)