Notice
I wrote this article and was originally published on Qiita on 17 September 2019.
Code is extracted from my notice board example application.
Thymeleaf template of input form 'resources/templates/private/message.html'
<!DOCTYPE html> <html th:lang="${#locale.language}" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <title>[[#{applicationName}]] - [[${isEdit}? #{editMessage}: #{newMessage}]]</title> <meta charset="utf-8"> <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"> </head> <body> <div class="ui container"> <h4 th:text="${isEdit}? #{editMessage}: #{newMessage}"></h4> <form th:action="@{/manage/__${postHandler}__}" method="post" th:object="${message}" class="ui form"> <fieldset> <legend>[[#{message}]]</legend> <div class="field"> <label>[[#{message.publishDate}]]</label> <input type="text" th:field="*{publishDate}" /> </div> <div class="field"> <label>[[#{message.removeDate}]]</label> <input type="text" th:field="*{removeDate}" /> </div> <div class="field"> <label>[[#{message.description}]]</label> <textarea name="description" th:text="*{description}"></textarea> </div> <button class="ui mini primary button">[[#{save}]] <i class="send icon"></i></button> </fieldset> </form> </div> </body> </html>
Actual HTML code received by browner when calling http://localhost:8080/manage/new
<!DOCTYPE html> <html lang="en"> <head> <title>Notice board - New message</title> <meta charset="utf-8"> <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"> </head> <body> <div class="ui container"> <h4>New message</h4> <form action="/manage/new/save" method="post" class="ui form"><input type="hidden" name="_csrf" value="87baeee6-deea-4c4b-8b2b-b17e9be876e0"/> <fieldset> <legend>Message</legend> <div class="field"> <label>Publish Date</label> <input type="text" id="publishDate" name="publishDate" value="" /> </div> <div class="field"> <label>Remove Date</label> <input type="text" id="removeDate" name="removeDate" value="" /> </div> <div class="field"> <label>Message</label> <textarea name="description"></textarea> </div> <button class="ui mini primary button">Save <i class="send icon"></i></button> </fieldset> </form> </div> </body> </html>
After filling data like below
click 'Save', a HTTP POST request with following parameter sends to http://localhost:8080/manage/new/save
saveCreateMessage() method in info.saladlam.example.spring.noticeboard.controller.PrivateController responses for handle this request
@Controller @RequestMapping("/manage") public class PrivateController { @PostMapping("/new/save") public String saveCreateMessage(@ModelAttribute MessageDto message, BindingResult errors) { message.setOwner(this.getLoginName()); this.messageService.save(message); return "redirect:/manage"; } // ... }
MessageDto instance is built by class org.springframework.web.method.annotation.ModelAttributeMethodProcessor, actual building operation is defined on method resolveArgument()
@Override @Nullable public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { // Create attribute instance try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { // No BindingResult parameter -> fail with BindException throw ex; } // Otherwise, expose null/empty value and associated BindingResult if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
MessageDto instance obtains after running
bindRequestParameters(binder, webRequest);
and validation performs if validator is defined
validateIfApplicable(binder, parameter);
finally, MessageDto instance as parameter message is passed into controller
Top comments (0)