DEV Community

Salad Lam
Salad Lam

Posted on

What happen when @ModelAttribute annoated on method parameter of controller

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

After filling data like below

form_filled.png

click 'Save', a HTTP POST request with following parameter sends to http://localhost:8080/manage/new/save

post.png

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";
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
Enter fullscreen mode Exit fullscreen mode

MessageDto instance obtains after running

bindRequestParameters(binder, webRequest);
Enter fullscreen mode Exit fullscreen mode

and validation performs if validator is defined

validateIfApplicable(binder, parameter);
Enter fullscreen mode Exit fullscreen mode

finally, MessageDto instance as parameter message is passed into controller

Top comments (0)