diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc index c133af0f367..ca5d9c28d01 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/literal.adoc @@ -9,7 +9,7 @@ SpEL supports the following types of literal expressions. - boolean values: `true` or `false` - null -Strings can delimited by single quotation marks (`'`) or double quotation marks (`"`). To +Strings can be delimited by single quotation marks (`'`) or double quotation marks (`"`). To include a single quotation mark within a string literal enclosed in single quotation marks, use two adjacent single quotation mark characters. Similarly, to include a double quotation mark within a string literal enclosed in double quotation marks, use two diff --git a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java index c7e0fd355c3..5af02af1ee4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/ConstructorExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,32 +16,37 @@ package org.springframework.expression; - -// TODO Is the resolver/executor model too pervasive in this package? /** - * Executors are built by resolvers and can be cached by the infrastructure to repeat an - * operation quickly without going back to the resolvers. For example, the particular - * constructor to run on a class may be discovered by the reflection constructor resolver - * - it will then build a ConstructorExecutor that executes that constructor and the - * ConstructorExecutor can be reused without needing to go back to the resolver to - * discover the constructor again. + * A {@code ConstructorExecutor} is built by a {@link ConstructorResolver} and + * can be cached by the infrastructure to repeat an operation quickly without + * going back to the resolvers. + * + *
For example, the particular constructor to execute on a class may be discovered + * by a {@code ConstructorResolver} which then builds a {@code ConstructorExecutor} + * that executes that constructor, and the resolved {@code ConstructorExecutor} + * can be reused without needing to go back to the resolvers to discover the + * constructor again. * - *
They can become stale, and in that case should throw an AccessException - this will - * cause the infrastructure to go back to the resolvers to ask for a new one. + *
If a {@code ConstructorExecutor} becomes stale, it should throw an + * {@link AccessException} which signals to the infrastructure to go back to the + * resolvers to ask for a new one. * * @author Andy Clement + * @author Sam Brannen * @since 3.0 + * @see ConstructorResolver + * @see MethodExecutor */ public interface ConstructorExecutor { /** * Execute a constructor in the specified context using the specified arguments. - * @param context the evaluation context in which the command is being executed - * @param arguments the arguments to the constructor call, should match (in terms - * of number and type) whatever the command will need to run + * @param context the evaluation context in which the constructor is being executed + * @param arguments the arguments to the constructor; should match (in terms + * of number and type) whatever the constructor will need to run * @return the new object - * @throws AccessException if there is a problem executing the command or the - * CommandExecutor is no longer valid + * @throws AccessException if there is a problem executing the constructor or + * if this {@code ConstructorExecutor} has become stale */ TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException; diff --git a/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java b/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java index 821a46c1bd2..3c9c232c3ee 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/ConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,24 +22,33 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.lang.Nullable; /** - * A constructor resolver attempts to locate a constructor and returns a ConstructorExecutor - * that can be used to invoke that constructor. The ConstructorExecutor will be cached but - * if it 'goes stale' the resolvers will be called again. + * A constructor resolver attempts to locate a constructor and returns a + * {@link ConstructorExecutor} that can be used to invoke that constructor. + * + *
The {@code ConstructorExecutor} will be cached, but if it becomes stale the + * resolvers will be called again. * * @author Andy Clement + * @author Sam Brannen * @since 3.0 + * @see ConstructorExecutor + * @see MethodResolver */ @FunctionalInterface public interface ConstructorResolver { /** - * Within the supplied context determine a suitable constructor on the supplied type - * that can handle the specified arguments. Return a ConstructorExecutor that can be - * used to invoke that constructor (or {@code null} if no constructor could be found). + * Within the supplied context, resolve a suitable constructor on the + * supplied type that can handle the specified arguments. + *
Returns a {@link ConstructorExecutor} that can be used to invoke that
+ * constructor (or {@code null} if no constructor could be found).
* @param context the current evaluation context
- * @param typeName the type upon which to look for the constructor
- * @param argumentTypes the arguments that the constructor must be able to handle
- * @return a ConstructorExecutor that can invoke the constructor, or null if non found
+ * @param typeName the fully-qualified name of the type upon which to look
+ * for the constructor
+ * @param argumentTypes the types of arguments that the constructor must be
+ * able to handle
+ * @return a {@code ConstructorExecutor} that can invoke the constructor,
+ * or {@code null} if the constructor cannot be found
*/
@Nullable
ConstructorExecutor resolve(EvaluationContext context, String typeName, List They can become stale, and in that case should throw an AccessException:
- * This will cause the infrastructure to go back to the resolvers to ask for a new one.
+ * For example, the particular method to execute on an object may be discovered
+ * by a {@code MethodResolver} which then builds a {@code MethodExecutor} that
+ * executes that method, and the resolved {@code MethodExecutor} can be reused
+ * without needing to go back to the resolvers to discover the method again.
+ *
+ * If a {@code MethodExecutor} becomes stale, it should throw an
+ * {@link AccessException} which signals to the infrastructure to go back to the
+ * resolvers to ask for a new one.
*
* @author Andy Clement
+ * @author Sam Brannen
* @since 3.0
+ * @see MethodResolver
+ * @see ConstructorExecutor
*/
public interface MethodExecutor {
/**
- * Execute a command using the specified arguments, and using the specified expression state.
- * @param context the evaluation context in which the command is being executed
- * @param target the target object of the call - null for static methods
- * @param arguments the arguments to the executor, should match (in terms of number
- * and type) whatever the command will need to run
- * @return the value returned from execution
- * @throws AccessException if there is a problem executing the command or the
- * MethodExecutor is no longer valid
+ * Execute a method in the specified context using the specified arguments.
+ * @param context the evaluation context in which the method is being executed
+ * @param target the target of the method invocation; may be {@code null} for
+ * {@code static} methods
+ * @param arguments the arguments to the method; should match (in terms of
+ * number and type) whatever the method will need to run
+ * @return the value returned from the method
+ * @throws AccessException if there is a problem executing the method or
+ * if this {@code MethodExecutor} has become stale
*/
TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException;
diff --git a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java
index d4216d2a55c..9d8c0bbaf06 100644
--- a/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java
+++ b/spring-expression/src/main/java/org/springframework/expression/MethodResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,23 +22,32 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
- * A method resolver attempts to locate a method and returns a command executor that can be
- * used to invoke that method. The command executor will be cached, but if it 'goes stale'
- * the resolvers will be called again.
+ * A method resolver attempts to locate a method and returns a
+ * {@link MethodExecutor} that can be used to invoke that method.
+ *
+ * The {@code MethodExecutor} will be cached, but if it becomes stale the
+ * resolvers will be called again.
*
* @author Andy Clement
+ * @author Sam Brannen
* @since 3.0
+ * @see MethodExecutor
+ * @see ConstructorResolver
*/
public interface MethodResolver {
/**
- * Within the supplied context determine a suitable method on the supplied object that
- * can handle the specified arguments. Return a {@link MethodExecutor} that can be used
- * to invoke that method, or {@code null} if no method could be found.
+ * Within the supplied context, resolve a suitable method on the supplied
+ * object that can handle the specified arguments.
+ * Returns a {@link MethodExecutor} that can be used to invoke that method,
+ * or {@code null} if no method could be found.
* @param context the current evaluation context
* @param targetObject the object upon which the method is being called
- * @param argumentTypes the arguments that the constructor must be able to handle
- * @return a MethodExecutor that can invoke the method, or null if the method cannot be found
+ * @param name the name of the method
+ * @param argumentTypes the types of arguments that the method must be able
+ * to handle
+ * @return a {@code MethodExecutor} that can invoke the method, or {@code null}
+ * if the method cannot be found
*/
@Nullable
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java
index 72aa836588a..abc80b03700 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java
@@ -21,13 +21,12 @@ import java.util.List;
import org.junit.jupiter.api.Test;
-import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.ConstructorResolver;
-import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.expression.spel.support.StandardTypeLocator;
+import org.springframework.expression.spel.testresources.Fruit;
import org.springframework.expression.spel.testresources.PlaceOfBirth;
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,53 +40,17 @@ import static org.assertj.core.api.Assertions.assertThatException;
class ConstructorInvocationTests extends AbstractExpressionTests {
@Test
- void testTypeConstructors() {
+ void constructorWithArgument() {
evaluate("new String('hello world')", "hello world", String.class);
}
@Test
- void testNonExistentType() {
+ void nonExistentType() {
evaluateAndCheckError("new FooBar()", SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
}
-
- @SuppressWarnings("serial")
- static class TestException extends Exception {
-
- }
-
- static class Tester {
-
- public static int counter;
- public int i;
-
-
- public Tester() {
- }
-
- public Tester(int i) throws Exception {
- counter++;
- if (i == 1) {
- throw new IllegalArgumentException("IllegalArgumentException for 1");
- }
- if (i == 2) {
- throw new RuntimeException("RuntimeException for 2");
- }
- if (i == 4) {
- throw new TestException();
- }
- this.i = i;
- }
-
- public Tester(PlaceOfBirth pob) {
-
- }
-
- }
-
-
@Test
- void testConstructorThrowingException_SPR6760() {
+ void constructorThrowingException() {
// Test ctor on inventor:
// On 1 it will throw an IllegalArgumentException
// On 2 it will throw a RuntimeException
@@ -98,18 +61,18 @@ class ConstructorInvocationTests extends AbstractExpressionTests {
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);
+ StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
+ context.setRootObject(new Tester());
+ context.setVariable("bar", 3);
+ Object o = expr.getValue(context);
assertThat(o).isEqualTo(3);
- assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(1);
+ assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(1);
// 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);
+ context.setVariable("bar", new PlaceOfBirth("London"));
+ o = expr.getValue(context);
assertThat(o).isEqualTo(0);
// That confirms the logic to mark the cached reference stale and retry is working
@@ -117,46 +80,48 @@ class ConstructorInvocationTests extends AbstractExpressionTests {
// a retry.
// First, switch back to throwException(int)
- eContext.setVariable("bar", 3);
- o = expr.getValue(eContext);
+ context.setVariable("bar", 3);
+ o = expr.getValue(context);
assertThat(o).isEqualTo(3);
- assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(2);
+ assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(2);
// 4 will make it throw a checked exception - this will be wrapped by spel on the
// way out
- eContext.setVariable("bar", 4);
+ context.setVariable("bar", 4);
assertThatException()
- .isThrownBy(() -> expr.getValue(eContext))
+ .isThrownBy(() -> expr.getValue(context))
.withMessageContaining("Tester");
// A problem occurred whilst attempting to construct an object of type
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
// using arguments '(java.lang.Integer)'
// If counter is 4 then the method got called twice!
- assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(3);
+ assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(3);
// 1 will make it throw a RuntimeException - SpEL will let this through
- eContext.setVariable("bar", 1);
+ context.setVariable("bar", 1);
assertThatException()
- .isThrownBy(() -> expr.getValue(eContext))
+ .isThrownBy(() -> expr.getValue(context))
.isNotInstanceOf(SpelEvaluationException.class);
// A problem occurred whilst attempting to construct an object of type
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
// using arguments '(java.lang.Integer)'
// If counter is 5 then the method got called twice!
- assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(4);
+ assertThat(parser.parseExpression("counter").getValue(context)).isEqualTo(4);
}
@Test
- void testAddingConstructorResolvers() {
+ void constructorResolvers() {
StandardEvaluationContext ctx = new StandardEvaluationContext();
// reflective constructor accessor is the only one by default
List