Skip to content

Commit 5a3ff35

Browse files
committed
Map arg resolver backs out if annotations present
Closes gh-21874
1 parent a2fcf0a commit 5a3ff35

File tree

4 files changed

+71
-44
lines changed

4 files changed

+71
-44
lines changed

spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -32,8 +32,8 @@
3232
*
3333
* <p>A Map return value can be interpreted in more than one ways depending
3434
* on the presence of annotations like {@code @ModelAttribute} or
35-
* {@code @ResponseBody}. Therefore this handler should be configured after
36-
* the handlers that support these annotations.
35+
* {@code @ResponseBody}. As of 5.2 this resolver returns false if the
36+
* parameter is annotated.
3737
*
3838
* @author Rossen Stoyanchev
3939
* @since 3.1
@@ -42,7 +42,8 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle
4242

4343
@Override
4444
public boolean supportsParameter(MethodParameter parameter) {
45-
return Map.class.isAssignableFrom(parameter.getParameterType());
45+
return Map.class.isAssignableFrom(parameter.getParameterType()) &&
46+
parameter.getParameterAnnotations().length == 0;
4647
}
4748

4849
@Override

spring-web/src/test/java/org/springframework/web/method/annotation/MapMethodProcessorTests.java

+30-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.web.method.annotation;
1818

19-
import java.lang.reflect.Method;
2019
import java.util.Map;
2120

2221
import org.junit.Before;
@@ -25,14 +24,18 @@
2524
import org.springframework.core.MethodParameter;
2625
import org.springframework.mock.web.test.MockHttpServletRequest;
2726
import org.springframework.ui.ModelMap;
27+
import org.springframework.web.bind.annotation.RequestBody;
28+
import org.springframework.web.bind.annotation.RequestMapping;
2829
import org.springframework.web.context.request.NativeWebRequest;
2930
import org.springframework.web.context.request.ServletWebRequest;
31+
import org.springframework.web.method.ResolvableMethod;
3032
import org.springframework.web.method.support.ModelAndViewContainer;
3133

3234
import static org.junit.Assert.*;
3335

