1
1
/*
2
- * Copyright 2002-2023 the original author or authors.
2
+ * Copyright 2002-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
39
39
import org .springframework .util .ReflectionUtils ;
40
40
41
41
/**
42
- * A function reference is of the form "#someFunction(a,b,c)". Functions may be defined
43
- * in the context prior to the expression being evaluated. Functions may also be static
44
- * Java methods, registered in the context prior to invocation of the expression.
42
+ * A function reference is of the form "#someFunction(a,b,c)".
45
43
*
46
- * <p>Functions are very simplistic. The arguments are not part of the definition
47
- * (right now), so the names must be unique.
44
+ * <p>Functions can be either a {@link Method} (for static Java methods) or a
45
+ * {@link MethodHandle} and must be registered in the context prior to evaluation
46
+ * of the expression. See the {@code registerFunction()} methods in
47
+ * {@link org.springframework.expression.spel.support.StandardEvaluationContext}
48
+ * for details.
48
49
*
49
50
* @author Andy Clement
50
51
* @author Juergen Hoeller
52
+ * @author Simon Baslé
53
+ * @author Sam Brannen
51
54
* @since 3.0
55
+ * @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction(String, Method)
56
+ * @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction(String, MethodHandle)
52
57
*/
53
58
public class FunctionReference extends SpelNodeImpl {
54
59
@@ -72,39 +77,44 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
72
77
if (value == TypedValue .NULL ) {
73
78
throw new SpelEvaluationException (getStartPosition (), SpelMessage .FUNCTION_NOT_DEFINED , this .name );
74
79
}
75
- Object resolvedValue = value .getValue ();
76
- if (resolvedValue instanceof MethodHandle methodHandle ) {
80
+ Object function = value .getValue ();
81
+
82
+ // Static Java method registered via a Method.
83
+ // Note: "javaMethod" cannot be named "method" due to a bug in Checkstyle.
84
+ if (function instanceof Method javaMethod ) {
77
85
try {
78
- return executeFunctionBoundMethodHandle (state , methodHandle );
86
+ return executeFunctionViaMethod (state , javaMethod );
79
87
}
80
88
catch (SpelEvaluationException ex ) {
81
89
ex .setPosition (getStartPosition ());
82
90
throw ex ;
83
91
}
84
92
}
85
- if (!(resolvedValue instanceof Method function )) {
86
- // Possibly a static Java method registered as a function
87
- throw new SpelEvaluationException (
88
- SpelMessage .FUNCTION_REFERENCE_CANNOT_BE_INVOKED , this .name , value .getClass ());
89
- }
90
93
91
- try {
92
- return executeFunctionJLRMethod (state , function );
93
- }
94
- catch (SpelEvaluationException ex ) {
95
- ex .setPosition (getStartPosition ());
96
- throw ex ;
94
+ // Function registered via a MethodHandle.
95
+ if (function instanceof MethodHandle methodHandle ) {
96
+ try {
97
+ return executeFunctionViaMethodHandle (state , methodHandle );
98
+ }
99
+ catch (SpelEvaluationException ex ) {
100
+ ex .setPosition (getStartPosition ());
101
+ throw ex ;
102
+ }
97
103
}
104
+
105
+ // Neither a Method nor a MethodHandle?
106
+ throw new SpelEvaluationException (
107
+ SpelMessage .FUNCTION_REFERENCE_CANNOT_BE_INVOKED , this .name , value .getClass ());
98
108
}
99
109
100
110
/**
101
- * Execute a function represented as a {@code java.lang.reflect. Method}.
111
+ * Execute a function represented as a {@link Method}.
102
112
* @param state the expression evaluation state
103
113
* @param method the method to invoke
104
114
* @return the return value of the invoked Java method
105
115
* @throws EvaluationException if there is any problem invoking the method
106
116
*/
107
- private TypedValue executeFunctionJLRMethod (ExpressionState state , Method method ) throws EvaluationException {
117
+ private TypedValue executeFunctionViaMethod (ExpressionState state , Method method ) throws EvaluationException {
108
118
Object [] functionArgs = getArguments (state );
109
119
110
120
if (!method .isVarArgs ()) {
@@ -151,35 +161,33 @@ private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method
151
161
}
152
162
153
163
/**
154
- * Execute a function represented as {@code java.lang.invoke. MethodHandle}.
155
- * Method types that take no arguments (fully bound handles or static methods
156
- * with no parameters) can use {@code #invoke()} which is the most efficient.
157
- * Otherwise, {@code #invokeWithArguments)} is used.
164
+ * Execute a function represented as {@link MethodHandle}.
165
+ * <p> Method types that take no arguments (fully bound handles or static methods
166
+ * with no parameters) can use {@link MethodHandle #invoke()} which is the most
167
+ * efficient. Otherwise, {@link MethodHandle #invokeWithArguments( )} is used.
158
168
* @param state the expression evaluation state
159
- * @param methodHandle the method to invoke
169
+ * @param methodHandle the method handle to invoke
160
170
* @return the return value of the invoked Java method
161
171
* @throws EvaluationException if there is any problem invoking the method
162
172
* @since 6.1
163
173
*/
164
- private TypedValue executeFunctionBoundMethodHandle (ExpressionState state , MethodHandle methodHandle ) throws EvaluationException {
174
+ private TypedValue executeFunctionViaMethodHandle (ExpressionState state , MethodHandle methodHandle ) throws EvaluationException {
165
175
Object [] functionArgs = getArguments (state );
166
176
MethodType declaredParams = methodHandle .type ();
167
177
int spelParamCount = functionArgs .length ;
168
178
int declaredParamCount = declaredParams .parameterCount ();
169
179
170
180
boolean isSuspectedVarargs = declaredParams .lastParameterType ().isArray ();
171
181
172
- if (spelParamCount < declaredParamCount || (spelParamCount > declaredParamCount
173
- && ! isSuspectedVarargs )) {
174
- //incorrect number, including more arguments and not a vararg
182
+ if (spelParamCount < declaredParamCount || (spelParamCount > declaredParamCount && ! isSuspectedVarargs )) {
183
+ // incorrect number, including more arguments and not a vararg
184
+ // perhaps a subset of arguments was provided but the MethodHandle wasn't bound?
175
185
throw new SpelEvaluationException (SpelMessage .INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION ,
176
186
functionArgs .length , declaredParamCount );
177
- //perhaps a subset of arguments was provided but the MethodHandle wasn't bound?
178
187
}
179
188
180
189
// simplest case: the MethodHandle is fully bound or represents a static method with no params:
181
190
if (declaredParamCount == 0 ) {
182
- //note we consider MethodHandles not compilable
183
191
try {
184
192
return new TypedValue (methodHandle .invoke ());
185
193
}
@@ -188,6 +196,7 @@ private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, Metho
188
196
this .name , ex .getMessage ());
189
197
}
190
198
finally {
199
+ // Note: we consider MethodHandles not compilable
191
200
this .exitTypeDescriptor = null ;
192
201
this .method = null ;
193
202
}
@@ -202,12 +211,11 @@ private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, Metho
202
211
ReflectionHelper .convertAllMethodHandleArguments (converter , functionArgs , methodHandle , varArgPosition );
203
212
204
213
if (isSuspectedVarargs && declaredParamCount == 1 ) {
205
- //we only repack the varargs if it is the ONLY argument
214
+ // we only repack the varargs if it is the ONLY argument
206
215
functionArgs = ReflectionHelper .setupArgumentsForVarargsInvocation (
207
216
methodHandle .type ().parameterArray (), functionArgs );
208
217
}
209
218
210
- //note we consider MethodHandles not compilable
211
219
try {
212
220
return new TypedValue (methodHandle .invokeWithArguments (functionArgs ));
213
221
}
@@ -216,6 +224,7 @@ private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, Metho
216
224
this .name , ex .getMessage ());
217
225
}
218
226
finally {
227
+ // Note: we consider MethodHandles not compilable
219
228
this .exitTypeDescriptor = null ;
220
229
this .method = null ;
221
230
}
0 commit comments