Skip to content

Commit fae503d

Browse files
committed
MethodBasedEvaluationContext reliably exposes varargs
Issue: SPR-14554
1 parent 106bda7 commit fae503d

File tree

3 files changed

+83
-49
lines changed

3 files changed

+83
-49
lines changed

spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
package org.springframework.cache.interceptor;
1818

1919
import java.lang.reflect.Method;
20-
import java.util.ArrayList;
21-
import java.util.List;
20+
import java.util.HashSet;
21+
import java.util.Set;
2222

2323
import org.springframework.context.expression.MethodBasedEvaluationContext;
2424
import org.springframework.core.ParameterNameDiscoverer;
@@ -38,25 +38,27 @@
3838
*
3939
* @author Costin Leau
4040
* @author Stephane Nicoll
41+
* @author Juergen Hoeller
4142
* @since 3.1
4243
*/
4344
class CacheEvaluationContext extends MethodBasedEvaluationContext {
4445

45-
private final List<String> unavailableVariables;
46+
private final Set<String> unavailableVariables = new HashSet<>(1);
4647

47-
CacheEvaluationContext(Object rootObject, Method method, Object[] args,
48-
ParameterNameDiscoverer paramDiscoverer) {
4948

50-
super(rootObject, method, args, paramDiscoverer);
51-
this.unavailableVariables = new ArrayList<>();
49+
CacheEvaluationContext(Object rootObject, Method method, Object[] arguments,
50+
ParameterNameDiscoverer parameterNameDiscoverer) {
51+
52+
super(rootObject, method, arguments, parameterNameDiscoverer);
5253
}
5354

55+
5456
/**
55-
* Add the specified variable name as unavailable for that context. Any expression trying
56-
* to access this variable should lead to an exception.
57-
* <p>This permits the validation of expressions that could potentially a variable even
58-
* when such variable isn't available yet. Any expression trying to use that variable should
59-
* therefore fail to evaluate.
57+
* Add the specified variable name as unavailable for that context.
58+
* Any expression trying to access this variable should lead to an exception.
59+
* <p>This permits the validation of expressions that could potentially a
60+
* variable even when such variable isn't available yet. Any expression
61+
* trying to use that variable should therefore fail to evaluate.
6062
*/
6163
public void addUnavailableVariable(String name) {
6264
this.unavailableVariables.add(name);

spring-context/src/main/java/org/springframework/context/expression/MethodBasedEvaluationContext.java

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.context.expression;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.Arrays;
2021

2122
import org.springframework.core.ParameterNameDiscoverer;
2223
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -34,27 +35,27 @@
3435
* </ol>
3536
*
3637
* @author Stephane Nicoll
37-
* @author Sergey Podgurskiy
38+
* @author Juergen Hoeller
3839
* @since 4.2
3940
*/
4041
public class MethodBasedEvaluationContext extends StandardEvaluationContext {
4142

4243
private final Method method;
4344

44-
private final Object[] args;
45+
private final Object[] arguments;
4546

46-
private final ParameterNameDiscoverer paramDiscoverer;
47+
private final ParameterNameDiscoverer parameterNameDiscoverer;
4748

48-
private boolean paramLoaded = false;
49+
private boolean argumentsLoaded = false;
4950

5051

51-
public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] args,
52-
ParameterNameDiscoverer paramDiscoverer) {
52+
public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] arguments,
53+
ParameterNameDiscoverer parameterNameDiscoverer) {
5354

5455
super(rootObject);
5556
this.method = method;
56-
this.args = args;
57-
this.paramDiscoverer = paramDiscoverer;
57+
this.arguments = arguments;
58+
this.parameterNameDiscoverer = parameterNameDiscoverer;
5859
}
5960

6061

@@ -64,9 +65,9 @@ public Object lookupVariable(String name) {
6465
if (variable != null) {
6566
return variable;
6667
}
67-
if (!this.paramLoaded) {
68+
if (!this.argumentsLoaded) {
6869
lazyLoadArguments();
69-
this.paramLoaded = true;
70+
this.argumentsLoaded = true;
7071
variable = super.lookupVariable(name);
7172
}
7273
return variable;
@@ -76,22 +77,30 @@ public Object lookupVariable(String name) {
7677
* Load the param information only when needed.
7778
*/
7879
protected void lazyLoadArguments() {
79-
// shortcut if no args need to be loaded
80-
if (ObjectUtils.isEmpty(this.args)) {
80+
// Shortcut if no args need to be loaded
81+
if (ObjectUtils.isEmpty(this.arguments)) {
8182
return;
8283
}
8384

84-
// save arguments as indexed variables
85-
for (int i = 0; i < this.args.length; i++) {
86-
setVariable("a" + i, this.args[i]);
87-
setVariable("p" + i, this.args[i]);
88-
}
85+
// Expose indexed variables as well as parameter names (if discoverable)
86+
String[] paramNames = this.parameterNameDiscoverer.getParameterNames(this.method);
87+
int paramCount = (paramNames != null ? paramNames.length : this.method.getParameterCount());
88+
int argsCount = this.arguments.length;
8989

90-
String[] parameterNames = this.paramDiscoverer.getParameterNames(this.method);
91-
// save parameter names (if discovered)
92-
if (parameterNames != null) {
93-
for (int i = 0; i < this.args.length; i++) {
94-
setVariable(parameterNames[i], this.args[i]);
90+
for (int i = 0; i < paramCount; i++) {
91+
Object value = null;
92+
if (argsCount > paramCount && i == paramCount - 1) {
93+
// Expose remaining arguments as vararg array for last parameter
94+
value = Arrays.copyOfRange(this.arguments, i, argsCount);
95+
}
96+
else if (argsCount > i) {
97+
// Actual argument found - otherwise left as null
98+
value = this.arguments[i];
99+
}
100+
setVariable("a" + i, value);
101+
setVariable("p" + i, value);
102+
if (paramNames != null) {
103+
setVariable(paramNames[i], value);
95104
}
96105
}
97106
}

spring-context/src/test/java/org/springframework/context/expression/MethodBasedEvaluationContextTests.java

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@
3030
* Unit tests for {@link MethodBasedEvaluationContext}.
3131
*
3232
* @author Stephane Nicoll
33+
* @author Juergen Hoeller
3334
* @author Sergey Podgurskiy
3435
*/
3536
public class MethodBasedEvaluationContextTests {
3637

3738
private final ParameterNameDiscoverer paramDiscover = new DefaultParameterNameDiscoverer();
3839

40+
3941
@Test
4042
public void simpleArguments() {
41-
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello",
42-
String.class, Boolean.class);
43-
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {"test", true});
43+
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", String.class, Boolean.class);
44+
MethodBasedEvaluationContext context = createEvaluationContext(method, "test", true);
4445

4546
assertEquals("test", context.lookupVariable("a0"));
4647
assertEquals("test", context.lookupVariable("p0"));
@@ -51,56 +52,80 @@ public void simpleArguments() {
5152
assertEquals(true, context.lookupVariable("flag"));
5253

5354
assertNull(context.lookupVariable("a2"));
55+
assertNull(context.lookupVariable("p2"));
5456
}
5557

5658
@Test
5759
public void nullArgument() {
58-
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello",
59-
String.class, Boolean.class);
60-
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null});
60+
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", String.class, Boolean.class);
61+
MethodBasedEvaluationContext context = createEvaluationContext(method, null, null);
6162

6263
assertNull(context.lookupVariable("a0"));
6364
assertNull(context.lookupVariable("p0"));
65+
assertNull(context.lookupVariable("foo"));
66+
67+
assertNull(context.lookupVariable("a1"));
68+
assertNull(context.lookupVariable("p1"));
69+
assertNull(context.lookupVariable("flag"));
6470
}
6571

6672
@Test
6773
public void varArgEmpty() {
6874
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
6975
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null});
7076

77+
assertNull(context.lookupVariable("a0"));
7178
assertNull(context.lookupVariable("p0"));
79+
assertNull(context.lookupVariable("flag"));
80+
81+
assertNull(context.lookupVariable("a1"));
7282
assertNull(context.lookupVariable("p1"));
83+
assertNull(context.lookupVariable("vararg"));
7384
}
7485

7586
@Test
7687
public void varArgNull() {
7788
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
78-
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null});
89+
MethodBasedEvaluationContext context = createEvaluationContext(method, null, null);
7990

91+
assertNull(context.lookupVariable("a0"));
8092
assertNull(context.lookupVariable("p0"));
93+
assertNull(context.lookupVariable("flag"));
94+
95+
assertNull(context.lookupVariable("a1"));
8196
assertNull(context.lookupVariable("p1"));
97+
assertNull(context.lookupVariable("vararg"));
8298
}
8399

