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

时间:2018-09-01 08:57:22

标签: java spring spring-mvc modelattribute request-mapping

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

  

方法参数上的@ModelAttribute指示应从模型中检索参数。如果模型中不存在该参数,则应首先实例化该参数,然后将其添加到模型中。一旦出现在模型中,则应从具有匹配名称的所有请求参数中填充参数的字段。这在Spring MVC中被称为数据绑定,这是一种非常有用的机制,使您不必分别解析每个表单字段。

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

}
     

Spring Documentation

在该段中最令人不安的是一行:

  

“如果模型中不存在...”

如何在模型中找到数据? (因为我们尚未创建模型-它会由我们创建。)

我还看到了一些接受Model类型作为参数的控制器方法。这意味着什么?是否在某处创建了Model?如果是这样,谁在为我们创建它?

2 个答案:

答案 0 :(得分:6)

  

如果模型中不存在该参数,则应首先实例化该参数,然后将其添加到模型中。

该段描述了以下代码:

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实例,该实例记录在控制器方法调用过程中HandlerMethodArgumentResolverHandlerMethodReturnValueHandler做出的模型和与视图相关的决策。 / p>

新创建的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,以确保所有字段petIdownerId均已正确绑定。

Pet[456,123,XXX]

我们设置hiddenField来检查我们的理论,然后重定向到方法invokeInternal,该方法也期望使用@ModelAttribute。如我们所见,第二个方法接收到为第一个方法创建的实例(具有自己的隐藏值)。

答案 1 :(得分:0)

要回答这个问题,我在@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 {
      ....
      ....
     }