Skip to content

Commit 70c326e

Browse files
committed
Support headers in DataBinding via constructor args
Closes gh-34073
1 parent 7b4e19c commit 70c326e

File tree

4 files changed

+77
-3
lines changed

4 files changed

+77
-3
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExtendedWebExchangeDataBinder.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.http.HttpHeaders;
2525
import org.springframework.lang.Nullable;
2626
import org.springframework.util.CollectionUtils;
27+
import org.springframework.util.StringUtils;
2728
import org.springframework.web.bind.support.WebExchangeDataBinder;
2829
import org.springframework.web.reactive.HandlerMapping;
2930
import org.springframework.web.server.ServerWebExchange;
@@ -57,7 +58,11 @@ public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
5758
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
5859
List<String> values = entry.getValue();
5960
if (!CollectionUtils.isEmpty(values)) {
60-
String name = entry.getKey().replace("-", "");
61+
// For constructor args with @BindParam mapped to the actual header name
62+
String name = entry.getKey();
63+
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
64+
// Also adapt to Java conventions for setters
65+
name = StringUtils.uncapitalize(entry.getKey().replace("-", ""));
6166
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
6267
}
6368
}

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java

+32-1
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
import org.springframework.beans.testfixture.beans.TestBean;
2828
import org.springframework.core.DefaultParameterNameDiscoverer;
2929
import org.springframework.core.ReactiveAdapterRegistry;
30+
import org.springframework.core.ResolvableType;
3031
import org.springframework.core.convert.ConversionService;
3132
import org.springframework.format.support.DefaultFormattingConversionService;
3233
import org.springframework.http.MediaType;
3334
import org.springframework.web.bind.WebDataBinder;
35+
import org.springframework.web.bind.annotation.BindParam;
3436
import org.springframework.web.bind.annotation.InitBinder;
3537
import org.springframework.web.bind.annotation.RequestParam;
3638
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
@@ -129,7 +131,7 @@ void createBinderTypeConversion() throws Exception {
129131
}
130132

131133
@Test
132-
void bindUriVariablesAndHeaders() throws Exception {
134+
void bindUriVariablesAndHeadersViaSetters() throws Exception {
133135

134136
MockServerHttpRequest request = MockServerHttpRequest.get("/path")
135137
.header("Some-Int-Array", "1")
@@ -153,6 +155,31 @@ void bindUriVariablesAndHeaders() throws Exception {
153155
assertThat(target.getSomeIntArray()).containsExactly(1, 2);
154156
}
155157

158+
@Test
159+
void bindUriVariablesAndHeadersViaConstructor() throws Exception {
160+
161+
MockServerHttpRequest request = MockServerHttpRequest.get("/path")
162+
.header("Some-Int-Array", "1")
163+
.header("Some-Int-Array", "2")
164+
.build();
165+
166+
MockServerWebExchange exchange = MockServerWebExchange.from(request);
167+
exchange.getAttributes().put(
168+
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
169+
Map.of("name", "John", "age", "25"));
170+
171+
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
172+
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, "dataBean", null);
173+
binder.setTargetType(ResolvableType.forClass(DataBean.class));
174+
binder.construct(exchange).block();
175+
176+
DataBean bean = (DataBean) binder.getTarget();
177+
178+
assertThat(bean.name()).isEqualTo("John");
179+
assertThat(bean.age()).isEqualTo(25);
180+
assertThat(bean.someIntArray()).containsExactly(1, 2);
181+
}
182+
156183
@Test
157184
void bindUriVarsAndHeadersAddedConditionally() throws Exception {
158185

@@ -212,4 +239,8 @@ public void initBinderTypeConversion(WebDataBinder dataBinder, @RequestParam int
212239
}
213240
}
214241

242+
243+
private record DataBean(String name, int age, @BindParam("Some-Int-Array") Integer[] someIntArray) {
244+
}
245+
215246
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java

+10
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ protected Object getRequestParameter(String name, Class<?> type) {
156156
if (uriVars != null) {
157157
value = uriVars.get(name);
158158
}
159+
if (value == null && getRequest() instanceof HttpServletRequest httpServletRequest) {
160+
value = getHeaderValue(httpServletRequest, name);
161+
}
159162
}
160163
return value;
161164
}
@@ -167,6 +170,13 @@ protected Set<String> initParameterNames(ServletRequest request) {
167170
if (uriVars != null) {
168171
set.addAll(uriVars.keySet());
169172
}
173+
if (request instanceof HttpServletRequest httpServletRequest) {
174+
Enumeration<String> enumeration = httpServletRequest.getHeaderNames();
175+
while (enumeration.hasMoreElements()) {
176+
String headerName = enumeration.nextElement();
177+
set.add(headerName.replaceAll("-", ""));
178+
}
179+
}
170180
return set;
171181
}
172182
}

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java

+29-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import org.junit.jupiter.api.Test;
2323

2424
import org.springframework.beans.testfixture.beans.TestBean;
25+
import org.springframework.core.ResolvableType;
2526
import org.springframework.web.bind.ServletRequestDataBinder;
27+
import org.springframework.web.bind.annotation.BindParam;
28+
import org.springframework.web.bind.support.BindParamNameResolver;
2629
import org.springframework.web.servlet.HandlerMapping;
2730
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
2831

@@ -45,7 +48,7 @@ void setup() {
4548

4649

4750
@Test
48-
void createBinder() {
51+
void createBinderViaSetters() {
4952
request.setAttribute(
5053
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
5154
Map.of("name", "John", "age", "25"));
@@ -62,6 +65,27 @@ void createBinder() {
6265
assertThat(target.getSomeIntArray()).containsExactly(1, 2);
6366
}
6467

68+
@Test
69+
void createBinderViaConstructor() {
70+
request.setAttribute(
71+
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
72+
Map.of("name", "John", "age", "25"));
73+
74+
request.addHeader("Some-Int-Array", "1");
75+
request.addHeader("Some-Int-Array", "2");
76+
77+
ServletRequestDataBinder binder = new ExtendedServletRequestDataBinder(null);
78+
binder.setTargetType(ResolvableType.forClass(DataBean.class));
79+
binder.setNameResolver(new BindParamNameResolver());
80+
binder.construct(request);
81+
82+
DataBean bean = (DataBean) binder.getTarget();
83+
84+
assertThat(bean.name()).isEqualTo("John");
85+
assertThat(bean.age()).isEqualTo(25);
86+
assertThat(bean.someIntArray()).containsExactly(1, 2);
87+
}
88+
6589
@Test
6690
void uriVarsAndHeadersAddedConditionally() {
6791
request.addParameter("name", "John");
@@ -88,4 +112,8 @@ void noUriTemplateVars() {
88112
assertThat(target.getAge()).isEqualTo(0);
89113
}
90114

115+
116+
private record DataBean(String name, int age, @BindParam("Some-Int-Array") Integer[] someIntArray) {
117+
}
118+
91119
}

0 commit comments

Comments
 (0)