Skip to content

Commit e0d7c6b

Browse files
committed
Name attributes in method argument annotations allow for placeholders and expressions
Issue: SPR-13952
1 parent c2f7047 commit e0d7c6b

File tree

4 files changed

+115
-70
lines changed

4 files changed

+115
-70
lines changed

spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java

+25-18
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,24 @@ public Object resolveArgument(MethodParameter parameter, Message<?> message) thr
8787
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
8888
MethodParameter nestedParameter = parameter.nestedIfOptional();
8989

90-
Object arg = resolveArgumentInternal(nestedParameter, message, namedValueInfo.name);
90+
Object resolvedName = resolveStringValue(namedValueInfo.name);
91+
if (resolvedName == null) {
92+
throw new IllegalArgumentException(
93+
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
94+
}
95+
96+
Object arg = resolveArgumentInternal(nestedParameter, message, resolvedName.toString());
9197
if (arg == null) {
9298
if (namedValueInfo.defaultValue != null) {
93-
arg = resolveDefaultValue(namedValueInfo.defaultValue);
99+
arg = resolveStringValue(namedValueInfo.defaultValue);
94100
}
95101
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
96102
handleMissingValue(namedValueInfo.name, nestedParameter, message);
97103
}
98104
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
99105
}
100106
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
101-
arg = resolveDefaultValue(namedValueInfo.defaultValue);
107+
arg = resolveStringValue(namedValueInfo.defaultValue);
102108
}
103109

104110
if (!ClassUtils.isAssignableValue(parameter.getParameterType(), arg)) {
@@ -148,6 +154,22 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
148154
return new NamedValueInfo(name, info.required, defaultValue);
149155
}
150156