3436
/**
35-
* Test fixture with {@link org.springframework.web.method.annotation.MapMethodProcessor}.
37+
* Test fixture with
38+
* {@link org.springframework.web.method.annotation.MapMethodProcessor}.
3639
*
3740
* @author Rossen Stoyanchev
3841
*/
@@ -42,52 +45,59 @@ public class MapMethodProcessorTests {
4245

4346
private ModelAndViewContainer mavContainer;
4447

45-
private MethodParameter paramMap;
48+
private NativeWebRequest webRequest;
4649

47-
private MethodParameter returnParamMap;
50+
private final ResolvableMethod resolvable =
51+
ResolvableMethod.on(getClass()).annotPresent(RequestMapping.class).build();
4852

49-
private NativeWebRequest webRequest;
5053

5154
@Before
5255
public void setUp() throws Exception {
53-
processor = new MapMethodProcessor();
54-
mavContainer = new ModelAndViewContainer();
55-
56-
Method method = getClass().getDeclaredMethod("map", Map.class);
57-
paramMap = new MethodParameter(method, 0);
58-
returnParamMap = new MethodParameter(method, 0);
59-
60-
webRequest = new ServletWebRequest(new MockHttpServletRequest());
56+
this.processor = new MapMethodProcessor();
57+
this.mavContainer = new ModelAndViewContainer();
58+
this.webRequest = new ServletWebRequest(new MockHttpServletRequest());
6159
}
6260

61+
6362
@Test
6463
public void supportsParameter() {
65-
assertTrue(processor.supportsParameter(paramMap));
64+
assertTrue(this.processor.supportsParameter(
65+
this.resolvable.annotNotPresent().arg(Map.class, String.class, Object.class)));
66+
assertFalse(this.processor.supportsParameter(
67+
this.resolvable.annotPresent(RequestBody.class).arg(Map.class, String.class, Object.class)));
6668
}
6769

6870
@Test
6971
public void supportsReturnType() {
70-
assertTrue(processor.supportsReturnType(returnParamMap));
72+
assertTrue(this.processor.supportsReturnType(this.resolvable.returnType()));
7173
}
7274

7375
@Test
7476
public void resolveArgumentValue() throws Exception {
75-
assertSame(mavContainer.getModel(), processor.resolveArgument(paramMap, mavContainer, webRequest, null));
77+
MethodParameter param = this.resolvable.annotNotPresent().arg(Map.class, String.class, Object.class);
78+
assertSame(this.mavContainer.getModel(),
79+
this.processor.resolveArgument(param, this.mavContainer, this.webRequest, null));
7680
}
7781

7882
@Test
7983
public void handleMapReturnValue() throws Exception {
80-
mavContainer.addAttribute("attr1", "value1");
84+
this.mavContainer.addAttribute("attr1", "value1");
8185
Map<String, Object> returnValue = new ModelMap("attr2", "value2");
8286

83-
processor.handleReturnValue(returnValue , returnParamMap, mavContainer, webRequest);
87+
this.processor.handleReturnValue(
88+
returnValue , this.resolvable.returnType(), this.mavContainer, this.webRequest);
8489

8590
assertEquals("value1", mavContainer.getModel().get("attr1"));
8691
assertEquals("value2", mavContainer.getModel().get("attr2"));
8792
}
8893

94+
8995
@SuppressWarnings("unused")
90-
private Map<String, Object> map(Map<String, Object> map) {
96+
@RequestMapping
97+
private Map<String, Object> handle(
98+
Map<String, Object> map,
99+
@RequestBody Map<String, Object> annotMap) {
100+
91101
return null;
92102
}
93103

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -30,6 +30,11 @@
3030
* Resolver for a controller method argument of type {@link Model} that can
3131
* also be resolved as a {@link java.util.Map}.
3232
*
33+
* <p>A Map return value can be interpreted in more than one ways depending
34+
* 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.
37+
*
3338
* @author Rossen Stoyanchev
3439
* @since 5.0
3540
*/
@@ -42,9 +47,10 @@ public ModelArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
4247

4348

4449
@Override
45-
public boolean supportsParameter(MethodParameter parameter) {
46-
return checkParameterTypeNoReactiveWrapper(parameter,
47-
type -> Model.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type));
50+
public boolean supportsParameter(MethodParameter param) {
51+
return checkParameterTypeNoReactiveWrapper(param, type ->
52+
Model.class.isAssignableFrom(type) ||
53+
(Map.class.isAssignableFrom(type) && param.getParameterAnnotations().length == 0));
4854
}
4955

5056
@Override

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

+26-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -25,14 +25,13 @@
2525
import org.springframework.mock.web.test.server.MockServerWebExchange;
2626
import org.springframework.ui.Model;
2727
import org.springframework.ui.ModelMap;
28+
import org.springframework.web.bind.annotation.RequestBody;
2829
import org.springframework.web.method.ResolvableMethod;
2930
import org.springframework.web.reactive.BindingContext;
3031
import org.springframework.web.server.ServerWebExchange;
3132

32-
import static org.junit.Assert.assertFalse;
33-
import static org.junit.Assert.assertSame;
34-
import static org.junit.Assert.assertTrue;
35-
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.get;
33+
import static org.junit.Assert.*;
34+
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.*;
3635

3736
/**
3837
* Unit tests for {@link ModelArgumentResolver}.
@@ -45,22 +44,27 @@ public class ModelArgumentResolverTests {
4544

4645
private final ServerWebExchange exchange = MockServerWebExchange.from(get("/"));
4746

48-
private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
47+
private final ResolvableMethod resolvable = ResolvableMethod.on(getClass()).named("handle").build();
4948

5049

5150
@Test
52-
public void supportsParameter() throws Exception {
53-
assertTrue(this.resolver.supportsParameter(this.testMethod.arg(Model.class)));
54-
assertTrue(this.resolver.supportsParameter(this.testMethod.arg(Map.class, String.class, Object.class)));
55-
assertTrue(this.resolver.supportsParameter(this.testMethod.arg(ModelMap.class)));
56-
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(Object.class)));
51+
public void supportsParameter() {
52+
53+
assertTrue(this.resolver.supportsParameter(this.resolvable.arg(Model.class)));
54+
assertTrue(this.resolver.supportsParameter(this.resolvable.arg(ModelMap.class)));
55+
assertTrue(this.resolver.supportsParameter(
56+
this.resolvable.annotNotPresent().arg(Map.class, String.class, Object.class)));
57+
58+
assertFalse(this.resolver.supportsParameter(this.resolvable.arg(Object.class)));
59+
assertFalse(this.resolver.supportsParameter(
60+
this.resolvable.annotPresent(RequestBody.class).arg(Map.class, String.class, Object.class)));
5761
}
5862

5963
@Test
60-
public void resolveArgument() throws Exception {
61-
testResolveArgument(this.testMethod.arg(Model.class));
62-
testResolveArgument(this.testMethod.arg(Map.class, String.class, Object.class));
63-
testResolveArgument(this.testMethod.arg(ModelMap.class));
64+
public void resolveArgument() {
65+
testResolveArgument(this.resolvable.arg(Model.class));
66+
testResolveArgument(this.resolvable.annotNotPresent().arg(Map.class, String.class, Object.class));
67+
testResolveArgument(this.resolvable.arg(ModelMap.class));
6468
}
6569

6670
private void testResolveArgument(MethodParameter parameter) {
@@ -69,7 +73,13 @@ private void testResolveArgument(MethodParameter parameter) {
6973
assertSame(context.getModel(), result);
7074
}
7175

76+
7277
@SuppressWarnings("unused")
73-
void handle(Model model, Map<String, Object> map, ModelMap modelMap, Object object) {}
78+
void handle(
79+
Model model,
80+
Map<String, Object> map,
81+
@RequestBody Map<String, Object> annotatedMap,
82+
ModelMap modelMap,
83+
Object object) {}
7484

7585
}

0 commit comments

Comments
 (0)