Skip to content

Commit d902bd7

Browse files
committed
Reject ModelMap argument types in WebFlux
Prior to this commit, if ModelMap was used as an argument type in a WebFlux controller method, the user encountered an exception similar to the following. java.lang.IllegalStateException: argument type mismatch Controller [example.SampleController] Method [java.lang.String example.SampleController.index(org.springframework.ui.ModelMap)] with argument values: [0] [type=org.springframework.validation.support.BindingAwareConcurrentModel] [value={}] However, the above error message is a bit cryptic since the error occurs while attempting to invoke the controller method with an instance of BindingAwareConcurrentModel which is not compatible with ModelMap. More importantly, this error message does not explicitly convey to the user that a ModelMap is not supported. This commit improve the diagnostics for the user in such scenarios by rejecting the use of ModelMap upfront in WebFlux. Consequently, for the same use case as above, the user now encounters an exception similar to the following. java.lang.IllegalStateException: Could not resolve parameter [0] in java.lang.String example.SampleController.index(org.springframework.ui.ModelMap): No suitable resolver Closes gh-33109
1 parent 053af5f commit d902bd7

File tree

3 files changed

+32
-16
lines changed

3 files changed

+32
-16
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.springframework.core.ResolvableType;
3232
import org.springframework.lang.Nullable;
3333
import org.springframework.ui.Model;
34+
import org.springframework.ui.ModelMap;
3435
import org.springframework.util.Assert;
3536
import org.springframework.util.ClassUtils;
3637
import org.springframework.validation.BindingResult;
@@ -53,9 +54,10 @@
5354
* {@code @jakarta.validation.Valid} or Spring's own
5455
* {@code @org.springframework.validation.annotation.Validated}.
5556
*
56-
* <p>When this handler is created with {@code useDefaultResolution=true}
57-
* any non-simple type argument and return value is regarded as a model
58-
* attribute with or without the presence of an {@code @ModelAttribute}.
57+
* <p>When this handler is created with {@code useDefaultResolution=true} any
58+
* non-simple type argument or return value (excluding those of type
59+
* {@link ModelMap}) is regarded as a model attribute with or without the
60+
* presence of {@code @ModelAttribute}.
5961
*
6062
* @author Rossen Stoyanchev
6163
* @author Juergen Hoeller
@@ -88,7 +90,8 @@ public boolean supportsParameter(MethodParameter parameter) {
8890
return true;
8991
}
9092
else if (this.useDefaultResolution) {
91-
return checkParameterType(parameter, type -> !BeanUtils.isSimpleProperty(type));
93+
return checkParameterType(parameter, type ->
94+
!(ModelMap.class.isAssignableFrom(type) || BeanUtils.isSimpleProperty(type)));
9295
}
9396
return false;
9497
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelMethodArgumentResolver.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
import org.springframework.core.MethodParameter;
2222
import org.springframework.core.ReactiveAdapterRegistry;
2323
import org.springframework.ui.Model;
24+
import org.springframework.ui.ModelMap;
2425
import org.springframework.web.reactive.BindingContext;
2526
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport;
2627
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
@@ -30,12 +31,16 @@
3031
* Resolver for a controller method argument of type {@link Model} that can
3132
* also be resolved as a {@link java.util.Map}.
3233
*
33-
* <p>A Map return value can be interpreted in more than one way depending
34+
* <p>A {@code Map} return value can be interpreted in more than one way depending
3435
* on the presence of annotations like {@code @ModelAttribute} or
35-
* {@code @ResponseBody}. As of 5.2 this resolver returns false if a
36-
* parameter of type {@code Map} is also annotated.
36+
* {@code @ResponseBody}.
37+
*
38+
* <p>As of 5.2 this resolver returns {@code false} if a parameter of type
39+
* {@code Map} is also annotated. As of 6.2 this resolver returns {@code false}
40+
* for a parameter of type {@link ModelMap}.
3741
*
3842
* @author Rossen Stoyanchev
43+
* @author Sam Brannen
3944
* @since 5.2
4045
*/
4146
public class ModelMethodArgumentResolver extends HandlerMethodArgumentResolverSupport
@@ -48,9 +53,9 @@ public ModelMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
4853