84100
@Test
85101
public void varArgSingle() {
86102
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
87-
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, "hello"});
103+
MethodBasedEvaluationContext context = createEvaluationContext(method, null, "hello");
88104

105+
assertNull(context.lookupVariable("a0"));
89106
assertNull(context.lookupVariable("p0"));
107+
assertNull(context.lookupVariable("flag"));
108+
109+
assertEquals("hello", context.lookupVariable("a1"));
90110
assertEquals("hello", context.lookupVariable("p1"));
111+
assertEquals("hello", context.lookupVariable("vararg"));
91112
}
92113

93114
@Test
94115
public void varArgMultiple() {
95116
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
96-
MethodBasedEvaluationContext context = createEvaluationContext(method,
97-
new Object[] {null, new String[]{"hello", "hi"}});
117+
MethodBasedEvaluationContext context = createEvaluationContext(method, null, "hello", "hi");
98118

119+
assertNull(context.lookupVariable("a0"));
99120
assertNull(context.lookupVariable("p0"));
100-
assertArrayEquals(new String[]{"hello", "hi"}, (String[]) context.lookupVariable("p1"));
121+
assertNull(context.lookupVariable("flag"));
122+
123+
assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("a1"));
124+
assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("p1"));
125+
assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("vararg"));
101126
}
102127

103-
private MethodBasedEvaluationContext createEvaluationContext(Method method, Object[] args) {
128+
private MethodBasedEvaluationContext createEvaluationContext(Method method, Object... args) {
104129
return new MethodBasedEvaluationContext(this, method, args, this.paramDiscover);
105130
}
106131

@@ -112,9 +137,7 @@ private void hello(String foo, Boolean flag) {
112137
}
113138

114139
private void hello(Boolean flag, String... vararg){
115-
116140
}
117-
118141
}
119142

120-
}
143+
}

0 commit comments

Comments
 (0)