MethodBasedEvaluationContext reliably exposes varargs

Issue: SPR-14554
(cherry picked from commit fae503d)
This commit is contained in:
Juergen Hoeller 2016-08-09 21:24:38 +02:00
parent aade2d1ec9
commit 4543a28e51
3 changed files with 84 additions and 50 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,8 +17,8 @@
package org.springframework.cache.interceptor; package org.springframework.cache.interceptor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List; import java.util.Set;
import org.springframework.context.expression.MethodBasedEvaluationContext; import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
@ -38,25 +38,27 @@ import org.springframework.core.ParameterNameDiscoverer;
* *
* @author Costin Leau * @author Costin Leau
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Juergen Hoeller
* @since 3.1 * @since 3.1
*/ */
class CacheEvaluationContext extends MethodBasedEvaluationContext { class CacheEvaluationContext extends MethodBasedEvaluationContext {
private final List<String> unavailableVariables; private final Set<String> unavailableVariables = new HashSet<String>(1);
CacheEvaluationContext(Object rootObject, Method method, Object[] args,
ParameterNameDiscoverer paramDiscoverer) {
super(rootObject, method, args, paramDiscoverer); CacheEvaluationContext(Object rootObject, Method method, Object[] arguments,
this.unavailableVariables = new ArrayList<String>(); ParameterNameDiscoverer parameterNameDiscoverer) {
super(rootObject, method, arguments, parameterNameDiscoverer);
} }
/** /**
* Add the specified variable name as unavailable for that context. Any expression trying * Add the specified variable name as unavailable for that context.
* to access this variable should lead to an exception. * Any expression trying to access this variable should lead to an exception.
* <p>This permits the validation of expressions that could potentially a variable even * <p>This permits the validation of expressions that could potentially a
* when such variable isn't available yet. Any expression trying to use that variable should * variable even when such variable isn't available yet. Any expression
* therefore fail to evaluate. * trying to use that variable should therefore fail to evaluate.
*/ */
public void addUnavailableVariable(String name) { public void addUnavailableVariable(String name) {
this.unavailableVariables.add(name); this.unavailableVariables.add(name);

View File

@ -17,6 +17,7 @@
package org.springframework.context.expression; package org.springframework.context.expression;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
@ -34,27 +35,27 @@ import org.springframework.util.ObjectUtils;
* </ol> * </ol>
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sergey Podgurskiy * @author Juergen Hoeller
* @since 4.2 * @since 4.2
*/ */
public class MethodBasedEvaluationContext extends StandardEvaluationContext { public class MethodBasedEvaluationContext extends StandardEvaluationContext {
private final Method method; private final Method method;
private final Object[] args; private final Object[] arguments;
private final ParameterNameDiscoverer paramDiscoverer; private final ParameterNameDiscoverer parameterNameDiscoverer;
private boolean paramLoaded = false; private boolean argumentsLoaded = false;
public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] args, public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] arguments,
ParameterNameDiscoverer paramDiscoverer) { ParameterNameDiscoverer parameterNameDiscoverer) {
super(rootObject); super(rootObject);
this.method = method; this.method = method;
this.args = args; this.arguments = arguments;
this.paramDiscoverer = paramDiscoverer; this.parameterNameDiscoverer = parameterNameDiscoverer;
} }
@ -64,9 +65,9 @@ public class MethodBasedEvaluationContext extends StandardEvaluationContext {
if (variable != null) { if (variable != null) {
return variable; return variable;
} }
if (!this.paramLoaded) { if (!this.argumentsLoaded) {
lazyLoadArguments(); lazyLoadArguments();
this.paramLoaded = true; this.argumentsLoaded = true;
variable = super.lookupVariable(name); variable = super.lookupVariable(name);
} }
return variable; return variable;
@ -76,22 +77,30 @@ public class MethodBasedEvaluationContext extends StandardEvaluationContext {
* Load the param information only when needed. * Load the param information only when needed.
*/ */
protected void lazyLoadArguments() { protected void lazyLoadArguments() {
// shortcut if no args need to be loaded // Shortcut if no args need to be loaded
if (ObjectUtils.isEmpty(this.args)) { if (ObjectUtils.isEmpty(this.arguments)) {
return; return;
} }
// save arguments as indexed variables // Expose indexed variables as well as parameter names (if discoverable)
for (int i = 0; i < this.args.length; i++) { String[] paramNames = this.parameterNameDiscoverer.getParameterNames(this.method);
setVariable("a" + i, this.args[i]); int paramCount = (paramNames != null ? paramNames.length : this.method.getParameterTypes().length);
setVariable("p" + i, this.args[i]); int argsCount = this.arguments.length;
}
String[] parameterNames = this.paramDiscoverer.getParameterNames(this.method); for (int i = 0; i < paramCount; i++) {
// save parameter names (if discovered) Object value = null;
if (parameterNames != null) { if (argsCount > paramCount && i == paramCount - 1) {
for (int i = 0; i < this.args.length; i++) { // Expose remaining arguments as vararg array for last parameter
setVariable(parameterNames[i], this.args[i]); value = Arrays.copyOfRange(this.arguments, i, argsCount);
}
else if (argsCount > i) {
// Actual argument found - otherwise left as null
value = this.arguments[i];
}
setVariable("a" + i, value);
setVariable("p" + i, value);
if (paramNames != null) {
setVariable(paramNames[i], value);
} }
} }
} }

View File

@ -30,17 +30,18 @@ import static org.junit.Assert.*;
* Unit tests for {@link MethodBasedEvaluationContext}. * Unit tests for {@link MethodBasedEvaluationContext}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Juergen Hoeller
* @author Sergey Podgurskiy * @author Sergey Podgurskiy
*/ */
public class MethodBasedEvaluationContextTests { public class MethodBasedEvaluationContextTests {
private final ParameterNameDiscoverer paramDiscover = new DefaultParameterNameDiscoverer(); private final ParameterNameDiscoverer paramDiscover = new DefaultParameterNameDiscoverer();
@Test @Test
public void simpleArguments() { public void simpleArguments() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", String.class, Boolean.class);
String.class, Boolean.class); MethodBasedEvaluationContext context = createEvaluationContext(method, "test", true);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {"test", true});
assertEquals("test", context.lookupVariable("a0")); assertEquals("test", context.lookupVariable("a0"));
assertEquals("test", context.lookupVariable("p0")); assertEquals("test", context.lookupVariable("p0"));
@ -51,16 +52,21 @@ public class MethodBasedEvaluationContextTests {
assertEquals(true, context.lookupVariable("flag")); assertEquals(true, context.lookupVariable("flag"));
assertNull(context.lookupVariable("a2")); assertNull(context.lookupVariable("a2"));
assertNull(context.lookupVariable("p2"));
} }
@Test @Test
public void nullArgument() { public void nullArgument() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", String.class, Boolean.class);
String.class, Boolean.class); MethodBasedEvaluationContext context = createEvaluationContext(method, null, null);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null});
assertNull(context.lookupVariable("a0")); assertNull(context.lookupVariable("a0"));
assertNull(context.lookupVariable("p0")); assertNull(context.lookupVariable("p0"));
assertNull(context.lookupVariable("foo"));
assertNull(context.lookupVariable("a1"));
assertNull(context.lookupVariable("p1"));
assertNull(context.lookupVariable("flag"));
} }
@Test @Test
@ -68,39 +74,58 @@ public class MethodBasedEvaluationContextTests {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null}); MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null});
assertNull(context.lookupVariable("a0"));
assertNull(context.lookupVariable("p0")); assertNull(context.lookupVariable("p0"));
assertNull(context.lookupVariable("flag"));
assertNull(context.lookupVariable("a1"));
assertNull(context.lookupVariable("p1")); assertNull(context.lookupVariable("p1"));
assertNull(context.lookupVariable("vararg"));
} }
@Test @Test
public void varArgNull() { public void varArgNull() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null}); MethodBasedEvaluationContext context = createEvaluationContext(method, null, null);
assertNull(context.lookupVariable("a0"));
assertNull(context.lookupVariable("p0")); assertNull(context.lookupVariable("p0"));
assertNull(context.lookupVariable("flag"));
assertNull(context.lookupVariable("a1"));
assertNull(context.lookupVariable("p1")); assertNull(context.lookupVariable("p1"));
assertNull(context.lookupVariable("vararg"));
} }
@Test @Test
public void varArgSingle() { public void varArgSingle() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, "hello"}); MethodBasedEvaluationContext context = createEvaluationContext(method, null, "hello");
assertNull(context.lookupVariable("a0"));
assertNull(context.lookupVariable("p0")); assertNull(context.lookupVariable("p0"));
assertNull(context.lookupVariable("flag"));
assertEquals("hello", context.lookupVariable("a1"));
assertEquals("hello", context.lookupVariable("p1")); assertEquals("hello", context.lookupVariable("p1"));
assertEquals("hello", context.lookupVariable("vararg"));
} }
@Test @Test
public void varArgMultiple() { public void varArgMultiple() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class); Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello", Boolean.class, String[].class);
MethodBasedEvaluationContext context = createEvaluationContext(method, MethodBasedEvaluationContext context = createEvaluationContext(method, null, "hello", "hi");
new Object[] {null, new String[]{"hello", "hi"}});
assertNull(context.lookupVariable("a0"));
assertNull(context.lookupVariable("p0")); assertNull(context.lookupVariable("p0"));
assertArrayEquals(new String[]{"hello", "hi"}, (String[]) context.lookupVariable("p1")); assertNull(context.lookupVariable("flag"));
assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("a1"));
assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("p1"));
assertArrayEquals(new Object[] {"hello", "hi"}, (Object[]) context.lookupVariable("vararg"));
} }
private MethodBasedEvaluationContext createEvaluationContext(Method method, Object[] args) { private MethodBasedEvaluationContext createEvaluationContext(Method method, Object... args) {
return new MethodBasedEvaluationContext(this, method, args, this.paramDiscover); return new MethodBasedEvaluationContext(this, method, args, this.paramDiscover);
} }
@ -112,9 +137,7 @@ public class MethodBasedEvaluationContextTests {
} }
private void hello(Boolean flag, String... vararg){ private void hello(Boolean flag, String... vararg){
} }
} }
} }