評価

このセクションでは、SpEL のインターフェースとその表現言語のプログラムによる使用方法を紹介します。完全な言語リファレンスは言語リファレンスにあります。

次のコードは、SpEL API を使用してリテラル文字列式 Hello World を評価する方法を示しています。

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 メッセージ変数の値は "Hello World" です。
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 メッセージ変数の値は "Hello World" です。

使用する可能性が最も高い SpEL クラスとインターフェースは、org.springframework.expression パッケージとそのサブパッケージ(spel.support など)にあります。

ExpressionParser インターフェースは、式文字列の解析を担当します。前の例では、式文字列は一重引用符で囲まれた文字列リテラルです。Expression インターフェースは、定義された式文字列を評価します。parser.parseExpression(…​) および exp.getValue(…​) を呼び出すときにスローされる可能性のある 2 種類の例外は、それぞれ ParseException および EvaluationException です。

SpEL は、メソッドの呼び出し、プロパティへのアクセス、コンストラクターの呼び出しなど、幅広い機能をサポートしています。

次のメソッド呼び出しの例では、文字列リテラル Hello World で concat メソッドを呼び出します。

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1message の値は "Hello World!" になりました。
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1message の値は "Hello World!" になりました。

次の例は、文字列リテラル Hello World の Bytes JavaBean プロパティにアクセスする方法を示しています。

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 この行は、リテラルをバイト配列に変換します。
val parser = SpelExpressionParser()

// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 この行は、リテラルをバイト配列に変換します。

SpEL は、標準のドット表記法 ( prop1.prop2.prop3 など) および対応するプロパティ値の設定を使用して、ネストされたプロパティもサポートします。パブリックフィールドにもアクセスできます。

次の例は、ドット表記を使用して文字列リテラルの長さを取得する方法を示しています。

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
1'Hello World'.bytes.length は、リテラルの長さを示します。
val parser = SpelExpressionParser()

// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1'Hello World'.bytes.length は、リテラルの長さを示します。

次の例に示すように、文字列リテラルを使用する代わりに、文字列のコンストラクターを呼び出すことができます。

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 リテラルから新しい String を構築し、それを大文字に変換します。
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")  (1)
val message = exp.getValue(String::class.java)
1 リテラルから新しい String を構築し、それを大文字に変換します。

ジェネリクスメソッド public <T> T getValue(Class<T> desiredResultType) の使用に注意してください。このメソッドを使用すると、式の値を目的の結果型にキャストする必要がなくなります。値を型 T にキャストできないか、登録済みの型コンバーターを使用して変換できない場合、EvaluationException がスローされます。

SpEL のより一般的な使用箇所は、特定のオブジェクトインスタンス (ルートオブジェクトと呼ばれる) に対して評価される式文字列を提供することです。次の例は、Inventor クラスのインスタンスから name プロパティを取得する方法と、ブール式で name プロパティを参照する方法を示しています。

  • Java

  • Kotlin

// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)

// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")

val parser = SpelExpressionParser()

var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true

EvaluationContext を理解する

EvaluationContext API は、式を評価してプロパティ、メソッド、フィールドを解決し、型変換を実行するときに使用されます。Spring は 2 つの実装を提供します。

SimpleEvaluationContext

SpEL 言語構文の全範囲を必要とせず、有意に制限される必要がある式のカテゴリに対して、本質的な SpEL 言語機能および構成オプションのサブセットを公開します。例には、データバインディング式およびプロパティベースのフィルターが含まれますが、これらに限定されません。

StandardEvaluationContext

SpEL 言語機能と構成オプションの完全なセットを公開します。これを使用して、デフォルトのルートオブジェクトを指定し、利用可能なすべての評価関連戦略を構成できます。

SimpleEvaluationContext は、SpEL 言語構文のサブセットのみをサポートするように設計されています。たとえば、Java 型参照、コンストラクター、Bean 参照は除外されます。また、式内のプロパティとメソッドのサポートレベルを明示的に選択する必要があります。SimpleEvaluationContext を作成するときは、SpEL 式でのデータバインディングに必要なサポートレベルを選択する必要があります。

  • 読み取り専用アクセスのデータバインディング

  • 読み取りおよび書き込みアクセスのデータバインディング

  • カスタム PropertyAccessor (通常は反射ベースではない)、DataBindingPropertyAccessor と組み合わせることも可能

