@ModelAttribute
@ModelAttribute
方法参数注解将请求参数、URI 路径变量和请求头绑定到某个模型对象。例如:
The @ModelAttribute
method parameter annotation binds request parameters, URI path variables,
and request headers onto a model object. For example:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { (1)
// method logic...
}
1 | 绑定到 `Pet`的一个实例。 |
2 | Bind to an instance of Pet . |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { (1)
// method logic...
}
1 | 绑定到 `Pet`的一个实例。 |
2 | Bind to an instance of Pet . |
请求参数是 Servlet API 概念,包括来自请求正文的表单数据和查询参数。也包括 URI 变量和头,但仅当它们不以相同名称覆盖请求参数时。连字符从头名称中除去。
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.
上面的 Pet
实例可能为:
The Pet
instance above may be:
-
从模型访问,可以在模型中通过@ModelAttribute method添加此模型。
-
Accessed from the model where it could have been added by a @ModelAttribute method.
-
如果模型属性在类级别的
@SessionAttributes
注释中列出,则从 HTTP 会话访问。 -
Accessed from the HTTP session if the model attribute was listed in the class-level
@SessionAttributes
annotation. -
如果模型属性名称与请求值(例如路径变量或请求参数)的名称匹配,则通过 `Converter`获取(如下例所示)。
-
Obtained through a
Converter
if the model attribute name matches the name of a request value such as a path variable or a request parameter (example follows). -
通过默认构造函数实例化。
-
Instantiated through a default constructor.
-
通过与 Servlet 请求参数匹配的参数实例化"`primary constructor`"。参数名称通过字节码中运行时保留的参数名称确定。
-
Instantiated through a “primary constructor” with arguments that match to Servlet request parameters. Argument names are determined through runtime-retained parameter names in the bytecode.
如上所述,Converter<String, T>
可用于在模型属性名称与路径变量或请求参数等请求值名称匹配并且存在兼容的 Converter<String, T>
时获取模型对象。在以下示例中,模型属性名称 account
与 URI 路径变量 account
匹配,并且有一个已注册的 Converter<String, Account>
,该 Converter<String, Account>
可能从持久化存储区中检索它:
As mentioned above, a Converter<String, T>
may be used to obtain the model object if
the model attribute name matches to the name of a request value such as a path variable or a
request parameter, and there is a compatible Converter<String, T>
. In the below example,
the model attribute name account
matches URI path variable account
, and there is a
registered Converter<String, Account>
that perhaps retrieves it from a persistence store:
-
Java
-
Kotlin
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) { (1)
// ...
}
@PutMapping("/accounts/{account}")
fun save(@ModelAttribute("account") account: Account): String { (1)
// ...
}
默认情况下,将应用构造函数和属性 data binding。不过,模型对象设计需要仔细考虑,并且出于安全原因,建议使用专门为 Web 绑定设计的对象,或者只应用构造函数绑定。如果仍然必须使用属性绑定,那么应当设置 allowedFields 模式以限制可设置的属性。有关此示例配置的更多详细信息,请参阅 model design。
By default, both constructor and property 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 model design.
在使用构造函数绑定时,你可以通过 @BindParam
注解自定义请求参数名称。例如:
When using constructor binding, you can customize request parameter names through an
@BindParam
annotation. For example:
-
Java
-
Kotlin
class Account {
private final String firstName;
public Account(@BindParam("first-name") String firstName) {
this.firstName = firstName;
}
}
class Account(@BindParam("first-name") val firstName: String)
也可以将 |
The |
构造函数绑定支持`List`、Map`和数组参数,这些参数从单个字符串(例如,逗号分隔的列表)转换而来,或者基于索引键(例如`accounts[2].name`或`account[KEY].name
)。
Constructor binding supports List
, Map
, and array arguments either converted from
a single string, e.g. comma-separated list, or based on indexed keys such as
accounts[2].name
or account[KEY].name
.
在某些情况下,你可能希望访问无数据绑定的模型属性。对于此类情况,你可以将 Model
注入到控制器并直接访问它,或者设置 @ModelAttribute(binding=false)
,如下例所示:
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,
alternatively, set @ModelAttribute(binding=false)
, as the following example shows:
-
Java
-
Kotlin
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(AccountForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) { (1)
// ...
}
1 | Setting @ModelAttribute(binding=false) . |
@ModelAttribute
fun setUpForm(): AccountForm {
return AccountForm()
}
@ModelAttribute
fun findAccount(@PathVariable accountId: String): Account {
return accountRepository.findOne(accountId)
}
@PostMapping("update")
fun update(form: AccountForm, result: BindingResult,
@ModelAttribute(binding = false) account: Account): String { (1)
// ...
}
1 | Setting @ModelAt\tribute(binding=false) . |
如果数据绑定导致错误,默认情况下会引发 MethodArgumentNotValidException
,但你也可以紧挨 @ModelAttribute
添加一个 BindingResult
参数,以便在控制器方法中处理此类错误。例如:
If data binding results in errors, by default a MethodArgumentNotValidException
is raised,
but you can also add a BindingResult
argument immediately next to the @ModelAttribute
in order to handle such errors in the controller method. For example:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { (1)
if (result.hasErrors()) {
return "petForm";
}
// ...
}
1 | 在`@ModelAttribute`旁边添加一个`BindingResult`。 |
2 | Adding a BindingResult next to the @ModelAttribute . |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
if (result.hasErrors()) {
return "petForm"
}
// ...
}
1 | 在`@ModelAttribute`旁边添加一个`BindingResult`。 |
2 | Adding a BindingResult next to the @ModelAttribute . |
在数据绑定后,可以通过添加 jakarta.validation.Valid
注释或 Spring 的 @Validated
注释,自动应用验证。请参见 Bean Validation 和 Spring validation。例如:
You can automatically apply validation after data binding by adding the
jakarta.validation.Valid
annotation or Spring’s @Validated
annotation.
See Bean Validation and
Spring validation. For example:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { (1)
if (result.hasErrors()) {
return "petForm";
}
// ...
}
1 | Validate the Pet instance. |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
if (result.hasErrors()) {
return "petForm"
}
// ...
}
1 | Validate the Pet instance. |
如果没有 @ModelAttribute
后面的 BindingResult
参数,那么会针对验证错误引发 MethodArgumentNotValueException
。但是,如果方法验证正在应用,因为其他参数具有 @jakarta.validation.Constraint
注释,那么会改为引发 HandlerMethodValidationException
。有关更多详细信息,请参见第 Validation 段。
If there is no BindingResult
parameter after the @ModelAttribute
, then
MethodArgumentNotValueException
is raised with the validation errors. However, if method
validation applies because other parameters have @jakarta.validation.Constraint
annotations,
then HandlerMethodValidationException
is raised instead. For more details, see the section
Validation.
使用 |
Using |
使用 GraalVM 编译为本机映像时,上面描述的隐式 @ModelAttribute
支持不允许提前正确推断相关的 data binding 反射提示。因此,建议明确使用 @ModelAttribute
为方法参数添加注释,以便在 GraalVM 本机映像中使用。
When compiling to a native image with GraalVM, the implicit @ModelAttribute
support described above does not allow proper ahead-of-time inference of related data
binding reflection hints. As a consequence, it is recommended to explicitly annotate
method parameters with @ModelAttribute
for use in a GraalVM native image.