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

Posted

技术标签:

【中文标题】Spring MVC 中创建的模型对象到底在哪里?【英文标题】:Where exactly is a model object created in Spring MVC? 【发布时间】:2019-02-07 03:24:34 【问题描述】:

在阅读了一些教程和从 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?如果是这样,谁在为我们创造它?

【问题讨论】:

【参考方案1】:

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

该段描述了以下一段代码:

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

新创建的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。如我们所见,第二个方法接收到为第一个方法创建的实例(具有自己的隐藏值)。

【讨论】:

这真的很有趣。但是关于“没有找到任何东西来填充实例”:在什么情况下它会找到一些东西?不是总是需要创建一个新实例吗? @daniu,如果发生å重定向,ModelAndViewContainer 的新实例可能会由重定向初始化程序设置的从RequestContextUtils.getInputFlashMap(request) 提取的属性填充 @daniu Spring 不认为重定向足以改变游戏规则以清除它们的整个“上下文”。这是有道理的:在invokeInternal 中,如果发生重定向,我确实希望为editPet 创建的宠物发生。 哦,这就是为什么你甚至添加了internal() 一个,明白了。谢谢:) @AndrewTobilko 您能否也回答这个问题“另外,我看到一些控制器方法接受模型类型作为参数。这是什么意思?它是在某处创建模型吗?如果那么是谁在为我们创造呢?”【参考方案2】:

为了回答这个问题,我在@andrew 回答的帮助下发现了一些代码 sn-ps。这证明了在为特定 URL 调用我们的控制器/处理程序之前创建了一个 ModelMap 实例[一个模型对象]

 public class ModelAndViewContainer 

    private boolean ignoreDefaultModelOnRedirect = false;

    @Nullable
    private Object view;

    private final ModelMap defaultModel = new BindingAwareModelMap();
      ....
      .....
   

如果我们看到上面的 sn-p 代码(取自 spring-webmvc-5.0.8 jar)。 BindingAwareModelMap 模型对象早于创建。

为了更好地理解为类添加 cmets 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 
      ....
      ....
     

【讨论】:

以上是关于Spring MVC 中创建的模型对象到底在哪里?的主要内容,如果未能解决你的问题,请参考以下文章

我在哪里可以找到可以在 Powershell 中创建的所有 COM 对象?

2.获取Spring容器中创建的对象

在 Blender 中创建的 3D 模型上使用材质

javaJava 创建的对象到底放在哪?垃圾回收

如何在 Eclipse 中创建 Spring MVC 项目?

如何停止自动登录到aspnet中创建的帐户