值表示式基礎

值表示式結合了 Spring Expression Language (SpEL)Property Placeholder Resolution(屬性佔位符解析)。它們將強大的程式化表示式求值與求助於屬性佔位符解析從 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 {
      // …
    }
  • Repository 查詢方法:主要透過 @Query 註解使用。

    以下程式碼演示瞭如何在 Repository 查詢方法的上下文中使用表示式。

    示例 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 表示式、一個 Property Placeholder(屬性佔位符)或一個混合了各種表示式(包括字面值)的複合表示式定義。

示例 3. 表示式示例
#{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 使用單個 Property Placeholder(屬性佔位符)的值表示式。
4 由字面值 orders- 和 Property Placeholder ${tenant-config.suffix} 組成的複合表示式。
5 使用 SpEL、Property Placeholders(屬性佔位符)和字面值的複合表示式。
使用值表示式為您的程式碼帶來了很大的靈活性。這樣做需要在每次使用時對錶達式進行求值,因此,值表示式求值會對效能產生影響。

Spring Expression Language (SpEL)Property Placeholder Resolution(屬性佔位符解析)詳細解釋了 SpEL 和 Property Placeholders 的語法及功能。

解析與求值

值表示式由 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)確定上下文擴充套件。

擴充套件實現了 EvaluationContextExtensionReactiveEvaluationContextExtension 來為 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 表示式

示例 4. 表示式求值示例
#{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 找到實際的上下文擴充套件示例。

Property Placeholders(屬性佔位符)

遵循 ${…} 形式的 Property placeholders(屬性佔位符)引用通常由 PropertySource 透過 Environment 提供的屬性。屬性可用於解析系統屬性、應用程式配置檔案、環境配置或秘密管理系統貢獻的屬性源。您可以在 Spring Framework 關於 @Value 用法的文件中找到有關屬性佔位符的更多詳細資訊。