http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 7. Validation, Data Binding, and Type Conversionをテキトーに訳した - kagamihogeの日記の続き。
8. Spring Expression Language (SpEL)
8.1 Introduction
Spring Expression Language(SpEL)とは、強力な式言語で実行時にオブジェクトグラフの検索と操作を行います。シンタックスはUnified ELに似ていますが、追加機能があり、notably method invocationとbasic string templating functionalityがあります。
Javaで利用可能な式言語は他にも幾つかあり、例を挙げるとOGNL, MVEL, JBossELなどです。Spring Expression LanguageはSpringポートフォリオの全プロダクトで使用可能な式言語を一つSpringコミュニティに提供するべく作られました。言語機能に対する要求はSpringポートフォリオのプロダクトから上げられ、これにはeclipseベースのSpring Tool Suiteにおけるコード補完も含みます。しかし、SpELはAPIに依存しないため*1、他の式言語実装と統合する必要が生じた場合でも利用可能です。
SpELはSpringポートフォリオ内の式評価のための基盤を提供するものですが、Springに直接結合してはおらず、独立して使用可能です。自己完結にするため、このチャプターの多くの例は、SpELを独立した式言語であるかのように使います。これにはパーサーなど基盤となるクラスを起動する手間が必要です。Springユーザーの多くはこの基盤クラスを扱う必要は無く、評価用の式を書くだけです。一般的な使用例としてはSpELインテグレーションをXMLもしくはアノテーションベースのビーン定義生成に組み込みことで、Expression support for defining bean definitionsで説明します。
このチャプターは、式言語の機能・API・シンタックス、を解説します。式評価用のターゲットオブジェクトの例としてInventorおよびSocietyクラスを使います。これらのクラス宣言と処理に使うデータはチャプターの末尾に載せてあります。
8.2 Feature Overview
式言語は以下の機能をサポートします。
- Literal expressions
- Boolean and relational operators
- Regular expressions
- Class expressions
- Accessing properties, arrays, lists, maps
- Method invocation
- Relational operators
- Assignment
- Calling constructors
- Bean references
- Array construction
- Inline lists
- Inline maps
- Ternary operator
- Variables
- User defined functions
- Collection projection
- Collection selection
- Templated expressions
8.3 Expression Evaluation using Spring’s Expression Interface
このセクションではSpELインタフェースと式言語の簡単な使用例を解説します。完全な言語リファレンスはLanguage Referenceにあります。
以下のコードはリテラル文字列の式Hello Worldを評価するSpEL APIの例です。
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'"); String message = (String) exp.getValue();
message変数の値は単にHello Worldとなります。
最も使用する可能性の高いSpELクラスとインタフェースはorg.springframework.expression
パッケージとそのサブパッケージとspel.support
にあります。
インタフェースExpressionParser
は式の文字列をパースする役割を持ちます。この例では、式はシングルクオートで囲われた文字列リテラルです。インタフェースExpression
は事前に定義しておいた式を評価する役割を持ちます。parser.parseExpression
とexp.getValue
をそれぞれ呼び出す場合、ParseException
とEvaluationException
例外をスローする可能性があります。
SpELは、メソッド呼び出し・プロパティアクセス・コンストラクタ呼び出しなど、様々な機能を持ちます。
メソッド呼び出しの例としては、文字列リテラルでconcatメソッドがあります。
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('!')"); String message = (String) exp.getValue();
この場合message変数の値はHello World!となります。
JavaBeanプロパティ呼び出しの例としては、文字列プロパティのBytesを以下のように呼び出せます。
ExpressionParser parser = new SpelExpressionParser(); // invokes getBytes() Expression exp = parser.parseExpression("'Hello World'.bytes"); byte[] bytes = (byte[]) exp.getValue();
また、SpELはdot記法を使用してネストされたプロパティをサポートし、例えば、prop1.prop2.prop3でプロパティ値を設定します。
また、publicなフィールドアクセスも可能です。
ExpressionParser parser = new SpelExpressionParser(); // invokes getBytes().length Expression exp = parser.parseExpression("'Hello World'.bytes.length"); int length = (Integer) exp.getValue();
Stringのコンストラクタを文字列リテラルの代わりに使うことも可能です。
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); String message = exp.getValue(String.class);
ジェネリックメソッドpublic <T> T getValue(Class<T> desiredResultType)
を使っていることに注目してください。これを使用すると、式の値を型にキャストする必要が無くなります。もし値が型T
や型コンバーターでキャスト出来ない場合EvaluationException
をスローします。
SpELの良くある使い方は、(ルートオブジェクトと呼ばれる)オブジェクトインスタンスに対して評価を行う式を扱うものです。これには二種類のオプションがあり、式に対するオブジェクトが評価ごとに変化するかどうかで選択します。以下の例ではInventorインスタンスからname
プロパティを取得します。
// 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"); EvaluationContext context = new StandardEvaluationContext(tesla); String name = (String) exp.getValue(context);
最後の行では、文字列変数nameは"Nikola Tesla"になります。StandardEvaluationContextには"name"プロパティが評価されるオブジェクトを指定します。このメカニズムはルートオブジェクトが滅多に変更されない場合に使うもので、コンテキストにはただ一度だけ設定が可能です。ルートオブジェクトが何度も頻繁に変更される場合、次のサンプルで示すように、getValue
を呼び出すごとにオブジェクトを渡します。
// 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"); String name = (String) exp.getValue(tesla);
この場合にはinventor tesla
は直接getValue
に渡しており、式の評価の裏側でデフォルトの評価コンテキストを内部的に作成して管理を行うので、コンテキストを渡す必要はありません。
StandardEvaluationContextは相対的に生成が高コストなので、繰り返し使うスコープ期間内はキャッシュすることで後続の評価処理が高速になります。こうした理由により、StandardEvaluationContextは評価処理ごとに新しく生成するよりも、キャッシュや再利用が可能な場所ではなるべくそうして下さい。
設定済みの評価コンテキストを使いつつgetValue
呼び出し時に別のルートオブジェクトを渡すことも可能です。getValue
はどちらのやり方でも呼び出し可能です。この場合、呼び出し時に渡すルートオブジェクトは評価コンテキストで指定した(もしくはnull)オブジェクトをオーバーライドすると見なせます。
上記の説明におけるSpELの標準的な使い方では、パーサーやパーサー式や評価コンテキストにルートコンテキストオブジェクトを生成する必要があります。ただし、より一般的な使い方では、例えばSpringビーンやSpring Web Flow定義では、設定の一部としてSpELの式のみ指定します。この場合では、パーサー・評価コンテキスト・ルートオブジェクト・定義済み変数などは暗黙的にすべてセットアップされるので、ユーザは式以外を指定する必要はありません。
前置きにおける最後の例として、前述の例のInventorオブジェクトでboolean演算子を使う例を示します。
Expression exp = parser.parseExpression("name == 'Nikola Tesla'"); boolean result = exp.getValue(context, Boolean.class); // evaluates to true
8.3.1 The EvaluationContext interface
EvaluationContext
インタフェースは、プロパティ・メソッド・フィールド解決と型変換で式を評価する際に使われます。Spring標準実装のStandardEvaluationContext
はオブジェクト操作にリフレクションを使用し、パフォーマンス向上のためにjava.lang.reflect
のMethod
・Field
・Constructor
をキャッシュします。
StandardEvaluationContext
には、コンストラクタにルートオブジェクトを渡すか、setRootObject()
メソッドで評価するルートオブジェクトを指定します。また、式で使う変数と関数をsetVariable()
とregisterFunction()
メソッドで指定します。変数と関数の使い方はlanguage reference sectionsのVariablesとFunctionsに解説があります。StandardEvaluationContext
には、SpEL式評価を拡張するConstructorResolvers
, MethodResolvers
, PropertyAccessor
も登録可能です。これらのクラスの詳細についてはjavadocを参照してください。
Type Conversion
デフォルトではSpELは変換サービスにはSpringコア(org.springframework.core.convert.ConversionService
)のものを使います。この変換サービスは、一般的な変換を行う多数のコンバーターで構成され、型間でカスタム変換を追加可能な拡張性があります。加えて、ジェネリクスも使用可能です。つまり、式でジェネリクス型を使う場合、SpELはオブジェクトの型の正しさを維持して変換を行います。
setValue()
でList
プロパティに設定する代入を例に取ります。プロパティの型は実際にはList<Boolean>
です。SpELは、代入を行う前に、リストの要素をBoolean
に変換する必要があると解釈します。以下が簡単な例です。
class Simple { public List<Boolean> booleanList = new ArrayList<Boolean>(); } Simple simple = new Simple(); simple.booleanList.add(true); StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); // ここでは文字列としてfalseを渡している。SpELと変換サービスは文字列のfalseを // 変換してBooleanにする必要があると正しく解釈する。 rser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); // bはfalseとなる。 Boolean b = simple.booleanList.get(0);
8.3.2 Parser configuration
SpEL式パーサーはパーサー設定オブジェクト(org.springframework.expression.spel.SpelParserConfiguration
)を使用して設定が可能です。設定オブジェクトは式コンポーネントの振る舞いのいくつかを制御します。たとえば、指定インデックスの要素や配列またはコレクションのインデックス参照先がnull
の場合、自動的に要素を生成できます。これはプロパティ参照チェーンで構成される式を使う場合に有効です。また、配列やリストサイズを超えたインデックス指定をした場合、自動的にそのインデックスまで配列やリストのサイズを拡張することも可能です。
class Demo { public List<String> list; } // 引数指定: // - null参照で自動初期化 // - コレクションの自動拡張 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は4個のコレクションになる。 // 各エントリは空文字列。
また、SpEL式コンパイラの振る舞いを設定することも可能です。
8.3.3 SpEL compilation
Spring Framework 4.1にはベーシックな式コンパイラが含まれます。式は通常は動的な柔軟性を持つインタープリターで処理されますが、パフォーマンス最適化は行いません。たまにしか使わない式ならそれで良いですが、Springインテグレーションなど他コンポーネントで使われる場合、パフォーマンスは非常に重要であり、動的さはさほど必要にはなりません。
新しいSpELコンパイラーはそうした要求を視野に入れています。コンパイラは式の振る舞いを実体化する処理の裏側でJavaクラスを生成し、式評価を高速化するのに使います。式には型情報が欠落しているので、コンパイラはコンパイル実行時に式のインタープリター評価中に集めた型情報を使います。たとえば、式からは全くプロパティ参照の型が分からなくとも、初回のインタープリタ評価中に型は分かります。当然ながら、この方法に基づくコンパイルでは式の型が後々に変更された場合にトラブルの原因になりえます。そのため、コンパイル方式は繰り返し評価する際に型情報が変化しない式に適しています。
一般的な式は以下のようになります。
someArray[0].someProperty.someOtherProperty < 0.1
上記は、配列にアクセスし、プロパティ参照と数値演算を行い、パフォーマンスは大変良好になると思われます。50000回のマイクロベンチマークでは、インタープリタを用いた評価では75msかかり、式のコンパイルバージョンを使用した場合は3msとなりました。
Compiler configuration
コンパイラはデフォルトでは作動せず、有効化するには二種類の方法があります。前述のパーサー設定プロセスを使用するか、別のコンポーネント内に組み込むSpELではシステムプロパティで設定します。このセクションでは両者について解説します。
重要なのはコンパイラモードが幾つかある点で、enumに定義されています(org.springframework.expression.spel.SpelCompilerMode
)。モードは以下の通りです。
OFF
- コンパイラは無効。デフォルト。IMMEDIATE
- immediateモードでは可能な限り式がコンパイルされます。通常は初回のインタープリタ評価後にコンパイルが行われます。もしコンパイル済みの式が失敗する(上述の型変換など)場合、式評価の呼び出し元が例外をスローします。MIXED
- mixedモードでは、式はインタープリターとコンパイルモードに自動的にスイッチします。一定数のインタープリター実行後にコンパイル形式になり、コンパイル形式に何らかの障害(型が変更された)があると、再度コンパイルからインタープリターに戻ります。そのあと別のコンパイル形式が生成されるとコンパイルにスイッチします。基本的に、IMMEDIATE
モードでユーザが受け取る例外は内部的に処理されます。
MIXED
モードが副作用を持つ式で問題を発生させる場合のためにIMMEDIATE
モードは存在します。コンパイル済みの式が部分的に成功したあと失敗する場合、既にシステムに何らかの状態変更を行った後である可能性があります。これが発生した場合、呼び出し元はインタープリターモードで暗黙的な再実行を望まない筈で、なぜなら式の一部が二回実行されてしまうためです。
使うモードを選んだ後、パーサーを設定時にSpelParserConfiguration
を使います。
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);
コンパイラーモード指定時にクラスローダーも指定可能です(nullも指定可能)。コンパイル済の式は、引数で指定したクラスローダー下に作成される子クラスローダーに定義されます*2。クラスローダーを指定する場合、式評価処理が使うすべての型を参照可能にしてください。クラスローダー指定が無い場合はデフォルトのクラスローダー(通常は式評価を実行するスレッドのコンテキストクラスローダー)が使われます。
コンパイラを設定するもう一つの方法は、別のコンポーネント内に組み込むSpELで使うもので、この場合は設定オブジェクトでは設定出来ません。この場合にはシステムプロパティを使います。spring.expression.compiler.mode
プロパティにSpelCompilerMode
enum(off
, immediate
, mixed
)のうち一つを設定します。
Compiler limitations
Spring Framework 4.1では基本的なコンパイルフレームワークが最初から組み込まれています。しかし、フレームワークは式のあらゆる種類のコンパイルをサポートしているわけではありません。パフォーマンスがクリティカルな文脈で使われうる一般的な式に当初はフォーカスしていました。式のうちいくつかは現時点ではコンパイル出来ません。
- expressions involving assignment
- expressions relying on the conversion service
- expressions using custom resolvers or accessors
- expressions using selection or projection
将来的にはそうした式もコンパイル可能になると思われます。
8.4 Expression support for defining bean definitions
SpEL式はXMLやアノテーション設定メタデータでBeanDefinition
を定義するのに使用可能です。両方式共にシンタックスは#{ <expression string> }
形式で定義します。
8.4.1 XML based configuration
プロパティやコンストラクタ引数の値は以下のような式で設定します。
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- other properties --> </bean>
組み込み変数systemProperties
を以下のように式で使用可能です。注意点として、以下のようなコンテキストでは組み込み変数のプレフィクスに#
は必要ありません。
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <!-- other properties --> </bean>
by nameで他ビーンのプロパティを参照することも可能です。
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="{ T(java.lang.Math).random() * 100.0 }"/> <!-- other properties --> </bean> <bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> <property name="initialShapeSeed" value="{ numberGuess.randomNumber }"/> <!-- other properties --> </bean>
8.4.2 Annotation-based configuration
@Value
アノテーションは、フィールド・メソッド・メソッド/コンストラクタ引数のデフォルト値指定、に設定可能です。
以下はフィールド変数にデフォルト値を設定する例です。
public static class FieldValueTestBean @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
以下は上記と同等なことをプロパティのセッターメソッドで行う例です。
public static class PropertyValueTestBean private String defaultLocale; @Value("#{ systemProperties['user.region'] }") public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
オートワイヤメソッドとコンストラクタにも@Value
アノテーションを使用可能です。
public class SimpleMovieLister { private MovieFinder movieFinder; private String defaultLocale; @Autowired public void configure(MovieFinder movieFinder, @Value("#{ systemProperties['user.region'] }") String defaultLocale) { this.movieFinder = movieFinder; this.defaultLocale = defaultLocale; } // ... }
public class MovieRecommender { private String defaultLocale; private CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, @Value("#{systemProperties['user.country']}") String defaultLocale) { this.customerPreferenceDao = customerPreferenceDao; this.defaultLocale = defaultLocale; } // ... }
8.5 Language Reference
8.5.1 Literal expressions
リテラル式の型は、文字列・日付・数値(整数・実数・16進数)・真偽値・null、をサポートしています。文字列はシングルクオートで区切ります。文字列内にシングルクオートを含めるにはシングルクオートを二つ重ねます。以下にリテラルの簡単な使用例を示します。実際には以下のように独立して使わず、より複雑な式の一部として、例えば論理比較演算子のどちらかの辺のリテラル値などに使います。
ExpressionParser parser = new SpelExpressionParser(); // evals to "Hello World" String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); // evals to 2147483647 int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); Object nullValue = parser.parseExpression("null").getValue();
数値は、負号・指数表記・小数点、をサポートします。デフォルトでは実数はDouble.parseDouble()でパースします。
8.5.2 Properties, Arrays, Lists, Maps, Indexers
プロパティ参照は簡単で、ネストされたプロパティ値の指定にはピリオドを使います。Inventor
クラスのインスタンスのpupinとteslaはClasses used in the examples.セクションに示すデータで処理を行います。Teslaの誕生年とPupinの誕生地を得るには以下の式を使います。
// evals to 1856 int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
プロパティ名の最初の文字は大文字小文字を無視します。配列とリストの中身は角括弧で取得します。
ExpressionParser parser = new SpelExpressionParser(); // Inventions Array StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla); // evaluates to "Induction motor" String invention = parser.parseExpression("inventions[3]").getValue( teslaContext, String.class); // Members List StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee); // evaluates to "Nikola Tesla" String name = parser.parseExpression("Members[0].Name").getValue( societyContext, String.class); // List and Array navigation // evaluates to "Wireless communication" String invention = parser.parseExpression("Members[0].Inventions[6]").getValue( societyContext, String.class);
mapの中身はブラケット内にキー値リテラルを指定することで取得します。この例の場合、Officersのmapのキーは文字列なので、文字列リテラルを指定可能です。
// Officer's Dictionary Inventor pupin = parser.parseExpression("Officers['president']").getValue( societyContext, Inventor.class); // evaluates to "Idvor" String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue( societyContext, String.class); // setting values parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue( societyContext, "Croatia");
8.5.3 Inline lists
リストは{}
を用いた式で表現できます。
// evaluates to a Java list containing the four numbers List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
{}
単独は空リストを意味します。パフォーマンス上の理由により、固定リテラルのみのリストの場合は、各評価ごとに新規リストを作らず、式を表現するのに定数リストを作ります。
8.5.4 Inline Maps
mapは{key:value}
を使う式で表現できます。
// evaluates to a Java map containing the two entries Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context); Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
{:}
単独は空mapを意味します。パフォーマンス上の理由により、固定リテラルもしくは他のネストされた定数構造(リストかmap)のmapの場合は、各評価ごとに新規mapを作らず、式を表現するのに定数mapを作ります。mapのキーはクオートで囲んでも囲まなくても良く、上記の例はクオートでキーを囲んでいません。
8.5.5 Array construction
配列はおなじみのJavaシンタックスで作成可能で、必要であればイニシャライザを指定できます。
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); // Array with initializer int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); // Multi dimensional array int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
今のところ多次元配列ではイニシャライズを指定できません。
8.5.6 Methods
メソッドは一般的なJavaプログラミングのシンタックスで呼び出します。メソッド呼び出しの引数にリテラルが使えます。また、Varargsもサポートしています。
8.5.7 Operators
Relational operators
比較演算子は標準演算子の記法、==, !=, <, <=, > >=、をサポートします。
// evaluates to true boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); // evaluates to false boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class); // evaluates to true boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
標準比較演算子に加えて、SpELはinstanceof
とmatches
ベースの正規表現をサポートします。
// evaluates to false boolean falseValue = parser.parseExpression( "'xyz' instanceof T(int)").getValue(Boolean.class); // evaluates to true boolean trueValue = parser.parseExpression( "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); //evaluates to false boolean falseValue = parser.parseExpression( "'5.0067' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
なお、演算子はアルファベットで同等な指定が可能です。ドキュメントタイプ(XMLなど)によっては式中の演算子が特別な意味を持つ場合を回避するためにこの指定を使います。lt
(<
), gt
(>
), le
(<=
), ge
(>=
), eq
(==
), ne
(!=
), div
(/
), mod
(%
), not
(!
) これらは大文字小文字を区別しません。
Logical operators
論理演算子はand, or, notをサポートします。使用例は以下の通りです。
// -- AND -- // evaluates to false boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); // evaluates to true String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- OR -- // evaluates to true boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); // evaluates to true String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- NOT -- // evaluates to false boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class); // -- AND and NOT -- String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Mathematical operators
加算演算子は数値と文字列で使用可能です。減算・乗算・除算は数値でのみ使用可能です。他の算術演算子には剰余(%)と指数(^)があります。標準演算子の順序が適用されます。これら演算子の使用例は以下の通りです。
// Addition int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 String testString = parser.parseExpression( "'test' + ' ' + 'string'").getValue(String.class); // test string // Subtraction int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 // Multiplication int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 // Division int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 // Modulus int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 // Operator precedence int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
8.5.8 Assignment
プロパティの設定は代入演算子を使います。通常setValue
で使いますが、getValue
でも使用可能です。
Inventor inventor = new Inventor(); StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor); parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2"); // alternatively String aleks = parser.parseExpression( "Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
8.5.9 Types
特殊なT
演算子はjava.lang.Classインスタンス(つまり"型")を指定するのに使います。staticメソッドはこの演算子を使用して呼び出します。StandardEvaluationContext
は型の参照にTypeLocator
を使用し、StandardTypeLocator
はjava.langパッケージの指定は不要です(((which can be replaced) is built with an understanding of the java.lang package.が原文。すぐ次の文にあるようにjava.langの指定は不要という意味なので意訳した))。つまりT()でjava.lang下の型を参照する際は完全修飾名を必要とせず、他の型については完全修飾名が必要、という意味です。
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); boolean trueValue = parser.parseExpression( "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") .getValue(Boolean.class);
8.5.10 Constructors
コンストラクタはnew演算子で呼び出します。常に完全修飾クラス名を使うべきですがプリミティブ型と文字列はその限りではありません。
Inventor einstein = p.parseExpression( "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')") .getValue(Inventor.class); //create new inventor instance within add method of List p.parseExpression( "Members.add(new org.spring.samples.spel.inventor.Inventor( 'Albert Einstein', 'German'))").getValue(societyContext);
8.5.11 Variables
式中で変数は#variableName
で参照します。変数はStandardEvaluationContext
のsetVariableメソッドで設定します。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); StandardEvaluationContext context = new StandardEvaluationContext(tesla); context.setVariable("newName", "Mike Tesla"); parser.parseExpression("Name = #newName").getValue(context); System.out.println(tesla.getName()) // "Mike Tesla"
The #this and #root variables
変数#thisは常に利用可能で現在の評価オブジェクトを指します(against which unqualified references are resolved)*3。#rootは常にルートコンテキストオブジェクトを指します。#thisは評価式のコンポーネントとして変化しますが、#rootは常にrootを指します。
// create an array of integers List<Integer> primes = new ArrayList<Integer>(); primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); // create parser and set variable primes as the array of integers ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("primes",primes); // all prime numbers > 10 from the list (using selection ?{...}) // evaluates to [11, 13, 17] List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression( "#primes.?[#this>10]").getValue(context);
8.5.12 Functions
式内で呼び出せるユーザ定義関数を登録することでSpELを拡張できます。関数は以下のメソッドによりStandardEvaluationContext
に登録されます。
public void registerFunction(String name, Method m)
上記のMethodが関数の実装です。たとえば、文字列を逆にするユーティリティーメソッドは以下の通りです。
public abstract class StringUtils { public static String reverseString(String input) { StringBuilder backwards = new StringBuilder(); for (int i = 0; i < input.length(); i++) backwards.append(input.charAt(input.length() - 1 - i)); } return backwards.toString(); } }
このメソッドを評価コンテキストに登録することで式から利用可能になります。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.registerFunction("reverseString", StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class })); String helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String.class);
8.5.13 Bean references
評価コンテキストがビーンリゾルバ―に登録されている場合、式から(@)でビーンをルックアップできます。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation Object bean = parser.parseExpression("@foo").getValue(context);
8.5.14 Ternary Operator (If-Then-Else)
式内でif-then-else条件分岐を行うのに三項演算子が使えます。
String falseString = parser.parseExpression( "false ? 'trueExp' : 'falseExp'").getValue(String.class);
上記の例の場合、booleanのfalseは文字列falseExpを返します。より現実的な例は以下の通りです。
parser.parseExpression("Name").setValue(societyContext, "IEEE"); societyContext.setVariable("queryName", "Nikola Tesla"); expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; String queryResultString = parser.parseExpression(expression) .getValue(societyContext, String.class); // queryResultString = "Nikola Tesla is a member of the IEEE Society"
また、三項演算子の短縮版としてElvis operatorがあります。
8.5.15 The Elvis Operator
Elvis operatorは三項演算子の短縮版でGroovy)で使われています。三項演算子では変数を二度繰り返す必要があります。例えば、
String name = "Elvis Presley"; String displayName = name != null ? name : "Unknown";
代わりにElvis operatorを使用可能で、この名前の由来はエルビス・プレスリーの髪型*4から来ています。
ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("null?:'Unknown'").getValue(String.class); System.out.println(name); // Unknown
以下はより複雑な例です。
ExpressionParser parser = new SpelExpressionParser(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); StandardEvaluationContext context = new StandardEvaluationContext(tesla); String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); System.out.println(name); // Nikola Tesla tesla.setName(null); name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); System.out.println(name); // Elvis Presley
8.5.16 Safe Navigation operator
Safe Navigation演算子はNullPointerException
を回避するためのものでGroovy)から来ています。通常、何らかのオブジェクト参照を使うときそのオブジェクトのメソッドやプロパティにアクセスする前にnullチェックをする必要があります。これを避けるため、Safe Navigation演算子は例外をスローする代わりにnullを返します。
ExpressionParser parser = new SpelExpressionParser(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); StandardEvaluationContext context = new StandardEvaluationContext(tesla); String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); System.out.println(city); // Smiljan tesla.setPlaceOfBirth(null); city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); System.out.println(city); // null - does not throw NullPointerException!!!
Elvis operatorは@Value
などの式におけるデフォルト値として使用可能です。
@Value("#{systemProperties['pop3.port'] ?: 25}")
システムプロパティpop3.port
が定義されていればそれを使い、そうでなければ25をインジェクトします。
8.5.17 Collection Selection
Selectionは強力な式言語の機能で、あるコレクションを選択したエントリで別のコレクションに変換します。
Selectionのシンタックスは?[selectionExpression]
です。コレクションをフィルターして元のコレクションのサブセットで新規コレクションを返します。例えば、SelectionによりSerbian inventorsのリストを簡単に作れます。
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
Selectionリストとmapの両方で使えます。リストでのSelectionは要素それぞれに対して評価が行われ、mapではmapのentry(Map.Entry
オブジェクト)に対して評価が行われます。mapエントリはプロパティとしてアクセス可能なキーと値を持ち、Selectionではそれらを使います。
以下の式は元のmapからentryの値が27未満の要素から成る新しいmapを返します。
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
全要素選択に加えて、最初や最後の値を取得することも可能です。selectionしたもののうち最初にマッチするエントリを取得するには^[...]
を使い、最後にマッチする要素を取得するには$[...]
です。
8.5.18 Collection Projection
Projectionはコレクションでサブ式を評価し、その結果を新しいコレクションとして返します。projectionのシンタックスは![projectionExpression]
です。簡単な例として、inventorsのリストから生まれた都市のリストが欲しい、とします。これをするにはinventorリストの各要素にplaceOfBirth.cityを評価する必要があります。この場合にはprojectionを使います。
// returns [Smiljan, Idvor ] List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
mapでもprojectionは使用可能で、mapの各要素(Map.Entry
オブジェクト)に対して評価が行われます。mapに対するprojectionの結果は、mapエントリの各要素に対してprojection式を評価した要素から成るリストが返されます。
8.5.19 Expression templating
Expression templatesにより、7一つ上以上の評価ブロックにリテラルテキストを入れられます。各評価ブロックは自前で定義するプレフィクスとサフィックスのデリミタで囲み、デリミタには慣例的に#{ }
が使われます。例えば、
String randomPhrase = parser.parseExpression( "random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class); // evaluates to "random number is 0.7038186818312008"
リテラルテキスト'random number is 'に#{ }デリミタ内の式の評価結果を連結したものが評価されます。#{ }デリミタ内の式はrandom()を呼び出します。parseExpression()
の第二引数はParserContext
型です。ParserContext
インタフェースはexpression templating機能をサポートする式のパース手順です。TemplateParserContext
の定義は以下の通りです。
public class TemplateParserContext implements ParserContext { public String getExpressionPrefix() { return "#{"; } public String getExpressionSuffix() { return "}"; } public boolean isTemplate() { return true; } }
8.6 Classes used in the examples
Inventor.java
package org.spring.samples.spel.inventor; import java.util.Date; import java.util.GregorianCalendar; public class Inventor { private String name; private String nationality; private String[] inventions; private Date birthdate; private PlaceOfBirth placeOfBirth; public Inventor(String name, String nationality) { GregorianCalendar c= new GregorianCalendar(); this.name = name; this.nationality = nationality; this.birthdate = c.getTime(); } public Inventor(String name, Date birthdate, String nationality) { this.name = name; this.nationality = nationality; this.birthdate = birthdate; } public Inventor() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNationality() { return nationality; } public void setNationality(String nationality) { this.nationality = nationality; } public Date getBirthdate() { return birthdate; } public void setBirthdate(Date birthdate) { this.birthdate = birthdate; } public PlaceOfBirth getPlaceOfBirth() { return placeOfBirth; } public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) { this.placeOfBirth = placeOfBirth; } public void setInventions(String[] inventions) { this.inventions = inventions; } public String[] getInventions() { return inventions; } }
PlaceOfBirth.java
package org.spring.samples.spel.inventor; public class PlaceOfBirth { private String city; private String country; public PlaceOfBirth(String city) { this.city=city; } public PlaceOfBirth(String city, String country) { this(city); this.country = country; } public String getCity() { return city; } public void setCity(String s) { this.city = s; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } }
Society.java
package org.spring.samples.spel.inventor; import java.util.*; public class Society { private String name; public static String Advisors = "advisors"; public static String President = "president"; private List<Inventor> members = new ArrayList<Inventor>(); private Map officers = new HashMap(); public List getMembers() { return members; } public Map getOfficers() { return officers; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isMember(String name) { for (Inventor inventor : members) { if (inventor.getName().equals(name)) { return true; } } return false; } }