157+
/**
158+
* Resolve the given annotation-specified value,
159+
* potentially containing placeholders and expressions.
160+
*/
161+
private Object resolveStringValue(String value) {
162+
if (this.configurableBeanFactory == null) {
163+
return value;
164+
}
165+
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
166+
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
167+
if (exprResolver == null) {
168+
return value;
169+
}
170+
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
171+
}
172+
151173
/**
152174
* Resolves the given parameter type and value name into an argument value.
153175
* @param parameter the method parameter to resolve to an argument value
@@ -159,21 +181,6 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
159181
protected abstract Object resolveArgumentInternal(MethodParameter parameter, Message<?> message, String name)
160182
throws Exception;
161183

162-
/**
163-
* Resolves the given default value into an argument value.
164-
*/
165-
private Object resolveDefaultValue(String defaultValue) {
166-
if (this.configurableBeanFactory == null) {
167-
return defaultValue;
168-
}
169-
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue);
170-
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
171-
if (exprResolver == null) {
172-
return defaultValue;
173-
}
174-
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
175-
}
176-
177184
/**
178185
* Invoked when a named value is required, but
179186
* {@link #resolveArgumentInternal(MethodParameter, Message, String)} returned {@code null} and

spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java

+27-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -34,6 +34,7 @@
3434
import org.springframework.messaging.handler.annotation.Header;
3535
import org.springframework.messaging.support.MessageBuilder;
3636
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
37+
import org.springframework.util.ReflectionUtils;
3738

3839
import static org.junit.Assert.*;
3940

@@ -49,7 +50,8 @@ public class HeaderMethodArgumentResolverTests {
4950

5051
private MethodParameter paramRequired;
5152
private MethodParameter paramNamedDefaultValueStringHeader;
52-
private MethodParameter paramSystemProperty;
53+
private MethodParameter paramSystemPropertyDefaultValue;
54+
private MethodParameter paramSystemPropertyName;
5355
private MethodParameter paramNotAnnotated;
5456
private MethodParameter paramNativeHeader;
5557

@@ -61,13 +63,13 @@ public void setup() throws Exception {
6163
cxt.refresh();
6264
this.resolver = new HeaderMethodArgumentResolver(new DefaultConversionService(), cxt.getBeanFactory());
6365

64-
Method method = getClass().getDeclaredMethod("handleMessage",
65-
String.class, String.class, String.class, String.class, String.class);
66+
Method method = ReflectionUtils.findMethod(getClass(), "handleMessage", (Class<?>[]) null);
6667
this.paramRequired = new SynthesizingMethodParameter(method, 0);
6768
this.paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 1);
68-
this.paramSystemProperty = new SynthesizingMethodParameter(method, 2);
69-
this.paramNotAnnotated = new SynthesizingMethodParameter(method, 3);
70-
this.paramNativeHeader = new SynthesizingMethodParameter(method, 4);
69+
this.paramSystemPropertyDefaultValue = new SynthesizingMethodParameter(method, 2);
70+
this.paramSystemPropertyName = new SynthesizingMethodParameter(method, 3);
71+
this.paramNotAnnotated = new SynthesizingMethodParameter(method, 4);
72+
this.paramNativeHeader = new SynthesizingMethodParameter(method, 5);
7173

7274
this.paramRequired.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
7375
GenericTypeResolver.resolveParameterType(this.paramRequired, HeaderMethodArgumentResolver.class);
@@ -84,18 +86,14 @@ public void supportsParameter() {
8486
public void resolveArgument() throws Exception {
8587
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader("param1", "foo").build();
8688
Object result = this.resolver.resolveArgument(this.paramRequired, message);
87-
8889
assertEquals("foo", result);
8990
}
9091

91-
// SPR-11326
92-
93-
@Test
92+
@Test // SPR-11326
9493
public void resolveArgumentNativeHeader() throws Exception {
9594
TestMessageHeaderAccessor headers = new TestMessageHeaderAccessor();
9695
headers.setNativeHeader("param1", "foo");
9796
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
98-
9997
assertEquals("foo", this.resolver.resolveArgument(this.paramRequired, message));
10098
}
10199

@@ -120,7 +118,6 @@ public void resolveArgumentNotFound() throws Exception {
120118
public void resolveArgumentDefaultValue() throws Exception {
121119
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
122120
Object result = this.resolver.resolveArgument(this.paramNamedDefaultValueStringHeader, message);
123-
124121
assertEquals("bar", result);
125122
}
126123

@@ -129,21 +126,34 @@ public void resolveDefaultValueSystemProperty() throws Exception {
129126
System.setProperty("systemProperty", "sysbar");
130127
try {
131128
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
132-
Object result = resolver.resolveArgument(paramSystemProperty, message);
129+
Object result = resolver.resolveArgument(paramSystemPropertyDefaultValue, message);
133130
assertEquals("sysbar", result);
134131
}
135132
finally {
136133
System.clearProperty("systemProperty");
137134
}
138135
}
139136

137+
@Test
138+
public void resolveNameFromSystemProperty() throws Exception {
139+
System.setProperty("systemProperty", "sysbar");
140+
try {
141+
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader("sysbar", "foo").build();
142+
Object result = resolver.resolveArgument(paramSystemPropertyName, message);
143+
assertEquals("foo", result);
144+
}
145+
finally {
146+
System.clearProperty("systemProperty");
147+
}
148+
}
149+
140150

141-
@SuppressWarnings("unused")
142-
private void handleMessage(
151+
public void handleMessage(
143152
@Header String param1,
144153
@Header(name = "name", defaultValue = "bar") String param2,
145154
@Header(name = "name", defaultValue = "#{systemProperties.systemProperty}") String param3,
146-
String param4,
155+
@Header(name = "#{systemProperties.systemProperty}") String param4,
156+
String param5,
147157
@Header("nativeHeaders.param1") String nativeHeaderParam1) {
148158
}
149159

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

+25-18
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,24 @@ public final Object resolveArgument(MethodParameter parameter, ModelAndViewConta
8989
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
9090
MethodParameter nestedParameter = parameter.nestedIfOptional();
9191

92-
Object arg = resolveName(namedValueInfo.name, nestedParameter, webRequest);
92+
Object resolvedName = resolveStringValue(namedValueInfo.name);
93+
if (resolvedName == null) {
94+
throw new IllegalArgumentException(
95+
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
96+
}
97+
98+
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
9399
if (arg == null) {
94100
if (namedValueInfo.defaultValue != null) {
95-
arg = resolveDefaultValue(namedValueInfo.defaultValue);
101+
arg = resolveStringValue(namedValueInfo.defaultValue);
96102
}
97103
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
98104
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
99105
}
100106
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
101107
}
102108
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
103-
arg = resolveDefaultValue(namedValueInfo.defaultValue);
109+
arg = resolveStringValue(namedValueInfo.defaultValue);
104110
}
105111

106112
if (binderFactory != null) {
@@ -162,6 +168,22 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
162168
return new NamedValueInfo(name, info.required, defaultValue);
163169
}
164170

171+
/**
172+
* Resolve the given annotation-specified value,
173+
* potentially containing placeholders and expressions.
174+
*/
175+
private Object resolveStringValue(String value) {
176+
if (this.configurableBeanFactory == null) {
177+
return value;
178+
}
179+
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
180+
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
181+
if (exprResolver == null) {
182+
return value;
183+
}
184+
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
185+
}
186+
165187
/**
166188
* Resolve the given parameter type and value name into an argument value.
167189
* @param name the name of the value being resolved
@@ -174,21 +196,6 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
174196
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
175197
throws Exception;
176198

177-
/**
178-
* Resolve the given default value into an argument value.
179-
*/
180-
private Object resolveDefaultValue(String defaultValue) {
181-
if (this.configurableBeanFactory == null) {
182-
return defaultValue;
183-
}
184-
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue);
185-
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
186-
if (exprResolver == null) {
187-
return defaultValue;
188-
}
189-
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
190-
}
191-
192199
/**
193200
* Invoked when a named value is required, but {@link #resolveName(String, MethodParameter, NativeWebRequest)}
194201
* returned {@code null} and there is no default value. Subclasses typically throw an exception in this case.

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

+38-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -27,6 +27,7 @@
2727
import org.springframework.core.annotation.SynthesizingMethodParameter;
2828
import org.springframework.mock.web.test.MockHttpServletRequest;
2929
import org.springframework.mock.web.test.MockHttpServletResponse;
30+
import org.springframework.util.ReflectionUtils;
3031
import org.springframework.web.bind.ServletRequestBindingException;
3132
import org.springframework.web.bind.annotation.RequestHeader;
3233
import org.springframework.web.context.request.NativeWebRequest;
@@ -50,6 +51,7 @@ public class RequestHeaderMethodArgumentResolverTests {
5051
private MethodParameter paramNamedValueStringArray;
5152
private MethodParameter paramSystemProperty;
5253
private MethodParameter paramContextPath;
54+
private MethodParameter paramResolvedName;
5355
private MethodParameter paramNamedValueMap;
5456

5557
private MockHttpServletRequest servletRequest;
@@ -64,12 +66,13 @@ public void setUp() throws Exception {
6466
context.refresh();
6567
resolver = new RequestHeaderMethodArgumentResolver(context.getBeanFactory());
6668

67-
Method method = getClass().getMethod("params", String.class, String[].class, String.class, String.class, Map.class);
69+
Method method = ReflectionUtils.findMethod(getClass(), "params", (Class<?>[]) null);
6870
paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 0);
6971
paramNamedValueStringArray = new SynthesizingMethodParameter(method, 1);
7072
paramSystemProperty = new SynthesizingMethodParameter(method, 2);
7173
paramContextPath = new SynthesizingMethodParameter(method, 3);
72-
paramNamedValueMap = new SynthesizingMethodParameter(method, 4);
74+
paramResolvedName = new SynthesizingMethodParameter(method, 4);
75+
paramNamedValueMap = new SynthesizingMethodParameter(method, 5);
7376

7477
servletRequest = new MockHttpServletRequest();
7578
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
@@ -97,45 +100,61 @@ public void resolveStringArgument() throws Exception {
97100
servletRequest.addHeader("name", expected);
98101

99102
Object result = resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, webRequest, null);
100-
101103
assertTrue(result instanceof String);
102104
assertEquals("Invalid result", expected, result);
103105
}
104106

105107
@Test
106108
public void resolveStringArrayArgument() throws Exception {
107-
String[] expected = new String[]{"foo", "bar"};
109+
String[] expected = new String[] {"foo", "bar"};
108110
servletRequest.addHeader("name", expected);
109111

110112
Object result = resolver.resolveArgument(paramNamedValueStringArray, null, webRequest, null);
111-
112113
assertTrue(result instanceof String[]);
113114
assertArrayEquals("Invalid result", expected, (String[]) result);
114115
}
115116

116117
@Test
117118
public void resolveDefaultValue() throws Exception {
118119
Object result = resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, webRequest, null);
119-
120120
assertTrue(result instanceof String);
121121
assertEquals("Invalid result", "bar", result);
122122
}
123123

124124
@Test
125125
public void resolveDefaultValueFromSystemProperty() throws Exception {
126126
System.setProperty("systemProperty", "bar");
127-
Object result = resolver.resolveArgument(paramSystemProperty, null, webRequest, null);
128-
System.clearProperty("systemProperty");
127+
try {
128+
Object result = resolver.resolveArgument(paramSystemProperty, null, webRequest, null);
129+
assertTrue(result instanceof String);
130+
assertEquals("bar", result);
131+
}
132+
finally {
133+
System.clearProperty("systemProperty");
134+
}
135+
}
129136

130-
assertTrue(result instanceof String);
131-
assertEquals("bar", result);
137+
@Test
138+
public void resolveNameFromSystemProperty() throws Exception {
139+
String expected = "foo";
140+
servletRequest.addHeader("bar", expected);
141+
142+
System.setProperty("systemProperty", "bar");
143+
try {
144+
Object result = resolver.resolveArgument(paramResolvedName, null, webRequest, null);
145+
assertTrue(result instanceof String);
146+
assertEquals("Invalid result", expected, result);
147+
}
148+
finally {
149+
System.clearProperty("systemProperty");
150+
}
132151
}
133152

134153
@Test
135154
public void resolveDefaultValueFromRequest() throws Exception {
136155
servletRequest.setContextPath("/bar");
137-
Object result = resolver.resolveArgument(paramContextPath, null, webRequest, null);
138156

157+
Object result = resolver.resolveArgument(paramContextPath, null, webRequest, null);
139158
assertTrue(result instanceof String);
140159
assertEquals("/bar", result);
141160
}
@@ -146,11 +165,13 @@ public void notFound() throws Exception {
146165
}
147166

148167

149-
public void params(@RequestHeader(name = "name", defaultValue = "bar") String param1,
150-
@RequestHeader("name") String[] param2,
151-
@RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
152-
@RequestHeader(name = "name", defaultValue="#{request.contextPath}") String param4,
153-
@RequestHeader("name") Map<?, ?> unsupported) {
168+
public void params(
169+
@RequestHeader(name = "name", defaultValue = "bar") String param1,
170+
@RequestHeader("name") String[] param2,
171+
@RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
172+
@RequestHeader(name = "name", defaultValue="#{request.contextPath}") String param4,
173+
@RequestHeader("#{systemProperties.systemProperty}") String param5,
174+
@RequestHeader("name") Map<?, ?> unsupported) {
154175
}
155176

156177
}

0 commit comments

Comments
 (0)