評価
このセクションでは、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();
1 | message の値は "Hello World!" になりました。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | message の値は "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
列挙値 (off
、immediate
、または mixed
) の 1 つに設定することができます。