便利なことに、SimpleEvaluationContext.forReadOnlyDataBinding() は DataBindingPropertyAccessor を介してプロパティへの読み取り専用アクセスを可能にします。同様に、SimpleEvaluationContext.forReadWriteDataBinding() はプロパティへの読み取りおよび書き込みアクセスを可能にします。あるいは、SimpleEvaluationContext.forPropertyAccessors(…​) を介してカスタムアクセサーを構成し、割り当てを無効にし、必要に応じてビルダーを介してメソッド解決や型コンバーターをアクティブ化します。

セキュリティに関する考慮事項

SpEL は、コンストラクターやメソッドの呼び出し、プロパティやフィールドの読み書き、Bean の参照など、リフレクションを基盤とした強力な式言語です。この強力な機能ゆえに、信頼できないソースから取得した SpEL 式を評価することは本質的に危険であり、一般的には避けるべきです。なぜなら、そうすることで、どの EvaluationContext 実装を使用しているかに関わらず、そのソースにアプリケーション内で任意のコードを実行する権限を事実上与えてしまう可能性があるからです。

このセクション全体を通して、SpEL 式のソースは、アプリケーションの開発者、またはアプリケーションの設定や操作を担当する管理者である場合に限り、「信頼できる」とみなされます。それ以外の SpEL 式のソースは、信頼できないものとして扱われます。たとえば、アプリケーションのエンドユーザーから提供された式や、外部システムから受信した式などがこれに該当します。

StandardEvaluationContext は SpEL 言語全体を公開しており、信頼できない情報源から取得した式を評価するために決して使用してはなりません。

SimpleEvaluationContext は SpEL 言語の機能の一部を制限しますが、この制限は最大限の努力に基づいて提供されるものであり、式の評価が安全であることを保証するものではありません。式は、構成されたルートオブジェクト、プロパティアクセサー、メソッドリゾルバー、変数、関数を介してアクセス可能なあらゆるプロパティ、メソッド、関数を呼び出す可能性があるため、信頼できないソースからの式を評価する場合は注意が必要です。EvaluationContext を構成するコード(たとえば、ルートオブジェクトを提供する、またはプロパティアクセサー、リゾルバー、変数、関数を登録する)は、コンテキストを介してアクセス可能なオブジェクトのいずれも、信頼できないソースからの式によって呼び出された場合に危険な操作を公開しないようにする責任があります。

さらに、式からアクセスできるプロパティ "getter" は、必ずしも純粋で副作用のない読み取り操作であるとは限りません。JavaBean スタイルのアクセサー (getName() や isActive() など) と、Java レコードや Kotlin データクラス (name() など) といったデータクラスをサポートするために使用される通常のアクセサーメソッドは、アクションを実行してたまたま値を返すメソッド (つまり、アクセサー型のメソッド) と区別できません。例: java.io.File の public boolean delete() メソッドは、SpEL から見ると通常のアクセサーメソッドのように見えます。具体的には、ReflectivePropertyAccessor も DataBindingPropertyAccessor も、そのようなメソッドに副作用がないかどうかを判断できません。さらに、SimpleEvaluationContext を読み取り専用のデータバインディングに制限しても、プロパティへの代入が許可されるかどうかのみを制御するだけで、プロパティの読み取りに副作用がないかどうかは検証されません。ルートオブジェクトまたは到達可能な他のオブジェクトを信頼できない式に公開する場合、そのアクセサー形式のメソッドが、その式によってトリガーされた場合に安全でないアクションを実行しないようにする必要があります。

メソッドが「アクセサー型」であると言えるのはなぜですか?

メソッドがアクセサー型であるのは、public であり、引数を取らず、値を返す場合です。これは、ReflectivePropertyAccessor および DataBindingPropertyAccessor が名前でプロパティ "getter" を解決する際に求める形状と同じです。この形状は、メソッドの呼び出しが実際に副作用がないかどうかについては何も示していません。例: 次のメソッドはすべてアクセサー型ですが、プロパティ読み取りとして安全に呼び出せるのはそのうちの一部だけです。

