在 Spring MVC 中创建的模型对象到底在哪里?

Where exactly is a model object created in Spring MVC?

在阅读了一些教程并从 docs.spring.org 参考资料中阅读了初步文档后,我了解到它是在开发人员创建的 POJO class 的控制器中创建的。 但是在阅读本文时,我遇到了以下段落:

An @ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument's fields should be populated from all request parameters that have matching names. This is known as data binding in Spring MVC, a very useful mechanism that saves you from having to parse each form field individually.

@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) {
   
}

Spring Documentation

段落中最令人不安的是行:

"If not present in the model ... "

模型中怎么会有数据? (因为我们还没有创建模型 - 它将由我们创建。)

另外,我看到一些控制器方法接受 Model 类型作为参数。那是什么意思?是否在某处创建了 Model?如果是的话,是谁为我们创造的?

If not present in the model, the argument should be instantiated first and then added to the 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 instance which records model and view-related decisions made by HandlerMethodArgumentResolvers and HandlerMethodReturnValueHandlers。

一个新创建的ModelAndViewContainer object is initially populated with flash attributes(如果有的话):

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

这意味着如果参数已经存在于模型中,则不会对其进行初始化。

为了证明这一点,让我们来看一个实际的例子。

Pet class:

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 class:

@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]

Once present in the model, the argument's fields should be populated from all request parameters that have matching names.

我们打印了给定的 Pet 以确保所有字段 petIdownerId 都已正确绑定。

Pet[456,123,XXX]

我们设置 hiddenField 来检查我们的理论并重定向到方法 invokeInternal,它也需要一个 @ModelAttribute。如我们所见,第二个方法接收了为第一个方法创建的实例(具有自己的隐藏值)。

为了回答这个问题,我在@andrew 回答的帮助下找到了一些代码片段。这证明在我们的 controller/handler 被调用特定 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 模型对象创建得很好。

为了更好地理解添加 class 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 {
      ....
      ....
     }