【问题标题】:Where exactly is a model object created in Spring MVC?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?如果是这样,谁在为我们创造它?

【问题讨论】:

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


    【解决方案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 {
          ....
          ....
         }
    

    【讨论】:

      猜你喜欢
      • 2021-01-30
      • 1970-01-01
      • 2019-07-10
      • 1970-01-01
      • 2018-02-27
      • 2015-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多