diff --git a/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc b/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc index db58aaa11e3..f7bf2586ea6 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/beans-beans.adoc @@ -1,5 +1,50 @@ +[[beans-binding]] += Data Binding + +Data binding is useful for binding user input to a target object where user input is a map +with property paths as keys, following xref:beans-beans-conventions[JavaBeans conventions]. +`DataBinder` is the main class that supports this, and it provides two ways to bind user +input: + +- xref:beans-constructor-binding[Constructor binding] - bind user input to a public data +constructor, looking up constructor argument values in the user input. +- xref:beans-beans[Property binding] - bind user input to setters, matching keys from the +the user input to properties of the target object structure. + +You can apply both constructor and property binding or only one. + + +[[beans-constructor-binding]] +== Constructor Binding + +To use constructor binding: + +1. Create a `DataBinder` with `null` as the target object. +2. Set `targetType` to the target class. +3. Call `construct`. + +The target class should have a single public constructor or a single non-public constructor +with arguments. If there are multiple constructors, then a default constructor if present +is used. + +By default, constructor parameter names are used to look up argument values, but you can +configure a `NameResolver`. Spring MVC and WebFlux both rely to allow customizing the name +of the value to bind through an `@BindParam` annotation on constructor parameters. + +xref:beans-beans-conventions[Type conversion] is applied as needed to convert user input. +If the constructor parameter is an object, it is constructed recursively in the same +manner, but through a nested property path. That means constructor binding creates both +the target object and any objects it contains. + +Binding and conversion errors are reflected in the `BindingResult` of the `DataBinder`. +If the target is created successfully, then `target` is set to the created instance +after the call to `construct`. + + + + [[beans-beans]] -= Bean Manipulation and the `BeanWrapper` +== Property Binding with `BeanWrapper` The `org.springframework.beans` package adheres to the JavaBeans standard. A JavaBean is a class with a default no-argument constructor and that follows @@ -26,7 +71,7 @@ perform actions on that bean, such as setting and retrieving properties. [[beans-beans-conventions]] -== Setting and Getting Basic and Nested Properties +=== Setting and Getting Basic and Nested Properties Setting and getting properties is done through the `setPropertyValue` and `getPropertyValue` overloaded method variants of `BeanWrapper`. See their Javadoc for @@ -192,7 +237,7 @@ Kotlin:: [[beans-beans-conversion]] -== Built-in `PropertyEditor` Implementations +== ``PropertyEditor``'s Spring uses the concept of a `PropertyEditor` to effect the conversion between an `Object` and a `String`. It can be handy @@ -378,7 +423,7 @@ Kotlin:: [[beans-beans-conversion-customeditor-registration]] -=== Registering Additional Custom `PropertyEditor` Implementations +=== Custom ``PropertyEditor``'s When setting bean properties as string values, a Spring IoC container ultimately uses standard JavaBeans `PropertyEditor` implementations to convert these strings to the complex type of the @@ -521,7 +566,7 @@ Finally, the following example shows how to use `CustomEditorConfigurer` to regi ---- [[beans-beans-conversion-customeditor-registration-per]] -==== Using `PropertyEditorRegistrar` +=== `PropertyEditorRegistrar` Another mechanism for registering property editors with the Spring container is to create and use a `PropertyEditorRegistrar`. This interface is particularly useful when diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc index 48353d675d1..8cccf3b7d0d 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc @@ -38,12 +38,44 @@ The `Pet` instance may be: request parameters. Argument names are determined through runtime-retained parameter names in the bytecode. -Once a model attribute instance is available, `WebDataBinder` binds request parameters to -properties of the target `Object` with type conversion where necessary. -For more on data binding and validation, see -xref:web/webmvc/mvc-config/validation.adoc[Validation]. -For more on customizing data binding, see -xref:web/webmvc/mvc-controller/ann-initbinder.adoc[DataBinder]. +By default, both constructor and property +xref:core/validation/beans-beans.adoc#beans-binding[data binding] are applied. However, +model object design requires careful consideration, and for security reasons it is +recommended either to use an object tailored specifically for web binding, or to apply +constructor binding only. If property binding must still be used, then _allowedFields_ +patterns should be set to limit which properties can be set. For further details on this +and example configuration, see +xref:web/webflux/controller/ann-initbinder.adoc#webflux-ann-initbinder-model-design[model design]. + +When using constructor binding, you can customize request parameter names through an +`@BindParam` annotation. For example: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + class Account { + + private final String firstName; + + public Account(@BindParam("first-name") String firstName) { + this.firstName = firstName; + } + } +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + class Account(@BindParam("first-name") val firstName: String) +---- +====== + +NOTE: The `@BindParam` may also be placed on the fields that correspond to constructor +parameters. While `@BindParam` is supported out of the box, you can also use a +different annotation by setting a `DataBinder.NameResolver` on `DataBinder` WebFlux, unlike Spring MVC, supports reactive types in the model, e.g. `Mono`. You can declare a `@ModelAttribute` argument with or without a reactive type wrapper, and diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc index f2dd158dc4d..9562ac0f7bc 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc @@ -1,5 +1,5 @@ [[mvc-ann-initbinder]] -= `DataBinder` += `@InitBinder` [.small]#xref:web/webflux/controller/ann-initbinder.adoc[See equivalent in the Reactive stack]# diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc index f7af5f1f536..02e314974ab 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc @@ -73,12 +73,44 @@ Kotlin:: ---- ====== -Once a model attribute instance is available, `WebDataBinder` binds request parameters to -properties of the target `Object` with type conversion where necessary. -For more on data binding and validation, see -xref:web/webmvc/mvc-config/validation.adoc[Validation]. -For more on customizing data binding, see -xref:web/webmvc/mvc-controller/ann-initbinder.adoc[DataBinder]. +By default, both constructor and property +xref:core/validation/beans-beans.adoc#beans-binding[data binding] are applied. However, +model object design requires careful consideration, and for security reasons it is +recommended either to use an object tailored specifically for web binding, or to apply +constructor binding only. If property binding must still be used, then _allowedFields_ +patterns should be set to limit which properties can be set. For further details on this +and example configuration, see +xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[model design]. + +When using constructor binding, you can customize request parameter names through an +`@BindParam` annotation. For example: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + class Account { + + private final String firstName; + + public Account(@BindParam("first-name") String firstName) { + this.firstName = firstName; + } + } +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + class Account(@BindParam("first-name") val firstName: String) +---- +====== + +NOTE: The `@BindParam` may also be placed on the fields that correspond to constructor +parameters. While `@BindParam` is supported out of the box, you can also use a +different annotation by setting a `DataBinder.NameResolver` on `DataBinder` In some cases, you may want access to a model attribute without data binding. For such cases, you can inject the `Model` into the controller and access it directly or, diff --git a/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc b/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc index 66894cec125..90ee5468e33 100644 --- a/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc +++ b/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc @@ -38,9 +38,16 @@ the properties required for the input: } ---- -If a dedicated model object is not feasible, we strongy recommend registering -`allowedFields` patterns (case sensitive) on `WebDataBinder` in order to prevent other -properties from being set. For example: +Another good practice is to apply +xref:core/validation/beans-beans.adoc#beans-constructor-binding[constructor binding], +which uses only the request parameters it needs for constructor arguments, and any other +input is ignored. This is in contrast to property binding which by default binds every +request parameter for which there is a matching property. + +If neither a dedicated model object nor constructor binding is sufficient, and you must +use property binding, we strongy recommend registering `allowedFields` patterns (case +sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set. +For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -60,3 +67,25 @@ properties from being set. For example: You can also register `disallowedFields` patterns (case insensitive). However, "allowed" configuration is preferred over "disallowed" as it is more explicit and less prone to mistakes. + +By default, constructor and property binding are both used. If you want to use +constructor binding only, you can set the `declarativeBinding` flag on `WebDataBinder` +through an `@InitBinder` method either locally within a controller or globally through an +`@ControllerAdvice`. Turning this flag on ensures that only constructor binding is used +and that property binding is not used unless `allowedFields` patterns are configured. +For example: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Controller + public class MyController { + + @InitBinder + void initBinder(WebDataBinder binder) { + binder.setDeclarativeBinding(true); + } + + // @RequestMapping methods, etc. + + } +----