副作用なし(特性として公開しても安全):

  • getName() および isActive(): 従来型の JavaBean スタイルのアクセサー。

  • name() および active(): Java レコードや Kotlin データクラスなどのデータクラスで使用される、単純なアクセサーメソッド。

副作用(形状は同じでも、特性として公開するのは安全ではない):

  • java.io.File#delete(): 基となるファイルを削除し、削除が成功したかどうかを返します。

  • java.util.Queue#poll(): 先頭要素を削除して返すことで、キューを変更します。

  • java.util.concurrent.atomic.AtomicInteger#incrementAndGet(): カウンターをインクリメントして返しますが、そのカウンターは変更されます。

信頼できない式がプロパティとして someFile.deletesomeQueue.pollsomeCounter.incrementAndGet を参照できる場合、SpEL は、正規の getter を呼び出すのと同様に、対応するメソッドを呼び出します。

オブジェクトデザイン

Web データバインディングの設計ガイドラインと同様に、信頼できない入力に対して評価される SpEL 式からアクセスできる可能性のあるオブジェクトは、慎重に設計する必要があります。これは、EvaluationContext に提供されるルートオブジェクトだけでなく、そのルートオブジェクトから式がナビゲートできるすべてのオブジェクト(たとえば、プロパティ、メソッド、インデックス操作、変数、関数によって返されるオブジェクトなど)にも適用されます。

信頼できないソースからの式にオブジェクトを公開する場合は、以下の推奨事項を考慮してください。

専用型を使用する

既存のドメイン型やインフラストラクチャ型をそのまま使用するよりも、信頼できない式に対して評価されるように特別に設計された専用型を使用することをお勧めします。専用型を使用すると、JPA エンティティ、java.io.File、JDBC Connection などのクラスの全領域を公開するのではなく、式からアクセスできるプロパティとメソッドを正確に制御できます。これらのクラスのほとんどは、SpEL 評価を念頭に置いて設計されたものではありません。

不変性を好む

不変型(たとえば、Java レコードや val プロパティのみを公開する Kotlin データクラスなど)は、プロパティへの書き込みを排除し、影響を受ける可変状態が存在しないため、"getter" が副作用として状態を変更する可能性を懸念する必要をなくします。不変性自体は、外部副作用(ネットワーク呼び出しやファイルシステム操作など)を持つアクセサー型のメソッドを排除するものではありませんが、リスクのクラス全体を排除します。

範囲を限定する

式が使用すると想定されるプロパティとメソッドのみを公開し、それ以外は公開しないでください。PropertyAccessor は式ごとに特定のプロパティやメソッドへのアクセスを制限できないため、公開されたオブジェクト上でアクセス可能なアクセサー形式のメソッドはすべて、アプリケーションが式で使用しようとしたプロパティやメソッドに関係なく、そのオブジェクトにアクセスできるすべての式からアクセス可能です。

監査アクセサー型手法

型が公開するアクセサー形式のメソッドを、信頼できない式からアクセス可能にする前に、上記のセキュリティ上の考慮事項を念頭に置いてすべて確認してください。アクセス可能なメソッドは、その式によってトリガーされた場合に安全でないアクションを実行してはなりません。

これらの推奨事項は推移的に適用されます。ルートオブジェクトが別のオブジェクトを返すプロパティまたはメソッドを公開し、信頼できない式がそれにアクセスできる場合(たとえば、rootObject.child.grandchild)、ネストされたオブジェクトはルートオブジェクト自体と同様にアクセス可能であり、同じ設計要件を満たす必要があります。インデックスを使用してアクセスされるオブジェクト(たとえば、rootObject.items[0] または rootObject.items['key'])についても同様です。インデックス操作によって返されるものは、他のネストされたオブジェクトと同様にアクセス可能です。

ライフサイクルと再利用

パフォーマンス向上のため、解析された Expression を構成する AST ノードは、以前の評価で条件を満たした特定の PropertyAccessorIndexAccessorMethodExecutor または ConstructorExecutor をキャッシュする場合があります。これにより、同じノードの後の評価で、登録されているすべてのアクセサーまたはリゾルバーに順番に問い合わせる必要がなくなります。このキャッシュ動作を理解することは、前述のセキュリティ上の考慮事項に加えて、Expression および EvaluationContext を正しく使用するために不可欠です。

