評価

このセクションでは、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 は 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 のコンパイル

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 (default): コンパイラーはオフになります。

  • IMMEDIATE: 即時モードでは、式はできるだけ早くコンパイルされます。これは通常、最初に解釈された評価の後です。コンパイルされた式が失敗する場合(通常、前述のように型の変更が原因)、式の評価の呼び出し元は例外を受け取ります。

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

  • 代入を含む式

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

  • カスタムリゾルバーまたはアクセサーを使用する式

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

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

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

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