值表达式基础
值表达式是 Spring 表达式语言 (SpEL) 和 属性占位符解析 的结合。它们将程序化表达式的强大评估能力与从 Environment 中获取值(如配置属性)的属性占位符解析的简洁性相结合。
表达式应该由可信的输入(例如注解值)来定义,而不是根据用户输入来确定。
范围
值表达式(Value Expressions)在注解的上下文中被广泛使用。Spring Data 主要在两种上下文中提供值表达式的评估:
- 
映射模型注解:例如 Spring Data 模块中的 @Document、@Field、@Value等注解,这些模块自带各自的映射模型和实体读取器,如 MongoDB、Elasticsearch、Cassandra、Neo4j。基于提供自身映射模型的库(如 JPA、LDAP)构建的模块不支持在映射注解中使用值表达式。以下代码展示了如何在映射模型注解的上下文中使用表达式。 示例 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 表达式定义,也可以从一个属性占位符定义,或者是混合了包括字面量在内的各种表达式的复合表达式。
示例 3. 表达式示例
#{tenantService.getOrderCollection()}                          // <1>
#{(1+1) + '-hello-world'}                                      // <2>
${tenant-config.suffix}                                        // <3>
orders-${tenant-config.suffix}                                 // <4>
#{tenantService.getOrderCollection()}-${tenant-config.suffix}  // <5>
- 使用单个 SpEL 表达式的值表达式。 
- 使用静态 SpEL 表达式求值为 - 2-hello-world的值表达式。
- 使用单个属性占位符的值表达式。 
- 由字面量 - orders-和属性占位符- ${tenant-config.suffix}组成的复合表达式。
- 使用 SpEL、属性占位符和字面量的复合表达式。 
使用值表达式为你的代码带来了很大的灵活性。这样做需要在每次使用时对表达式进行评估,因此,值表达式的评估会对性能产生影响。
Spring Expression Language (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 表达式 遵循模板样式,其中表达式应包含在 #{…} 格式中。表达式使用由 EvaluationContextProvider 提供的 EvaluationContext 进行评估。上下文本身是一个强大的 StandardEvaluationContext,允许进行广泛的操作、访问静态类型和上下文扩展。
请确保仅解析和评估来自可信来源(例如注解)的表达式。接受用户提供的表达式可能会为利用应用上下文和系统创建入口路径,从而导致潜在的安全漏洞。
扩展评估上下文
EvaluationContextProvider 及其响应式变体 ReactiveEvaluationContextProvider 提供了对 EvaluationContext 的访问。ExtensionAwareEvaluationContextProvider 及其响应式变体 ReactiveExtensionAwareEvaluationContextProvider 是默认实现,它们从应用程序上下文中确定上下文扩展,特别是 ListableBeanFactory。
扩展通过实现 EvaluationContextExtension 或 ReactiveEvaluationContextExtension 来为 EvaluationContext 提供扩展支持。这些扩展可以是根对象、属性以及函数(顶层方法)。
以下示例展示了一个上下文扩展,它提供了一个根对象、属性、函数以及一个别名函数。
- 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 表达式:
示例 4. 表达式求值示例
#{add(1, 2)}                                             // <1>
#{extensionMethod()}                                     // <2>
#{aliasedMethod()}                                       // <3>
#{key}                                                   // <4>
#{rootObjectInstanceMethod()}                            // <5>
- 调用由 - MyExtension声明的- add方法,结果为- 3,因为该方法将两个数字参数相加并返回和。
- 调用由 - MyExtension声明的- extensionMethod方法,结果为- Hello World。
- 调用 - aliasedMethod方法。该方法被暴露为函数,并重定向到由- MyExtension声明的- extensionMethod方法,结果为- Hello World。
- 评估 - key属性,结果为- Hello。
- 在根对象实例 - CustomExtensionRootObject上调用- rootObjectInstanceMethod方法。
你可以在 SecurityEvaluationContextExtension 找到实际应用中的上下文扩展。
属性占位符
属性占位符 ${…} 通常通过 Environment 由 PropertySource 提供。属性解析可用于系统属性、应用程序配置文件、环境配置或由密钥管理系统提供的属性源。你可以在 Spring Framework 的 @Value 使用文档 中找到更多关于属性占位符的详细信息。