解析済みの Expression は、一度作成して繰り返し評価することを前提として設計されており、そのように行うことはサポートされ、推奨されています。具体的には、以下のとおりです。

  • 解析された Expression は、異なるルートオブジェクト、および同じ型で同等の構成を持つ異なる EvaluationContext インスタンスに対して評価される場合があります。たとえば、同じ種類のカスタム PropertyAccessor をそれぞれ登録する複数の StandardEvaluationContext インスタンスなどです。同じ式の評価間でコンテキストに登録されているアクセサーまたはリゾルバーを変更することは一般的ではなく、通常は推奨されませんが、正しく動作することが期待されます。参照されるのは、以前の評価中に取得されたスナップショットではなく、現在のコンテキストに登録されている状態です。

  • 解析された Expression は、最初にセキュリティ上の制約が 1 つのコンテキストに対して評価され、その後、異なる、通常はより制限的なセキュリティ上の制約を持つコンテキストに対して評価されてはなりません (たとえば、最初に StandardEvaluationContext に対して評価され、その後 SimpleEvaluationContext に対して評価される)。これは、管理者に代わってデータベースクエリを実行し、結果として得られる管理者特権の実行プランをキャッシュし、その後、そのキャッシュされたプランを特権の低いユーザーに再利用しながら、特権の低いユーザーの制限が適用されることを期待するのと似ています。最初のキャッシュされた状態、より寛容な評価が 2 番目の評価で再利用される可能性があり、その結果、2 番目のコンテキストの制限を確実に適用できなくなります。同じ式文字列を異なるセキュリティ上の制約を持つコンテキストで評価する必要がある場合は、コンテキストごとに 1 つの個別の Expression インスタンスに解析してください。

セキュリティ上の影響が異なる複数の EvaluationContext インスタンス間で、解析済みの単一の Expression を再利用することは、サポートされている使用パターンではなく、どの EvaluationContext 実装が関係しているかに関わらず、避ける必要があります。

型変換

デフォルトでは、SpEL は Spring コア (org.springframework.core.convert.ConversionService) で利用可能な変換サービスを使用します。この変換サービスには、一般的な変換のための多くの組み込みコンバーターが付属していますが、型間のカスタム変換を追加できるように完全に拡張可能です。さらに、ジェネリクス医薬品も認識しています。これは、式でジェネリクス型を使用する場合、SpEL は、遭遇したすべてのオブジェクトの型の正確さを維持するために変換を試みることを意味します。

これは実際には何を意味するのでしょうか ? setValue() を使用した代入が List プロパティの設定に使用されているとします。プロパティの型は実際には List<Boolean> です。SpEL は、リストの要素をリストに配置する前に Boolean に変換する必要があることを認識します。次の例は、その方法を示しています。

  • Java

  • Kotlin

class Simple {
	public List<Boolean> booleanList = new ArrayList<>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b is false
Boolean b = simple.booleanList.get(0);
class Simple {
	var booleanList: MutableList<Boolean> = ArrayList()
}

val simple = Simple()
simple.booleanList.add(true)

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")

// b is false
val b = simple.booleanList[0]

パーサー構成

パーサー構成オブジェクト (org.springframework.expression.spel.SpelParserConfiguration) を使用して SpEL 式パーサーを構成することができます。構成オブジェクトは、式コンポーネントの一部の動作を制御します。例: コレクションにインデックスを付け、指定されたインデックスの要素が null である場合、SpEL は自動的に要素を作成できます。これは、プロパティ参照の チェーンで構成された式を使用する場合に便利です。同様に、コレクションにインデックスを付け、コレクションの現在のサイズよりも大きいインデックスを指定すると、SpEL はそのインデックスに合わせてコレクションを自動的に拡張できます。指定されたインデックスに要素を追加するために、SpEL は指定された値を設定する前に、要素型のデフォルトコンストラクターを使用して要素を作成しようとします。要素型にデフォルトコンストラクターがない場合は、コレクションに null が追加されます。値の設定メソッドを認識する組み込みコンバーターまたはカスタムコンバーターがない場合、null は指定されたインデックスのコレクションに残ります。次の例は、List を自動的に拡張する方法を示しています。

