LoginSignup
0
0

More than 3 years have passed since last update.

What happen when @ModelAttribute annoated on method parameter of controller

Posted at

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

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";
    }
    // ...
}

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0