值表示式基礎
值表示式是 Spring Expression Language (SpEL) 和 屬性佔位符解析 的組合。它們將程式化表示式的強大評估功能與訴諸於屬性佔位符解析以從 Environment 獲取值(例如配置屬性)的簡潔性結合起來。
表示式應由受信任的輸入(如註解值)定義,而不是由使用者輸入確定。
範圍
值表示式用於跨註解的上下文。Spring Data 在兩個主要上下文提供值表示式評估:
-
對映模型註解:例如
@Document、@Field、@Value以及 Spring Data 模組中附帶其自己的對映模型(例如 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 表示式、屬性佔位符或包含各種表示式(包括文字)的複合表示式定義。
#{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 Expression Language (SpEL) 和 屬性佔位符解析 詳細解釋了 SpEL 和屬性佔位符的語法和功能。
解析和評估
值表示式由 ValueExpressionParser API 解析。ValueExpression 的例項是執行緒安全的,可以快取以供後續使用,從而避免重複解析。
以下示例展示了值表示式 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。它們是根物件、屬性和函式(頂級方法)。
以下示例展示了一個上下文擴充套件,它提供了一個根物件、屬性、函式和別名函式。
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 | 呼叫 MyExtension 宣告的 add 方法,結果為 3,因為該方法將兩個數字引數相加並返回它們的和。 |
| 2 | 呼叫 MyExtension 宣告的 extensionMethod 方法,結果為 Hello World。 |
| 3 | 呼叫 aliasedMethod 方法。該方法作為函式公開,並重定向到 MyExtension 宣告的 extensionMethod 方法,結果為 Hello World。 |
| 4 | 評估 key 屬性,結果為 Hello。 |
| 5 | 在根物件例項 CustomExtensionRootObject 上呼叫 rootObjectInstanceMethod 方法。 |
您可以在 SecurityEvaluationContextExtension 中找到真實的上下文擴充套件。
屬性佔位符
遵循 ${…} 形式的屬性佔位符通常指透過 Environment 由 PropertySource 提供的屬性。屬性對於解析系統屬性、應用程式配置檔案、環境配置或秘密管理系統貢獻的屬性源非常有用。您可以在 Spring Framework 關於 @Value 用法的文件中找到有關屬性佔位符的更多詳細資訊。