SPR-6760: method called twice if exits via exception in a 'normal' case
This commit is contained in:
parent
e3cdabfaac
commit
4c35697c79
|
@ -90,8 +90,28 @@ public class ConstructorReference extends SpelNodeImpl {
|
||||||
return executorToUse.execute(state.getEvaluationContext(), arguments);
|
return executorToUse.execute(state.getEvaluationContext(), arguments);
|
||||||
}
|
}
|
||||||
catch (AccessException ae) {
|
catch (AccessException ae) {
|
||||||
// this is OK - it may have gone stale due to a class change,
|
// Two reasons this can occur:
|
||||||
// let's try to get a new one and call it before giving up
|
// 1. the method invoked actually threw a real exception
|
||||||
|
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
|
||||||
|
|
||||||
|
// In the first case we should not retry, in the second case we should see if there is a
|
||||||
|
// better suited method.
|
||||||
|
|
||||||
|
// To determine which situation it is, the AccessException will contain a cause - this
|
||||||
|
// will be the exception thrown by the reflective invocation. Inside this exception there
|
||||||
|
// may or may not be a root cause. If there is a root cause it is a user created exception.
|
||||||
|
// If there is no root cause it was a reflective invocation problem.
|
||||||
|
|
||||||
|
Throwable causeOfAccessException = ae.getCause();
|
||||||
|
Throwable rootCause = (causeOfAccessException==null?null:causeOfAccessException.getCause());
|
||||||
|
if (rootCause!=null) {
|
||||||
|
// User exception was the root cause - exit now
|
||||||
|
String typename = (String) children[0].getValueInternal(state).getValue();
|
||||||
|
throw new SpelEvaluationException(getStartPosition(), rootCause, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM,
|
||||||
|
typename,FormatHelper.formatMethodForMessage("", argumentTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point we know it wasn't a user problem so worth a retry if a better candidate can be found
|
||||||
this.cachedExecutor = null;
|
this.cachedExecutor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,8 +77,27 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments);
|
state.getEvaluationContext(), state.getActiveContextObject().getValue(), arguments);
|
||||||
}
|
}
|
||||||
catch (AccessException ae) {
|
catch (AccessException ae) {
|
||||||
// this is OK - it may have gone stale due to a class change,
|
// Two reasons this can occur:
|
||||||
// let's try to get a new one and call it before giving up
|
// 1. the method invoked actually threw a real exception
|
||||||
|
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
|
||||||
|
|
||||||
|
// In the first case we should not retry, in the second case we should see if there is a
|
||||||
|
// better suited method.
|
||||||
|
|
||||||
|
// To determine which situation it is, the AccessException will contain a cause - this
|
||||||
|
// will be the exception thrown by the reflective invocation. Inside this exception there
|
||||||
|
// may or may not be a root cause. If there is a root cause it is a user created exception.
|
||||||
|
// If there is no root cause it was a reflective invocation problem.
|
||||||
|
|
||||||
|
Throwable causeOfAccessException = ae.getCause();
|
||||||
|
Throwable rootCause = (causeOfAccessException==null?null:causeOfAccessException.getCause());
|
||||||
|
if (rootCause!=null) {
|
||||||
|
// User exception was the root cause - exit now
|
||||||
|
throw new SpelEvaluationException( getStartPosition(), rootCause, SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION,
|
||||||
|
this.name, state.getActiveContextObject().getValue().getClass().getName(), rootCause.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point we know it wasn't a user problem so worth a retry if a better candidate can be found
|
||||||
this.cachedExecutor = null;
|
this.cachedExecutor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,13 @@
|
||||||
|
|
||||||
package org.springframework.expression.spel;
|
package org.springframework.expression.spel;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.expression.spel.testresources.PlaceOfBirth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests invocation of constructors.
|
* Tests invocation of constructors.
|
||||||
|
@ -36,6 +41,82 @@ public class ConstructorInvocationTests extends ExpressionTestCase {
|
||||||
evaluateAndCheckError("new FooBar()",SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
|
evaluateAndCheckError("new FooBar()",SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class Tester {
|
||||||
|
public static int counter;
|
||||||
|
public int i;
|
||||||
|
|
||||||
|
public Tester() {}
|
||||||
|
|
||||||
|
public Tester(int i) {
|
||||||
|
counter++;
|
||||||
|
if (i==1) {
|
||||||
|
throw new IllegalArgumentException("IllegalArgumentException for 1");
|
||||||
|
}
|
||||||
|
if (i==2) {
|
||||||
|
throw new RuntimeException("RuntimeException for 2");
|
||||||
|
}
|
||||||
|
this.i = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tester(PlaceOfBirth pob) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void testConstructorThrowingException_SPR6760() {
|
||||||
|
// Test ctor on inventor:
|
||||||
|
// On 1 it will throw an IllegalArgumentException
|
||||||
|
// On 2 it will throw a RuntimeException
|
||||||
|
// On 3 it will exit normally
|
||||||
|
// In each case it increments the Tester field 'counter' when invoked
|
||||||
|
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
Expression expr = parser.parseExpression("new org.springframework.expression.spel.ConstructorInvocationTests$Tester(#bar).i");
|
||||||
|
|
||||||
|
// Normal exit
|
||||||
|
StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
|
||||||
|
eContext.setRootObject(new Tester());
|
||||||
|
eContext.setVariable("bar",3);
|
||||||
|
Object o = expr.getValue(eContext);
|
||||||
|
Assert.assertEquals(o,3);
|
||||||
|
Assert.assertEquals(1,parser.parseExpression("counter").getValue(eContext));
|
||||||
|
|
||||||
|
// Now the expression has cached that throwException(int) is the right thing to call
|
||||||
|
// Let's change 'bar' to be a PlaceOfBirth which indicates the cached reference is
|
||||||
|
// out of date.
|
||||||
|
eContext.setVariable("bar",new PlaceOfBirth("London"));
|
||||||
|
o = expr.getValue(eContext);
|
||||||
|
Assert.assertEquals(0, o);
|
||||||
|
// That confirms the logic to mark the cached reference stale and retry is working
|
||||||
|
|
||||||
|
|
||||||
|
// Now let's cause the method to exit via exception and ensure it doesn't cause
|
||||||
|
// a retry.
|
||||||
|
|
||||||
|
// First, switch back to throwException(int)
|
||||||
|
eContext.setVariable("bar",3);
|
||||||
|
o = expr.getValue(eContext);
|
||||||
|
Assert.assertEquals(3, o);
|
||||||
|
Assert.assertEquals(2,parser.parseExpression("counter").getValue(eContext));
|
||||||
|
|
||||||
|
|
||||||
|
// Now cause it to throw an exception:
|
||||||
|
eContext.setVariable("bar",1);
|
||||||
|
try {
|
||||||
|
o = expr.getValue(eContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// A problem occurred whilst attempting to construct an object of type 'org.springframework.expression.spel.ConstructorInvocationTests$Tester' using arguments '(java.lang.Integer)'
|
||||||
|
int idx = e.getMessage().indexOf("Tester");
|
||||||
|
if (idx==-1) {
|
||||||
|
Assert.fail("Expected reference to Tester in :"+e.getMessage());
|
||||||
|
}
|
||||||
|
// normal
|
||||||
|
}
|
||||||
|
// If counter is 4 then the method got called twice!
|
||||||
|
Assert.assertEquals(3,parser.parseExpression("counter").getValue(eContext));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVarargsInvocation01() {
|
public void testVarargsInvocation01() {
|
||||||
// Calling 'Fruit(String... strings)'
|
// Calling 'Fruit(String... strings)'
|
||||||
|
|
|
@ -16,7 +16,12 @@
|
||||||
|
|
||||||
package org.springframework.expression.spel;
|
package org.springframework.expression.spel;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.expression.spel.testresources.PlaceOfBirth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests invocation of methods.
|
* Tests invocation of methods.
|
||||||
|
@ -70,6 +75,55 @@ public class MethodInvocationTests extends ExpressionTestCase {
|
||||||
evaluate("new String('7.0 foobar').startsWith(7.0d)", true, Boolean.class);
|
evaluate("new String('7.0 foobar').startsWith(7.0d)", true, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMethodThrowingException_SPR6760() {
|
||||||
|
// Test method on inventor: throwException()
|
||||||
|
// On 1 it will throw an IllegalArgumentException
|
||||||
|
// On 2 it will throw a RuntimeException
|
||||||
|
// On 3 it will exit normally
|
||||||
|
// In each case it increments the Inventor field 'counter' when invoked
|
||||||
|
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
Expression expr = parser.parseExpression("throwException(#bar)");
|
||||||
|
|
||||||
|
// Normal exit
|
||||||
|
StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
|
||||||
|
eContext.setVariable("bar",3);
|
||||||
|
Object o = expr.getValue(eContext);
|
||||||
|
Assert.assertEquals(o,3);
|
||||||
|
Assert.assertEquals(1,parser.parseExpression("counter").getValue(eContext));
|
||||||
|
|
||||||
|
// Now the expression has cached that throwException(int) is the right thing to call
|
||||||
|
// Let's change 'bar' to be a PlaceOfBirth which indicates the cached reference is
|
||||||
|
// out of date.
|
||||||
|
eContext.setVariable("bar",new PlaceOfBirth("London"));
|
||||||
|
o = expr.getValue(eContext);
|
||||||
|
Assert.assertEquals("London", o);
|
||||||
|
// That confirms the logic to mark the cached reference stale and retry is working
|
||||||
|
|
||||||
|
|
||||||
|
// Now let's cause the method to exit via exception and ensure it doesn't cause
|
||||||
|
// a retry.
|
||||||
|
|
||||||
|
// First, switch back to throwException(int)
|
||||||
|
eContext.setVariable("bar",3);
|
||||||
|
o = expr.getValue(eContext);
|
||||||
|
Assert.assertEquals(3, o);
|
||||||
|
Assert.assertEquals(2,parser.parseExpression("counter").getValue(eContext));
|
||||||
|
|
||||||
|
|
||||||
|
// Now cause it to throw an exception:
|
||||||
|
eContext.setVariable("bar",1);
|
||||||
|
try {
|
||||||
|
o = expr.getValue(eContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
// normal
|
||||||
|
}
|
||||||
|
// If counter is 4 then the method got called twice!
|
||||||
|
Assert.assertEquals(3,parser.parseExpression("counter").getValue(eContext));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVarargsInvocation01() {
|
public void testVarargsInvocation01() {
|
||||||
// Calling 'public int aVarargsMethod(String... strings)'
|
// Calling 'public int aVarargsMethod(String... strings)'
|
||||||
|
|
|
@ -32,6 +32,7 @@ public class Inventor {
|
||||||
public List<Integer> listOneFive = new ArrayList<Integer>();
|
public List<Integer> listOneFive = new ArrayList<Integer>();
|
||||||
public String[] stringArrayOfThreeItems = new String[]{"1","2","3"};
|
public String[] stringArrayOfThreeItems = new String[]{"1","2","3"};
|
||||||
private String foo;
|
private String foo;
|
||||||
|
public int counter;
|
||||||
|
|
||||||
public Inventor(String name, Date birthdate, String nationality) {
|
public Inventor(String name, Date birthdate, String nationality) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -90,6 +91,21 @@ public class Inventor {
|
||||||
return placeOfBirth;
|
return placeOfBirth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int throwException(int valueIn) {
|
||||||
|
counter++;
|
||||||
|
if (valueIn==1) {
|
||||||
|
throw new IllegalArgumentException("IllegalArgumentException for 1");
|
||||||
|
}
|
||||||
|
if (valueIn==2) {
|
||||||
|
throw new RuntimeException("RuntimeException for 2");
|
||||||
|
}
|
||||||
|
return valueIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String throwException(PlaceOfBirth pob) {
|
||||||
|
return pob.getCity();
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue