此版本仍在开发中,尚未稳定。如需最新的稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

功能

您可以扩展SpEL,通过注册用户自定义函数,这些函数可以在表达式中使用#functionName(…​)语法进行调用。与标准方法调用一样,函数调用也支持可变参数spring-doc.cadn.net.cn

函数可以作为变量EvaluationContext实现中通过 setVariable()方法进行注册。spring-doc.cadn.net.cn

StandardEvaluationContext 还定义了 registerFunction(…​) 方法,这些方法提供了一种便捷的方式来将函数注册为 java.lang.reflect.Methodjava.lang.invoke.MethodHandlespring-doc.cadn.net.cn

由于函数在求值上下文中与 变量 共享相同的命名空间, 因此必须注意确保函数名和变量名不发生冲突。spring-doc.cadn.net.cn

以下示例展示了如何注册一个用户定义的函数,通过使用 java.lang.reflect.Method 进行反射调用来触发:spring-doc.cadn.net.cn

Method method = ...;

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

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

例如,考虑以下将字符串反转的实用方法:spring-doc.cadn.net.cn

public abstract class StringUtils {

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

你可以注册并使用上述方法,如下例所示:spring-doc.cadn.net.cn

ExpressionParser parser = new SpelExpressionParser();

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

// evaluates to "olleh"
String helloWorldReversed = parser.parseExpression(
		"#reverseString('hello')").getValue(context, String.class);
val parser = SpelExpressionParser()

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString.javaMethod)

// evaluates to "olleh"
val helloWorldReversed = parser.parseExpression(
		"#reverseString('hello')").getValue(context, String::class.java)

函数也可以注册为 java.lang.invoke.MethodHandle。如果在注册之前,MethodHandle 目标和参数已完全绑定,则可以实现更高效的使用场景;但同时也支持部分绑定的句柄。spring-doc.cadn.net.cn

考虑 String#formatted(Object…​) 实例方法,它根据一个模板和可变数量的参数 (可变参数)生成一条消息。spring-doc.cadn.net.cn

您可以注册并使用 formatted 方法作为 MethodHandle,如下例所示:spring-doc.cadn.net.cn

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);

// evaluates to "Simple message: <Hello World>"
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
		.getValue(context, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
		MethodType.methodType(String::class.java, Array<Any>::class.java))
context.setVariable("message", mh)

// evaluates to "Simple message: <Hello World>"
val message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
		.getValue(context, String::class.java)

如上所述,也支持绑定 MethodHandle 并注册已绑定的 MethodHandle。如果目标对象和所有参数都被绑定,这种方式的性能可能会更高。在这种情况下,SpEL 表达式中不需要任何参数,如下例所示:spring-doc.cadn.net.cn

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)
		// Here we have to provide the arguments in a single array binding:
		.bindTo(varargs);
context.setVariable("message", mh);

// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
String message = parser.parseExpression("#message()")
		.getValue(context, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val template = "This is a %s message with %s words: <%s>"
val varargs = arrayOf("prerecorded", 3, "Oh Hello World!", "ignored")

val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
	MethodType.methodType(String::class.java, Array<Any>::class.java))
		.bindTo(template)
		// Here we have to provide the arguments in a single array binding:
		.bindTo(varargs)
context.setVariable("message", mh)

// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
val message = parser.parseExpression("#message()")
		.getValue(context, String::class.java)