Где именно создается объект модели в Spring MVC?

после прохождения некоторых учебных пособий и первоначального чтения документа из docs.spring.org ссылка я понял, что он создан в контроллере класса POJO, созданного разработчиком. Но, читая это, я наткнулся на следующий абзац:--5-->

An @ModelAttribute в аргументе метода указывается, что аргумент должен быть извлечен из модели. Если аргумент отсутствует в модели, его следует сначала создать, а затем добавить в модель. Когда-то присутствует в модель, поля аргумента должны быть заполнены из всех параметров запроса, которые имеют совпадающие имена. Это известно как привязка данных в Spring MVC, очень полезный механизм, который избавляет вас от необходимости анализировать каждое поле формы отдельно.

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

}

Весна Документации

в абзаце больше всего тревожит строка:

"если нет в модели ... "

как данные могут быть там в модели? (Потому что мы не создали модель - она будет создана нами.)

кроме того, я видел несколько методов контроллера, принимая Model тип в качестве аргумента. Что это значит? Он получает Model создал где-то? Если да, то кто создает его для нас?

2 ответов


если в модели нет аргумента, его следует сначала создать, а затем добавить в модель.

в абзаце описывается следующий фрагмент кода:

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 инициализирует a ModelAndViewContainer экземпляр, который записывает модель и связанные с представлением решения, принятые HandlerMethodArgumentResolvers и HandlerMethodReturnValueHandlers во время вызова метода контроллера.

новая ModelAndViewContainer "объект" изначально заполняется flash атрибуты (если есть):

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");
    }

}

давайте сделаем запрос POST в URI /owners/123/pets/456/edit и видим результаты:

A new Pet instance was created!
Pet[456,123,null]
Pet[456,123,XXX]

A new Pet instance was created!

Весна создала ModelAndViewContainer и не нашел ничего, чтобы заполнить экземпляр (это запрос от клиента; не было никаких редиректов). Поскольку модель пуста, Spring пришлось создать новый Pet объект, вызывая конструктор по умолчанию, который напечатал строку.

Pet[456,123,null]

раз присутствующие в модели поля аргумента должны быть заполнены из всех параметров запроса, имеющих совпадающие имена.

мы напечатали данного Pet убедитесь, что все поля!--19--> и ownerId были правильно привязаны.

Pet[456,123,XXX]

мы hiddenField проверить нашу теорию и перенаправить на метод invokeInternal, который также ожидает @ModelAttribute. Как мы видим, второй метод получил экземпляр (с собственным скрытым значением), который был создано для первого метода.


чтобы ответить на вопрос, я нашел несколько фрагментов кода с помощью @andrew answer. Что оправдывает экземпляр ModelMap[объект модели] создается задолго до того, как наш контроллер/обработчик вызывается для определенного URL

 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 {
      ....
      ....
     }