在浏览了docs.spring.org引用的一些教程和初始文档后,我了解到它是在开发人员创建的POJO类的控制器中创建的。但在阅读本文时,我看到了以下段落:
方法参数上的
@ModelAttribute
表示应该从模型中检索参数。如果模型中不存在,则应首先实例化参数,然后将其添加到模型中。一旦出现在模型中,参数的字段应该从具有匹配名称的所有请求参数中填充。这在Spring MVC中称为数据绑定,这是一种非常有用的机制,可以使您不必单独解析每个表单字段。@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute Pet pet) { }
在该段中,最令人不安的是这条线:
“如果模型中没有......”
模型中的数据如何? (因为我们还没有创建模型 - 它将由我们创建。)
另外,我已经看到一些控制器方法接受Model
类型作为参数。那是什么意思?它是否在某处创建了Model
?如果是这样,谁为我们创造它?
如果模型中不存在,则应首先实例化参数,然后将其添加到模型中。
该段描述了以下代码:
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
} else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
...
}
}
...
mavContainer.addAllAttributes(attribute);
(取自ModelAttributeMethodProcessor#resolveArgument
)
对于每个请求,Spring都会初始化一个ModelAndViewContainer
实例,该实例记录了在调用控制器方法的过程中由HandlerMethodArgumentResolver
s和HandlerMethodReturnValueHandler
s做出的模型和视图相关决策。
新创建的ModelAndViewContainer
对象最初填充flash attributes(如果有):
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
这意味着如果参数已经存在于模型中,则不会初始化该参数。
为了证明这一点,让我们转到一个实际的例子。
Pet
类:
public class Pet {
private String petId;
private String ownerId;
private String hiddenField;
public Pet() {
System.out.println("A new Pet instance was created!");
}
// setters and toString
}
PetController
类:
@RestController
public class PetController {
@GetMapping(value = "/internal")
public void invokeInternal(@ModelAttribute Pet pet) {
System.out.println(pet);
}
@PostMapping(value = "/owners/{ownerId}/pets/{petId}/edit")
public RedirectView editPet(@ModelAttribute Pet pet, RedirectAttributes attributes) {
System.out.println(pet);
pet.setHiddenField("XXX");
attributes.addFlashAttribute("pet", pet);
return new RedirectView("/internal");
}
}
让我们向URI /owners/123/pets/456/edit
发出POST请求并查看结果:
A new Pet instance was created!
Pet[456,123,null]
Pet[456,123,XXX]
A new Pet instance was created!
Spring创建了一个ModelAndViewContainer
并且找不到任何东西来填充实例(这是来自客户端的请求;没有任何重定向)。由于模型是空的,Spring必须通过调用打印该行的默认构造函数来创建一个新的Pet
对象。
Pet[456,123,null]
一旦出现在模型中,参数的字段应该从具有匹配名称的所有请求参数中填充。
我们打印了给定的Pet
以确保所有字段petId
和ownerId
都已正确绑定。
Pet[456,123,XXX]
我们设置hiddenField
来检查我们的理论并重定向到invokeInternal
方法,该方法也期望@ModelAttribute
。如我们所见,第二种方法接收了为第一种方法创建的实例(具有自己的隐藏值)。
为了回答这个问题,我在@andrew回答的帮助下找到了几段代码。在为特定URL调用控制器/处理程序之前,很好地创建了ModelMap实例[模型对象]
public class ModelAndViewContainer {
private boolean ignoreDefaultModelOnRedirect = false;
@Nullable
private Object view;
private final ModelMap defaultModel = new BindingAwareModelMap();
....
.....
}
如果我们看到上面的代码片段(取自spring-webmvc-5.0.8 jar)。之前很好地创建了BindingAwareModelMap模型对象。
为了更好地理解添加类BindingAwareModelMap的注释
/**
* Subclass of {@link org.springframework.ui.ExtendedModelMap} that automatically removes
* a {@link org.springframework.validation.BindingResult} object if the corresponding
* target attribute gets replaced through regular {@link Map} operations.
*
* <p>This is the class exposed to handler methods by Spring MVC, typically consumed through
* a declaration of the {@link org.springframework.ui.Model} interface. There is no need to
* build it within user code; a plain {@link org.springframework.ui.ModelMap} or even a just
* a regular {@link Map} with String keys will be good enough to return a user model.
*
@SuppressWarnings("serial")
public class BindingAwareModelMap extends ExtendedModelMap {
....
....
}