Где именно находится модельный объект, созданный в Spring MVC?
Пройдя несколько уроков и начальное чтение документа из ссылки на docs.spring.org, я понял, что он создан в контроллере класса POJO, созданного разработчиком. Но, читая это, я наткнулся на абзац ниже:
@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 инициализирует ModelAndViewContainer
экземпляр, который записывает модель и связанные с видом решения, принятые HandlerMethodArgumentResolver
с и HandlerMethodReturnValueHandler
s во время вызова метода контроллера.
Недавно созданный ModelAndViewContainer
Объект изначально заполняется флэш-атрибутами (если есть):
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
чтобы убедиться, что все поля petId
а также ownerId
был связан правильно.
Pet[456,123,XXX]
Мы установили hiddenField
проверить нашу теорию и перенаправить на метод invokeInternal
который также ожидает @ModelAttribute
, Как видим, второй метод получил экземпляр (со своим скрытым значением), который был создан для первого метода.
Чтобы ответить на вопрос, я нашел несколько фрагментов кода с помощью ответа @andrew. Что оправдывает экземпляр 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 {
....
....
}