parent
23160a43dd
commit
f4f89aa2a4
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
The `@ModelAttribute` method parameter annotation binds request parameters onto a model
|
||||
object. For example:
|
||||
The `@ModelAttribute` method parameter annotation binds form data, query parameters,
|
||||
URI path variables, and request headers onto a model object. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
|
@ -27,6 +27,10 @@ Kotlin::
|
|||
<1> Bind to an instance of `Pet`.
|
||||
======
|
||||
|
||||
Form data and query parameters take precedence over URI variables and headers, which are
|
||||
included only if they don't override request parameters with the same name. Dashes are
|
||||
stripped from header names.
|
||||
|
||||
The `Pet` instance may be:
|
||||
|
||||
* Accessed from the model where it could have been added by a
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
[.small]#xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
The `@ModelAttribute` method parameter annotation binds request parameters onto a model
|
||||
object. For example:
|
||||
The `@ModelAttribute` method parameter annotation binds request parameters, URI path variables,
|
||||
and request headers onto a model object. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
|
@ -31,7 +31,11 @@ fun processSubmit(@ModelAttribute pet: Pet): String { // <1>
|
|||
<1> Bind to an instance of `Pet`.
|
||||
======
|
||||
|
||||
The `Pet` instance may be:
|
||||
Request parameters are a Servlet API concept that includes form data from the request body,
|
||||
and query parameters. URI variables and headers are also included, but only if they don't
|
||||
override request parameters with the same name. Dashes are stripped from header names.
|
||||
|
||||
The `Pet` instance above may be:
|
||||
|
||||
* Accessed from the model where it could have been added by a
|
||||
xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[@ModelAttribute method].
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.springframework.web.reactive;
|
|||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
@ -26,6 +27,7 @@ import org.springframework.beans.BeanUtils;
|
|||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
|
@ -214,6 +216,14 @@ public class BindingContext {
|
|||
if (!CollectionUtils.isEmpty(vars)) {
|
||||
vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value));
|
||||
}
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
List<String> values = entry.getValue();
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
String name = entry.getKey().replace("-", "");
|
||||
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,24 +69,50 @@ class BindingContextTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void uriVariablesAddedConditionally() {
|
||||
void bindUriVariablesAndHeaders() {
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/path")
|
||||
.header("Some-Int-Array", "1")
|
||||
.header("Some-Int-Array", "2")
|
||||
.build();
|
||||
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
exchange.getAttributes().put(
|
||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
|
||||
Map.of("name", "John", "age", "25"));
|
||||
|
||||
TestBean target = new TestBean();
|
||||
|
||||
BindingContext bindingContext = new BindingContext(null);
|
||||
WebExchangeDataBinder binder = bindingContext.createDataBinder(exchange, target, "testBean", null);
|
||||
|
||||
binder.bind(exchange).block();
|
||||
|
||||
assertThat(target.getName()).isEqualTo("John");
|
||||
assertThat(target.getAge()).isEqualTo(25);
|
||||
assertThat(target.getSomeIntArray()).containsExactly(1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindUriVarsAndHeadersAddedConditionally() {
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/path")
|
||||
.header("name", "Johnny")
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body("name=John&age=25");
|
||||
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map.of("age", "26"));
|
||||
|
||||
TestBean testBean = new TestBean();
|
||||
TestBean target = new TestBean();
|
||||
|
||||
BindingContext bindingContext = new BindingContext(null);
|
||||
WebExchangeDataBinder binder = bindingContext.createDataBinder(exchange, testBean, "testBean", null);
|
||||
WebExchangeDataBinder binder = bindingContext.createDataBinder(exchange, target, "testBean", null);
|
||||
|
||||
binder.bind(exchange).block();
|
||||
|
||||
assertThat(testBean.getName()).isEqualTo("John");
|
||||
assertThat(testBean.getAge()).isEqualTo(25);
|
||||
assertThat(target.getName()).isEqualTo("John");
|
||||
assertThat(target.getAge()).isEqualTo(25);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,14 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
|
@ -83,6 +87,17 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
|||
if (uriVars != null) {
|
||||
uriVars.forEach((name, value) -> addValueIfNotPresent(mpvs, "URI variable", name, value));
|
||||
}
|
||||
if (request instanceof HttpServletRequest httpRequest) {
|
||||
Enumeration<String> names = httpRequest.getHeaderNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
Object value = getHeaderValue(httpRequest, name);
|
||||
if (value != null) {
|
||||
name = name.replace("-", "");
|
||||
addValueIfNotPresent(mpvs, "Header", name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -91,19 +106,35 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
|||
return (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private static void addValueIfNotPresent(
|
||||
MutablePropertyValues mpvs, String label, String name, @Nullable Object value) {
|
||||
|
||||
if (value != null) {
|
||||
if (mpvs.contains(name)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(label + " '" + name + "' overridden by request bind value.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
mpvs.addPropertyValue(name, value);
|
||||
private static void addValueIfNotPresent(MutablePropertyValues mpvs, String label, String name, Object value) {
|
||||
if (mpvs.contains(name)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(label + " '" + name + "' overridden by request bind value.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
mpvs.addPropertyValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Object getHeaderValue(HttpServletRequest request, String name) {
|
||||
Enumeration<String> valuesEnum = request.getHeaders(name);
|
||||
if (!valuesEnum.hasMoreElements()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = valuesEnum.nextElement();
|
||||
if (!valuesEnum.hasMoreElements()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
while (valuesEnum.hasMoreElements()) {
|
||||
values.add(valuesEnum.nextElement());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
|
@ -38,41 +37,45 @@ class ExtendedServletRequestDataBinderTests {
|
|||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void createBinder() {
|
||||
|
||||
this.request.setAttribute(
|
||||
request.setAttribute(
|
||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
|
||||
Map.of("name", "nameValue", "age", "25"));
|
||||
Map.of("name", "John", "age", "25"));
|
||||
|
||||
request.addHeader("Some-Int-Array", "1");
|
||||
request.addHeader("Some-Int-Array", "2");
|
||||
|
||||
TestBean target = new TestBean();
|
||||
ServletRequestDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
|
||||
binder.bind(request);
|
||||
|
||||
assertThat(target.getName()).isEqualTo("nameValue");
|
||||
assertThat(target.getName()).isEqualTo("John");
|
||||
assertThat(target.getAge()).isEqualTo(25);
|
||||
assertThat(target.getSomeIntArray()).containsExactly(1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void uriTemplateVarAndRequestParam() {
|
||||
request.addParameter("age", "35");
|
||||
void uriVarsAndHeadersAddedConditionally() {
|
||||
request.addParameter("name", "John");
|
||||
request.addParameter("age", "25");
|
||||
|
||||
Map<String, String> uriTemplateVars = new HashMap<>();
|
||||
uriTemplateVars.put("name", "nameValue");
|
||||
uriTemplateVars.put("age", "25");
|
||||
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
||||
request.addHeader("name", "Johnny");
|
||||
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map.of("age", "26"));
|
||||
|
||||
TestBean target = new TestBean();
|
||||
ServletRequestDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
|
||||
binder.bind(request);
|
||||
|
||||
assertThat(target.getName()).isEqualTo("nameValue");
|
||||
assertThat(target.getAge()).isEqualTo(35);
|
||||
assertThat(target.getName()).isEqualTo("John");
|
||||
assertThat(target.getAge()).isEqualTo(25);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in New Issue