  • Java

  • Kotlin

class Demo {
	public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true, true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
class Demo {
	var list: List<String>? = null
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)

val parser = SpelExpressionParser(config)

val expression = parser.parseExpression("list[3]")

val demo = Demo()

val o = expression.getValue(demo)

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String

デフォルトでは、SpEL 式には 10,000 文字を超える文字を含めることはできません。ただし、maxExpressionLength は構成可能です。SpelExpressionParser をプログラムで作成する場合は、SpelExpressionParser に提供する SpelParserConfiguration を作成するときにカスタム maxExpressionLength を指定できます。ApplicationContext 内の SpEL 式の解析に使用する maxExpressionLength を設定する場合 (たとえば、XML Bean 定義、@Value など)、JVM システムプロパティまたは spring.context.expression.maxLength という名前の Spring プロパティをアプリケーションで必要な式の最大長に設定できます。( サポートされている Spring プロパティを参照)。

同様に、SpEL 式の評価中に実行される操作の数は、デフォルトでは 10,000 を超えることはできませんが、maxOperations の値は構成可能です。SpelExpressionParser をプログラムで作成する場合 (推奨される方法)、SpelExpressionParser に渡す SpelParserConfiguration を作成する際に、カスタムの maxOperations 値を指定できます。SpelParserConfiguration を介して maxOperations の明示的な値を構成できない場合は、JVM システムプロパティまたは Spring プロパティの spring.expression.maxOperations を、アプリケーションに必要な最大操作数に設定できます ( サポートされている Spring プロパティを参照)。

さらに、SpEL 式内の BigDecimal または BigInteger のべき乗演算の結果は、デフォルトでは 1,000,000 ビットを超えることはできません。これは、おおよそ 300,000 桁の 10 進数に相当します。大きな基数または大きな指数を含むべき乗演算は計算コストが高くなる可能性があるため、この制限により評価が制限されることが保証されます。ただし、maximumBigPowerBits の値は構成可能です。SpelExpressionParser をプログラムで作成する場合 (推奨される方法)、SpelExpressionParser に渡す SpelParserConfiguration を作成する際に、カスタムの maximumBigPowerBits 値を指定できます。この制限を完全に解除するには、maximumBigPowerBits の値として Integer.MAX_VALUE を渡します。SpelParserConfiguration を介して maximumBigPowerBits の明示的な値を構成できない場合は、JVM システムプロパティまたは Spring プロパティの spring.expression.maxBigPowerBits を最大結果サイズ (ビット単位) に設定できます ( サポートされている Spring プロパティを参照)。

SpEL のコンパイル

Spring は、SpEL 式の基本的なコンパイラーを提供します。通常、式は解釈されるため、評価中に多くの動的な柔軟性が得られますが、最適なパフォーマンスは得られません。エクスプレッションを時折使用する場合にはこれで問題ありませんが、Spring Integration などの他のコンポーネントで使用する場合はパフォーマンスが非常に重要になるため、ダイナミズムは実際には必要ありません。

SpEL コンパイラーは、このニーズに対処することを目的としています。評価中に、コンパイラーは実行時の式の動作を具体化する Java クラスを生成し、そのクラスを使用して式の評価をより高速に実行します。式の周囲に入力できないため、コンパイラーは、コンパイルの実行時に式の解釈された評価中に収集された情報を使用します。例: 純粋に式からプロパティ参照の型を知りませんが、最初に解釈された評価の間に、それが何であるかを見つけます。もちろん、そのような派生情報に基づいてコンパイルを行うと、さまざまな式要素の型が時間とともに変化する場合、後でトラブルを引き起こす可能性があります。このため、コンパイルは、評価が繰り返されても型情報が変更されない式に最適です。

次の基本的な式を考えてみましょう。

someArray[0].someProperty.someOtherProperty < 0.1

上記の式には配列アクセス、一部のプロパティの逆参照、数値演算が含まれるため、パフォーマンスの向上は非常に顕著です。50,000 反復のマイクロベンチマークの実行例では、インタープリターを使用して評価するのに 75 ミリ秒かかり、コンパイルされたバージョンの式を使用して 3 ミリ秒しかかかりませんでした。

コンパイラー構成

コンパイラーはデフォルトではオンになっていませんが、2 つの異なる方法のいずれかでオンにすることができます。これをオンにするには、パーサー構成プロセス(前述)を使用するか、SpEL の使用箇所が別のコンポーネントに埋め込まれている場合は Spring プロパティを使用します。このセクションでは、これらのオプションの両方について説明します。

コンパイラーは、org.springframework.expression.spel.SpelCompilerMode enum でキャプチャーされる 3 つのモードのいずれかで動作できます。モードは以下の通りです。

OFF

コンパイラーはオフになり、すべての式は解釈モードで評価されます。これがデフォルトのモードです。

IMMEDIATE

即時モードでは、式はできるだけ早く、通常は最初の解釈された評価の後にコンパイルされます。コンパイルされた式の評価が失敗した場合 (たとえば、前述のように型の変更が原因)、式評価の呼び出し元は例外を受け取ります。さまざまな式要素の型が時間の経過とともに変化する場合は、MIXED モードに切り替えるか、コンパイラーをオフにすることを検討してください。

MIXED

混合モードでは、式の評価は時間の経過とともに解釈モードコンパイルモードの間で暗黙的に切り替わります。解釈モードの実行が何回か成功すると、式はコンパイルされます。コンパイルされた式の評価が失敗した場合 (たとえば、型の変更により)、その失敗は内部でキャッチされ、システムは指定された式に対して解釈モードに戻ります。基本的に、呼び出し元が IMMEDIATE モードで受け取る例外は、代わりに内部で処理されます。しばらくすると、コンパイラーは別のコンパイル済み形式を生成し、それに切り替える場合があります。解釈モードとコンパイルモード間のこの切り替えサイクルは、システムが試行を続ける意味がないと判断するまで (たとえば、特定の失敗しきい値に達した場合) 継続され、その時点でシステムは指定された式に対して解釈モードに永続的に切り替わります。

