Skip to content

@ModelAttribute binding defined globally for particular attribute rather than per method invocation [SPR-16083] #20632

@spring-projects-issues

Description

@spring-projects-issues

Brice Roncace opened SPR-16083 and commented

Given this test controller (and java bean):

@Controller
public class TestController {

  public static class Bean {
    private Long id;

    public Long getId() {
      return id;
    }

    public void setId(Long id) {
      this.id = id;
    }

    @Override
    public String toString() {
      return "Bean{" + "id=" + id + '}';
    }
  }

  @ModelAttribute
  public Bean prepareBean() {
    return new Bean();
  }

  @ModelAttribute
  public void prepareBean2(@ModelAttribute(binding=false) Bean bean) {
    System.out.println("prepareBean2: " + bean);
  }

  @GetMapping("/bindTest")
  public String editBean(@ModelAttribute(binding=true) Bean bean, BindingResult br) {
    System.out.println("Inside editBean: " + bean);
    System.out.println(br.getAllErrors());
    return "index";
  }
}

When exercising this controller with bindTest?id=1 no binding will occur because the prepareBean2 method disables binding for the bean model attribute even though a subsequent use of the @ModelAttribute in the editBean method expects binding to occur.

The unsatisfactory workarounds are to allow binding and insert an ignored BindingResult parameter in the prepareBean2 method so that if binding errors occur, they can be handled in the editBean method where they are expected:

  @ModelAttribute
  public Bean prepareBean() {
    return new Bean();
  }

  @ModelAttribute
  public void prepareBean2(@ModelAttribute Bean bean, BindingResult ignore) {
    System.out.println("prepareBean2: " + bean);
  }

  @GetMapping("/bindTest")
  public String editBean(@ModelAttribute Bean bean, BindingResult br) {
    System.out.println("Inside editBean: " + bean);
    System.out.println(br.getAllErrors());
    return "index";
  }

Or pull the bean out of the model to prevent binding in the prepareBean2 method:

  @ModelAttribute
  public Bean prepareBean() {
    return new Bean();
  }

  @ModelAttribute
  public void prepareBean2(Model m) {
    Object bean = m.asMap().get("bean");
    System.out.println("prepareBean2: " + bean);
  }

  @GetMapping("/bindTest")
  public String editBean(@ModelAttribute Bean bean, BindingResult br) {
    System.out.println("Inside editBean: " + bean);
    System.out.println(br.getAllErrors());
    return "login";
  }

Note: in a real-world example these two @ModelAttribute annotated methods would not appear in the same class (e.g. @ControllerAdvice would apply the initial prepareBean method).


Issue Links:

Referenced from: commits b0ae8f6, bec1fc1

Backported to: 4.3.13

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)status: backportedAn issue that has been backported to maintenance branchestype: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions