|
对于最新的稳定版本,请使用 Spring Data Cassandra 5.0.4! |
值表达式基础
值表达式(Value Expressions)是Spring 表达式语言(SpEL)与属性占位符解析(Property Placeholder Resolution)的结合。
它们将强大的编程表达式求值能力与使用属性占位符解析从Environment(例如配置属性)中获取值的简便性结合起来。
表达式应由可信输入(例如注解值)定义,而不应由用户输入决定。
作用域
值表达式(Value Expressions)用于各种注解上下文中。 Spring Data 在以下两个主要上下文中提供值表达式的求值功能:
-
映射模型注解:例如
@Document、@Field、@Value以及 Spring Data 模块中自带的其他注解,这些模块拥有各自的映射模型和相应的实体读取器(Entity Reader),如 MongoDB、Elasticsearch、Cassandra、Neo4j。 基于提供自身映射模型的库(如 JPA、LDAP)构建的模块,在其映射注解中不支持值表达式(Value Expressions)。以下代码演示了如何在映射模型注解的上下文中使用表达式。
示例 1.@Document注解的使用@Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}") class Order { // … } -
仓库查询方法:主要通过
@Query实现。以下代码演示了如何在仓库查询方法的上下文中使用表达式。
示例 2.@Query注解的使用class OrderRepository extends Repository<Order, String> { @Query("select u from User u where u.tenant = ?${spring.application.name:unknown} and u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}") List<Order> findContainingEscaped(String namePart); }
请查阅您所使用模块的文档,以确定实际的按名称/按索引参数绑定语法。
通常,表达式会以 :#{…}/:${…} 或 ?#{…}/?${…} 作为前缀。 |
表达式语法
值表达式可以从单一的SpEL表达式、属性占位符定义,也可以是由包括字面量在内的多种表达式组合而成的复合表达式。
#{tenantService.getOrderCollection()} (1)
#{(1+1) + '-hello-world'} (2)
${tenant-config.suffix} (3)
orders-${tenant-config.suffix} (4)
#{tenantService.getOrderCollection()}-${tenant-config.suffix} (5)
| 1 | 使用单个 SpEL 表达式的值表达式。 |
| 2 | 使用静态 SpEL 表达式求值为 2-hello-world 的值表达式。 |
| 3 | 使用单个属性占位符的值表达式。 |
| 4 | 由字面量 orders- 和属性占位符 ${tenant-config.suffix} 组成的复合表达式。 |
| 5 | 使用 SpEL、属性占位符和字面量的复合表达式。 |
| 使用值表达式为您的代码带来了很大的灵活性。 这样做需要在每次使用时对表达式进行求值,因此值表达式的求值会对性能表现产生影响。 |
Spring 表达式语言(SpEL) 和 属性占位符解析 详细解释了 SpEL 和属性占位符的语法与功能。
解析与求值
值表达式由 ValueExpressionParser API 进行解析。
ValueExpression 的实例是线程安全的,可以缓存以供后续使用,从而避免重复解析。
以下示例展示了值表达式(Value Expression)API 的用法:
-
Java
-
Kotlin
ValueParserConfiguration configuration = SpelExpressionParser::new;
ValueEvaluationContext context = ValueEvaluationContext.of(environment, evaluationContext);
ValueExpressionParser parser = ValueExpressionParser.create(configuration);
ValueExpression expression = parser.parse("Hello, World");
Object result = expression.evaluate(context);
val configuration = ValueParserConfiguration { SpelExpressionParser() }
val context = ValueEvaluationContext.of(environment, evaluationContext)
val parser = ValueExpressionParser.create(configuration)
val expression: ValueExpression = parser.parse("Hello, World")
val result: Any = expression.evaluate(context)
SpEL 表达式
SpEL 表达式采用模板风格,其中表达式应包含在 #{…} 格式内。
表达式通过由 EvaluationContext 提供的 EvaluationContextProvider 进行求值。
该上下文本身是一个功能强大的 StandardEvaluationContext,支持多种操作、对静态类型的访问以及上下文扩展。
| 请确保仅解析和评估来自可信来源(例如注解)的表达式。 接受用户提供的表达式可能会创建一条入口路径,用于利用应用程序上下文和您的系统,从而导致潜在的安全漏洞。 |
扩展评估上下文
EvaluationContextProvider 及其响应式变体 ReactiveEvaluationContextProvider 提供对 EvaluationContext 的访问。
ExtensionAwareEvaluationContextProvider 及其响应式变体 ReactiveExtensionAwareEvaluationContextProvider 是默认实现,它们从应用上下文中(特别是 ListableBeanFactory)确定上下文扩展。
扩展通过实现 EvaluationContextExtension 或 ReactiveEvaluationContextExtension 来为 EvaluationContext 的填充提供扩展支持。
这些扩展包括根对象、属性和函数(顶层方法)。
以下示例展示了一个上下文扩展,它提供了根对象、属性、函数以及一个别名函数。
EvaluationContextExtension-
Java
-
Kotlin
@Component
public class MyExtension implements EvaluationContextExtension {
@Override
public String getExtensionId() {
return "my-extension";
}
@Override
public Object getRootObject() {
return new CustomExtensionRootObject();
}
@Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put("key", "Hello");
return properties;
}
@Override
public Map<String, Function> getFunctions() {
Map<String, Function> functions = new HashMap<>();
try {
functions.put("aliasedMethod", new Function(getClass().getMethod("extensionMethod")));
return functions;
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
}
public static String extensionMethod() {
return "Hello World";
}
public static int add(int i1, int i2) {
return i1 + i2;
}
}
public class CustomExtensionRootObject {
public boolean rootObjectInstanceMethod() {
return true;
}
}
@Component
class MyExtension : EvaluationContextExtension {
override fun getExtensionId(): String {
return "my-extension"
}
override fun getRootObject(): Any? {
return CustomExtensionRootObject()
}
override fun getProperties(): Map<String, Any> {
val properties: MutableMap<String, Any> = HashMap()
properties["key"] = "Hello"
return properties
}
override fun getFunctions(): Map<String, Function> {
val functions: MutableMap<String, Function> = HashMap()
try {
functions["aliasedMethod"] = Function(javaClass.getMethod("extensionMethod"))
return functions
} catch (o_O: Exception) {
throw RuntimeException(o_O)
}
}
companion object {
fun extensionMethod(): String {
return "Hello World"
}
fun add(i1: Int, i2: Int): Int {
return i1 + i2
}
}
}
class CustomExtensionRootObject {
fun rootObjectInstanceMethod(): Boolean {
return true
}
}
一旦注册了上述所示的扩展,您就可以使用其导出的方法、属性和根对象来求值 SpEL 表达式:
#{add(1, 2)} (1)
#{extensionMethod()} (2)
#{aliasedMethod()} (3)
#{key} (4)
#{rootObjectInstanceMethod()} (5)
| 1 | 调用 add 中声明的 MyExtension 方法,该方法将两个数值参数相加并返回其和,结果为 3。 |
| 2 | 调用由 extensionMethod 声明的 MyExtension 方法,结果为 Hello World。 |
| 3 | 调用方法 aliasedMethod。
该方法作为函数公开,并重定向到由 extensionMethod 声明的 MyExtension 方法,结果输出 Hello World。 |
| 4 | 计算 key 属性,结果为 Hello。 |
| 5 | 在根对象实例 rootObjectInstanceMethod 上调用方法 CustomExtensionRootObject。 |
您可以在 SecurityEvaluationContextExtension 找到真实场景的扩展内容。
属性占位符
属性占位符遵循 ${…} 的形式,通常指由 PropertySource 通过 Environment 提供的属性。
属性可用于解析系统属性、应用程序配置文件、环境配置或由秘密管理系统贡献的属性源。
您可以在 Spring Framework 关于 @Value 用法的文档 中找到有关属性占位符的更多详细信息。