値式の基礎

値式は、Spring 式言語 (SpEL)プロパティのプレースホルダーの解決の組み合わせです。これらは、プログラム式の強力な評価と、構成プロパティなどの Environment から値を取得するためにプロパティプレースホルダー解決に頼る単純さを組み合わせています。

式は、ユーザー入力から決定されるのではなく、アノテーション値などの信頼できる入力によって定義されることが期待されます。

次のコードは、アノテーションのコンテキストで式を使用する方法を示しています。

例 1: アノテーションの使用箇所
@Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}")
class Order {
  // …
}

値式は、単一の SpEL 式、プロパティプレースホルダー、またはリテラルを含むさまざまな式を組み合わせた複合式から定義できます。

例 2: 式の例
#{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 式を使用した値式。
22-hello-world に評価される静的な SpEL 式を使用した値式。
3 単一のプロパティプレースホルダーを使用した値式。
4 リテラル orders- とプロパティプレースホルダー ${tenant-config.suffix} で構成される複合式。
5SpEL、プロパティプレースホルダー、リテラルを使用した複合式。
値式を使用すると、コードに大きな柔軟性がもたらされます。これを行うには、使用箇所ごとに式を評価する必要があるため、値式の評価はパフォーマンスプロファイルに影響を与えます。

解析と評価

値式は 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 式を評価できます。

例 3: 式の評価例
#{add(1, 2)}                                             (1)
#{extensionMethod()}                                     (2)
#{aliasedMethod()}                                       (3)
#{key}                                                   (4)
#{rootObjectInstanceMethod()}                            (5)
1MyExtension によって宣言されたメソッド add を呼び出すと、メソッドは両方の数値パラメーターを追加して合計を返すため、3 が生成されます。
2MyExtension によって宣言されたメソッド extensionMethod を呼び出すと、Hello World が生成されます。
3 メソッド aliasedMethod を呼び出します。このメソッドは関数として公開され、MyExtension によって宣言されたメソッド extensionMethod にリダイレクトされ、Hello World になります。
4key プロパティを評価すると、Hello が得られます。
5 ルートオブジェクトインスタンス CustomExtensionRootObject でメソッド rootObjectInstanceMethod を呼び出します。

実際のコンテキスト拡張機能は SecurityEvaluationContextExtension [GitHub] (英語) で見つけることができます。

プロパティプレースホルダー

${ … } 形式に続くプロパティプレースホルダーは、通常 PropertySource から Environment によって提供されるプロパティを参照します。プロパティは、システムプロパティ、アプリケーション構成ファイル、環境構成、シークレット管理システムによって提供されるプロパティソースに対して解決できます。プロパティプレースホルダーの詳細については、@Value の使用箇所に関する Spring Framework のドキュメントを参照してください。