diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 207324ab86..fa68a11062 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -153,6 +153,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { @Nullable private ExtendedTypeConverter typeConverter; + private boolean declarativeBinding = false; + private boolean ignoreUnknownFields = true; private boolean ignoreInvalidFields = false; @@ -425,6 +427,28 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { return getInternalBindingResult(); } + /** + * Set whether to bind only fields explicitly intended for binding including: + * + *

Default is "false". Turn this on to limit binding to constructor + * parameters and allowed fields. + * @since 6.1 + */ + public void setDeclarativeBinding(boolean declarativeBinding) { + this.declarativeBinding = declarativeBinding; + } + + /** + * Return whether to bind only fields intended for binding. + * @since 6.1 + */ + public boolean isDeclarativeBinding() { + return this.declarativeBinding; + } /** * Set whether to ignore unknown fields, that is, whether to ignore bind @@ -1031,11 +1055,24 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { * @see #doBind(org.springframework.beans.MutablePropertyValues) */ public void bind(PropertyValues pvs) { + if (shouldNotBindPropertyValues()) { + return; + } MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues mutablePropertyValues ? mutablePropertyValues : new MutablePropertyValues(pvs)); doBind(mpvs); } + /** + * Whether to not bind parameters to properties. Returns "true" if + * {@link #isDeclarativeBinding()} is on, and + * {@link #setAllowedFields(String...) allowedFields} are not configured. + * @since 6.1 + */ + protected boolean shouldNotBindPropertyValues() { + return (isDeclarativeBinding() && ObjectUtils.isEmpty(this.allowedFields)); + } + /** * Actual implementation of the binding process, working with the * passed-in MutablePropertyValues instance. diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java index a5cc5ba0a9..d5c174b9db 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -681,6 +681,23 @@ class DataBinderTests { assertThat(dataBinder.convertIfNecessary(bean, String.class)).as("Type converter should have been used").isEqualTo("[Fred]"); } + @Test + void bindingInDeclarativeMode() throws BindException { + TestBean rod = new TestBean(); + DataBinder binder = new DataBinder(rod); + binder.setDeclarativeBinding(true); + + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("name", "Rod"); + pvs.add("age", "32x"); + + binder.bind(pvs); + binder.close(); + + assertThat(rod.getName()).isNull(); + assertThat(rod.getAge()).isEqualTo(0); + } + @Test void bindingWithAllowedFields() throws BindException { TestBean rod = new TestBean(); diff --git a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java index f004de89c7..f0b361b09e 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java @@ -140,6 +140,9 @@ public class ServletRequestDataBinder extends WebDataBinder { * @see #bind(org.springframework.beans.PropertyValues) */ public void bind(ServletRequest request) { + if (shouldNotBindPropertyValues()) { + return; + } MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class); if (multipartRequest != null) { diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java b/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java index 8a3220f8ff..293bb83a0c 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java @@ -44,6 +44,9 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer private boolean directFieldAccess = false; + @Nullable + private Boolean declarativeBinding; + @Nullable private MessageCodesResolver messageCodesResolver; @@ -99,6 +102,23 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer return this.directFieldAccess; } + /** + * Set whether to bind only fields intended for binding as described in + * {@link org.springframework.validation.DataBinder#setDeclarativeBinding}. + * @since 6.1 + */ + public void setDeclarativeBinding(boolean declarativeBinding) { + this.declarativeBinding = declarativeBinding; + } + + /** + * Return whether to bind only fields intended for binding. + * @since 6.1 + */ + public boolean isDeclarativeBinding() { + return (this.declarativeBinding != null ? this.declarativeBinding : false); + } + /** * Set the strategy to use for resolving errors into message codes. * Applies the given strategy to all data binders used by this controller. @@ -197,6 +217,9 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer if (this.directFieldAccess) { binder.initDirectFieldAccess(); } + if (this.declarativeBinding != null) { + binder.setDeclarativeBinding(this.declarativeBinding); + } if (this.messageCodesResolver != null) { binder.setMessageCodesResolver(this.messageCodesResolver); } diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java index 6367227dfa..38e0927519 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java @@ -93,6 +93,9 @@ public class WebExchangeDataBinder extends WebDataBinder { * @return a {@code Mono} that completes when binding is complete */ public Mono bind(ServerWebExchange exchange) { + if (shouldNotBindPropertyValues()) { + return Mono.empty(); + } return getValuesToBind(exchange) .doOnNext(map -> doBind(new MutablePropertyValues(map))) .then(); diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java index 778fdc7ea8..ebf8db439e 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java @@ -138,6 +138,9 @@ public class WebRequestDataBinder extends WebDataBinder { * @see #bind(org.springframework.beans.PropertyValues) */ public void bind(WebRequest request) { + if (shouldNotBindPropertyValues()) { + return; + } MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap()); if (request instanceof NativeWebRequest nativeRequest) { MultipartRequest multipartRequest = nativeRequest.getNativeRequest(MultipartRequest.class); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index f17e50b3a8..f90910d5d8 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -2056,6 +2056,26 @@ class ServletAnnotationControllerHandlerMethodTests extends AbstractServletHandl assertThat(response.getContentAsString()).isEqualTo("value1-true-3"); } + @PathPatternsParameterizedTest + void dataClassBindingWithAdditionalSetterInDeclarativeBindingMode(boolean usePathPatterns) throws Exception { + initDispatcherServlet(DataClassController.class, usePathPatterns, wac -> { + ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); + initializer.setDeclarativeBinding(true); + + RootBeanDefinition mappingDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); + mappingDef.getPropertyValues().add("webBindingInitializer", initializer); + wac.registerBeanDefinition("handlerAdapter", mappingDef); + }); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param1", "value1"); + request.addParameter("param2", "true"); + request.addParameter("param3", "3"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertThat(response.getContentAsString()).isEqualTo("value1-true-0"); + } + @PathPatternsParameterizedTest void dataClassBindingWithResult(boolean usePathPatterns) throws Exception { initDispatcherServlet(ValidatedDataClassController.class, usePathPatterns);