 MIXED モードでは副作用のある式で問題が発生する可能性があるため、IMMEDIATE モードが存在します。コンパイルされた式が部分的に成功した後に展開する場合は、システムの状態に影響を与える何かをすでに行っている可能性があります。これが発生した場合、式の一部が 2 回実行される可能性があるため、呼び出し側は解釈モードでサイレントに再実行することを望まない可能性があります。

モードを選択した後、SpelParserConfiguration を使用してパーサーを構成します。次の例は、その方法を示しています。

  • Java

  • Kotlin

SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
		this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
		this.javaClass.classLoader)

val parser = SpelExpressionParser(config)

val expr = parser.parseExpression("payload")

val message = MyMessage()

val payload = expr.getValue(message)

コンパイラーモードを指定する場合は、ClassLoader も指定できます (null を渡すことができます)。コンパイルされた式は、提供された式に作成された子 ClassLoader で定義されます。ClassLoader が指定されている場合、式の評価プロセスに関係するすべての型を確認できることを確認することが重要です。ClassLoader を指定しない場合は、デフォルトの ClassLoader が使用されます (通常は、式の評価中に実行されるスレッドのコンテキスト ClassLoader )。

コンパイラーを構成する 2 番目の方法は、SpEL が他のコンポーネント内に埋め込まれており、構成オブジェクトを介して構成できない場合に使用します。このような場合、JVM システムプロパティ (または SpringProperties メカニズム) を介して spring.expression.compiler.mode プロパティを SpelCompilerMode 列挙値 (offimmediate、または mixed) の 1 つに設定することができます。

コンパイラーの制限

Spring は、あらゆる種類の式のコンパイルをサポートしているわけではありません。主な焦点は、パフォーマンスが重要なコンテキストで使用される可能性が高い一般的な式です。次の種類の式はコンパイルできません。

  • 代入を含む式

  • 変換サービスに依存する式

  • カスタムリゾルバーを使用した式

  • オーバーロードされた演算子を使用した式

  • Optional を null 安全またはエルビス演算子とともに使用した式

  • 配列構築構文を使用した式

  • 選択または射影を使用した式

  • Bean 参照を使用した表現

将来的には、追加の種類の式のコンパイルがサポートされる可能性があります。