4954
@Override
5055
public boolean supportsParameter(MethodParameter param) {
51-
return checkParameterTypeNoReactiveWrapper(param, type ->
52-
Model.class.isAssignableFrom(type) ||
53-
(Map.class.isAssignableFrom(type) && param.getParameterAnnotations().length == 0));
56+
return checkParameterTypeNoReactiveWrapper(param, type -> Model.class.isAssignableFrom(type) ||
57+
(Map.class.isAssignableFrom(type) && !ModelMap.class.isAssignableFrom(type) &&
58+
!param.hasParameterAnnotations()));
5459
}
5560

5661
@Override
@@ -61,12 +66,12 @@ public Object resolveArgumentValue(
6166
if (Model.class.isAssignableFrom(type)) {
6267
return context.getModel();
6368
}
64-
else if (Map.class.isAssignableFrom(type)) {
69+
else if (Map.class.isAssignableFrom(type) && !ModelMap.class.isAssignableFrom(type)) {
6570
return context.getModel().asMap();
6671
}
6772
else {
6873
// Should never happen..
69-
throw new IllegalStateException("Unexpected method parameter type: " + type);
74+
throw new IllegalStateException("Unexpected method parameter type: " + type.getName());
7075
}
7176
}
7277

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelMethodArgumentResolverTests.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@
3232
import org.springframework.web.testfixture.server.MockServerWebExchange;
3333

3434
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
3536
import static org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest.get;
3637

3738
/**
3839
* Tests for {@link ModelMethodArgumentResolver}.
3940
*
4041
* @author Rossen Stoyanchev
42+
* @author Sam Brannen
4143
*/
4244
class ModelMethodArgumentResolverTests {
4345

@@ -52,11 +54,11 @@ class ModelMethodArgumentResolverTests {
5254
@Test
5355
void supportsParameter() {
5456
assertThat(this.resolver.supportsParameter(this.resolvable.arg(Model.class))).isTrue();
55-
assertThat(this.resolver.supportsParameter(this.resolvable.arg(ModelMap.class))).isTrue();
5657
assertThat(this.resolver.supportsParameter(
5758
this.resolvable.annotNotPresent().arg(Map.class, String.class, Object.class))).isTrue();
5859

5960
assertThat(this.resolver.supportsParameter(this.resolvable.arg(Object.class))).isFalse();
61+
assertThat(this.resolver.supportsParameter(this.resolvable.arg(ModelMap.class))).isFalse();
6062
assertThat(this.resolver.supportsParameter(
6163
this.resolvable.annotPresent(RequestBody.class).arg(Map.class, String.class, Object.class))).isFalse();
6264
}
@@ -65,7 +67,13 @@ void supportsParameter() {
6567
void resolveArgument() {
6668
testResolveArgument(this.resolvable.arg(Model.class));
6769
testResolveArgument(this.resolvable.annotNotPresent().arg(Map.class, String.class, Object.class));
68-
testResolveArgument(this.resolvable.arg(ModelMap.class));
70+
71+
assertThatIllegalStateException()
72+
.isThrownBy(() -> testResolveArgument(this.resolvable.arg(Object.class)))
73+
.withMessage("Unexpected method parameter type: " + Object.class.getName());
74+
assertThatIllegalStateException()
75+
.isThrownBy(() -> testResolveArgument(this.resolvable.arg(ModelMap.class)))
76+
.withMessage("Unexpected method parameter type: " + ModelMap.class.getName());
6977
}
7078

7179
private void testResolveArgument(MethodParameter parameter) {

0 commit comments

Comments
 (0)