Dongmin Shin opened SPR-16198 and commented

@ModelAttribute method arguments of @ModelAttribute annotated method are not resolved on webflux

@ModelAttribute("a")
public A attrA(@PathVariable String param1) {
    return new A(param1);
}

@ModelAttribute("b")
public B attrB(@ModelAttribute("a") A a, @RequestParam String param2) {
    // a.getParam1() is null
    return new B(a.getParam1(), param2);
}

cf.) * spring webmvc https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java#L131

  • spring webflux https://github.com/spring-projects/spring-framework/blob/master/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java#L110

Affects: 5.0.1

References: pull request https://github.com/spring-projects/spring-framework/pull/2003

Comment From: spring-projects-issues

Kevin Binswanger commented

Related to #10965

Comment From: spring-projects-issues

Rossen Stoyanchev commented

I will comment here on the proposed PR in order to add broader context about the feature.

In Spring MVC the it works regardless of how model attributes are added, i.e. via return value, from a method parameter, or by adding directly to an injected Model. To do that we continuously loop over model methods, each time selecting the first one to have all dependencies in the model already or none at all, until there are no more model methods left. By contrast the PR builds a graph upfront based on the declared return values from model methods, but doesn't take into account attributes that may be added from method parameters, or directly to Model

Unfortunately ModelFactoryTests doesn't demonstrate this capability so that's a failure on the part of the tests, but for example consider that you can change this code here to the one below and the tests still pass:

@ModelAttribute
public void getA(Model model) throws IOException {
    A a = new A();
    model.addAttribute("a", a);
    updateAndReturn(model, "getA", a);
}

There is also an important difference to keep in mind with WebFlux where unlike Spring MVC, the model can contain asynchronous attributes e.g. Mono<T>, and likewise model methods can declare asynchronous method parameters. fFr example, if methodA adds Mono<A> via model.addAttribute, then methodB could declare a Mono<A> method parameter and compose on that without resolving value A, or methodB could also choose to declare a method parameter of type A in which case the Mono would be resolved prior to calling methodB in order to pass the resolved value. For the most part this feature doesn't have too much to do for this because the invocation of model methods automatically takes care of resolving asynchronous values if necessary. However when checking dependencies it should be take into account the fact that the name of an attribute may vary slightly with or without a Mono/Single wrapper.