跳到主要内容

值表达式基础

DeepSeek V3 中英对照 Value Expressions Fundamentals

值表达式(Value Expressions)是 Spring 表达式语言(SpEL)属性占位符解析 的组合。它们将编程表达式的强大评估能力与通过属性占位符解析从 Environment 中获取值(如配置属性)的简单性结合在一起。

表达式应由受信任的输入(如注解值)定义,而不应从用户输入中确定。

作用域

值表达式(Value Expressions)在注解的上下文中被广泛使用。Spring Data 主要在两种上下文中提供值表达式的评估:

  • 映射模型注解:例如 @Document@Field@Value 等注解,这些注解在 Spring Data 模块中自带其各自的映射模型和实体读取器,如 MongoDB、Elasticsearch、Cassandra、Neo4j 等。基于提供自身映射模型的库(如 JPA、LDAP)构建的模块在映射注解中不支持值表达式。

    以下代码展示了如何在映射模型注解的上下文中使用表达式。

    示例 1. @Document 注解用法

    @Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}")
    class Order {
    // …
    }
    java
  • 仓库查询方法:主要通过 @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);
    }
    java
备注

请查阅您的模块文档,以确定实际的按名称/按索引绑定参数语法。通常,表达式会以 :#{…}/:${…}?#{…}/?${…} 作为前缀。

表达式语法

值表达式可以从单一的 SpEL 表达式、属性占位符或混合各种表达式(包括字面量)的复合表达式中定义。

示例 3. 表达式示例

#{tenantService.getOrderCollection()}                          // <1>
#{(1+1) + '-hello-world'} // <2>
${tenant-config.suffix} // <3>
orders-${tenant-config.suffix} // <4>
#{tenantService.getOrderCollection()}-${tenant-config.suffix} // <5>
none
  • 使用单个 SpEL 表达式的值表达式。

  • 使用静态 SpEL 表达式的值表达式,结果为 2-hello-world

  • 使用单个属性占位符的值表达式。

  • 由字面量 orders- 和属性占位符 ${tenant-config.suffix} 组成的复合表达式。

  • 使用 SpEL、属性占位符和字面量的复合表达式。

备注

使用值表达式为代码引入了很大的灵活性。这样做需要在每次使用表达式时进行评估,因此,值表达式的评估会对性能产生影响。

Spring 表达式语言 (SpEL)属性占位符解析 详细解释了 SpEL 和属性占位符的语法和功能。

解析与求值

值表达式(Value Expressions)由 ValueExpressionParser API 解析。ValueExpression 的实例是线程安全的,可以缓存以供后续使用,以避免重复解析。

以下示例展示了 Value Expression API 的使用:

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

SpEL 表达式

SpEL 表达式遵循模板样式,其中表达式需要包含在 #{…} 格式中。表达式使用由 EvaluationContextProvider 提供的 EvaluationContext 进行评估。上下文本身是一个功能强大的 StandardEvaluationContext,允许广泛的操作、对静态类型的访问以及上下文扩展。

备注

确保仅解析和评估来自受信任来源(如注解)的表达式。接受用户提供的表达式可能会为利用应用程序上下文和系统创造入口路径,从而导致潜在的安全漏洞。

扩展评估上下文

EvaluationContextProvider 及其响应式变体 ReactiveEvaluationContextProvider 提供了对 EvaluationContext 的访问权限。ExtensionAwareEvaluationContextProvider 及其响应式变体 ReactiveExtensionAwareEvaluationContextProvider 是默认实现,它们从应用上下文中确定上下文扩展,特别是从 ListableBeanFactory 中获取。

扩展通过实现 EvaluationContextExtensionReactiveEvaluationContextExtension 来为 EvaluationContext 提供扩展支持。这些扩展包括根对象、属性和函数(顶级方法)。

以下示例展示了一个上下文扩展,它提供了一个根对象、属性、函数以及一个别名函数。

@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;
}

}
java

一旦注册了上述扩展,你就可以使用其导出的方法、属性和根对象来评估 SpEL 表达式:

示例 4. 表达式求值示例

#{add(1, 2)}                                             // <1>
#{extensionMethod()} // <2>
#{aliasedMethod()} // <3>
#{key} // <4>
#{rootObjectInstanceMethod()} // <5>
none
  • 调用由 MyExtension 声明的方法 add,结果为 3,因为该方法将两个数字参数相加并返回和。

  • 调用由 MyExtension 声明的方法 extensionMethod,结果为 Hello World

  • 调用方法 aliasedMethod。该方法作为函数公开,并重定向到由 MyExtension 声明的方法 extensionMethod,结果为 Hello World

  • 评估 key 属性,结果为 Hello

  • 在根对象实例 CustomExtensionRootObject 上调用方法 rootObjectInstanceMethod

你可以在 SecurityEvaluationContextExtension 中找到实际应用场景的上下文扩展。

属性占位符

属性占位符遵循 ${…} 的形式,通常通过 EnvironmentPropertySource 提供的属性来引用。属性对于解析系统属性、应用程序配置文件、环境配置或由秘密管理系统提供的属性源非常有用。你可以在 Spring Framework 的 @Value 用法文档 中找到有关属性占位符的更多详细信息。