函数

你可以通过注册用户定义的函数来扩展 SpEL,这些函数可以使用 #functionName(…​) 语法在表达式中调用。 函数可以通过 setVariable() 方法注册为 EvaluationContext 实现中的变量。

StandardEvaluationContext 还定义了 registerFunction(…​) 方法, 提供了一种方便的方式来将函数注册为 java.lang.reflect.Methodjava.lang.invoke.MethodHandle

由于函数与评估上下文中的 变量 共享一个公共命名空间, 因此必须注意确保函数名称和变量名称不重叠。

下例展示了如何注册一个用户定义的函数,以便使用 java.lang.reflect.Method 通过反射调用:

  • Java

Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);

例如,考虑以下反转字符串的实用方法:

  • Java

public abstract class StringUtils {

  public static String reverseString(String input) {
    return new StringBuilder(input).reverse().toString();
  }
}

你可以注册并使用前面的方法,如下例所示:

  • Java

ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString", StringUtils.class.getMethod("reverseString", String.class));

// 评估结果为 "olleh"
String helloWorldReversed = parser.parseExpression("#reverseString('hello')").getValue(context, String.class);

函数也可以注册为 java.lang.invoke.MethodHandle。 如果 MethodHandle 目标和参数在注册之前已完全绑定,这可能会启用更高效的用例; 但是,也支持部分绑定的句柄。

考虑 String#formatted(String, Object…​) 实例方法,它根据模板和可变数量的参数生成消息。

你可以将 formatted 方法注册并使用为 MethodHandle,如下例所示:

  • Java

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
    MethodType.methodType(String.class, Object[].class));
context.setVariable("message", mh);

// 评估结果为 "Simple message: <Hello World>"
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
    .getValue(context, String.class);

如上所述,也支持绑定 MethodHandle 并注册绑定的 MethodHandle。 如果目标和所有参数都已绑定,这可能会更高效。 在这种情况下,SpEL 表达式中不需要任何参数,如下例所示:

  • Java

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

String template = "This is a %s message with %s words: <%s>";
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
    MethodType.methodType(String.class, Object[].class))
    .bindTo(template)
    .bindTo(varargs); // 在这里我们必须在一个数组绑定中提供参数
context.setVariable("message", mh);

// 评估结果为 "This is a prerecorded message with 3 words: <Oh Hello World!>"
String message = parser.parseExpression("#message()")
    .getValue(context, String.class);