1. 概要

Spring Expression Language(SpEL)は、実行時にオブジェクトグラフのクエリと操作をサポートする強力な式言語です。 XMLまたは注釈ベースのSpring構成で使用できます。

この言語で使用できる演算子はいくつかあります。

タイプ オペレーター
算術 +、-、*、/、%、^、div、mod
関連した <、>、==、!=、<=、> =、lt、gt、eq、ne、le、ge
論理的 そして、または、ではなく、&&、|| 、!
条件付き ?:
正規表現 一致する

2. オペレーター

これらの例では、アノテーションベースの構成を使用します。 XML構成の詳細については、この記事の後のセクションを参照してください。

SpEL式は、記号で始まり、中かっこで囲まれています:#{expression}

プロパティは、 $ 記号で始まり、中かっこで囲む $ {property.name} から、同様の方法で参照できます。

プロパティプレースホルダーにSpEL式を含めることはできませんが、式にプロパティ参照を含めることができます。

#{${someProperty} + 2}

上記の例では、 someProperty の値が2であると想定しているため、結果の式は2 + 2になり、4と評価されます。

2.1. 算術演算子

SpELは、すべての基本的な算術演算子をサポートしています。

@Value("#{19 + 1}") // 20
private double add; 

@Value("#{'String1 ' + 'string2'}") // "String1 string2"
private String addString; 

@Value("#{20 - 1}") // 19
private double subtract;

@Value("#{10 * 2}") // 20
private double multiply;

@Value("#{36 / 2}") // 19
private double divide;

@Value("#{36 div 2}") // 18, the same as for / operator
private double divideAlphabetic; 

@Value("#{37 % 10}") // 7
private double modulo;

@Value("#{37 mod 10}") // 7, the same as for % operator
private double moduloAlphabetic; 

@Value("#{2 ^ 9}") // 512
private double powerOf;

@Value("#{(2 + 2) * 2 + 9}") // 17
private double brackets;

除算およびモジュロ演算にはアルファベットのエイリアスがあり、/の場合はdivの場合はmodです。 + 演算子を使用して、文字列を連結することもできます。

2.2. 関係演算子と論理演算子

SpELは、すべての基本的なリレーショナルおよび論理演算もサポートします。

@Value("#{1 == 1}") // true
private boolean equal;

@Value("#{1 eq 1}") // true
private boolean equalAlphabetic;

@Value("#{1 != 1}") // false
private boolean notEqual;

@Value("#{1 ne 1}") // false
private boolean notEqualAlphabetic;

@Value("#{1 < 1}") // false
private boolean lessThan;

@Value("#{1 lt 1}") // false
private boolean lessThanAlphabetic;

@Value("#{1 <= 1}") // true
private boolean lessThanOrEqual;

@Value("#{1 le 1}") // true
private boolean lessThanOrEqualAlphabetic;

@Value("#{1 > 1}") // false
private boolean greaterThan;

@Value("#{1 gt 1}") // false
private boolean greaterThanAlphabetic;

@Value("#{1 >= 1}") // true
private boolean greaterThanOrEqual;

@Value("#{1 ge 1}") // true
private boolean greaterThanOrEqualAlphabetic;

すべての関係演算子には、アルファベットのエイリアスもあります。 たとえば、XMLベースの構成では、山かっこを含む演算子を使用できません( < <= >> > = )。 代わりに、 lt (より小さい)、 le (以下)、 gt (より大きい)、またはgeを使用できます。 (以上)。

2.3. 論理演算子

SpELは、すべての基本的な論理演算もサポートしています。

@Value("#{250 > 200 && 200 < 4000}") // true
private boolean and; 

@Value("#{250 > 200 and 200 < 4000}") // true
private boolean andAlphabetic;

@Value("#{400 > 300 || 150 < 100}") // true
private boolean or;

@Value("#{400 > 300 or 150 < 100}") // true
private boolean orAlphabetic;

@Value("#{!true}") // false
private boolean not;

@Value("#{not true}") // false
private boolean notAlphabetic;

算術演算子や関係演算子と同様に、すべての論理演算子にもアルファベットのクローンがあります。

2.4. 条件付き演算子

条件に応じて異なる値を注入するために、条件演算子を使用します。

@Value("#{2 > 1 ? 'a' : 'b'}") // "a"
private String ternary;

式内でコンパクトなif-then-else条件付きロジックを実行するために、三項演算子を使用します。 この例では、trueがあったかどうかを確認しようとしています。

三項演算子のもう1つの一般的な使用法は、変数が null であるかどうかを確認してから、変数値またはデフォルトを返すことです。

@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
private String ternary;

エルビス演算子は、Groovy言語で使用される上記の場合の三項演算子構文を短縮する方法です。 SpELでも利用できます。

このコードは、上記のコードと同等です。

@Value("#{someBean.someProperty ?: 'default'}") // Will inject provided string if someProperty is null
private String elvis;

2.5. SpELでの正規表現の使用

matches 演算子を使用して、文字列が特定の正規表現に一致するかどうかを確認できます。

@Value("#{'100' matches '\\d+' }") // true
private boolean validNumericStringResult;

@Value("#{'100fghdjf' matches '\\d+' }") // false
private boolean invalidNumericStringResult;

@Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }") // true
private boolean validAlphabeticStringResult;

@Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }") // false
private boolean invalidAlphabeticStringResult;

@Value("#{someBean.someValue matches '\d+'}") // true if someValue contains only digits
private boolean validNumericValue;

2.6. リストおよびマップオブジェクトへのアクセス

SpELを使用すると、コンテキスト内の任意のMapまたはListのコンテンツにアクセスできます。

新しいbeanworkerHolder を作成します。これは、一部のワーカーとその給与に関する情報をリストマップに格納します。

@Component("workersHolder")
public class WorkersHolder {
    private List<String> workers = new LinkedList<>();
    private Map<String, Integer> salaryByWorkers = new HashMap<>();

    public WorkersHolder() {
        workers.add("John");
        workers.add("Susie");
        workers.add("Alex");
        workers.add("George");

        salaryByWorkers.put("John", 35000);
        salaryByWorkers.put("Susie", 47000);
        salaryByWorkers.put("Alex", 12000);
        salaryByWorkers.put("George", 14000);
    }

    //Getters and setters
}

これで、SpELを使用してコレクションの値にアクセスできます。

@Value("#{workersHolder.salaryByWorkers['John']}") // 35000
private Integer johnSalary;

@Value("#{workersHolder.salaryByWorkers['George']}") // 14000
private Integer georgeSalary;

@Value("#{workersHolder.salaryByWorkers['Susie']}") // 47000
private Integer susieSalary;

@Value("#{workersHolder.workers[0]}") // John
private String firstWorker;

@Value("#{workersHolder.workers[3]}") // George
private String lastWorker;

@Value("#{workersHolder.workers.size()}") // 4
private Integer numberOfWorkers;

3. Spring構成での使用

3.1. Beanの参照

この例では、XMLベースの構成でSpELを使用する方法を見ていきます。 式を使用して、beanまたはbeanフィールド/メソッドを参照できます。

たとえば、次のクラスがあるとします。

public class Engine {
    private int capacity;
    private int horsePower;
    private int numberOfCylinders;

   // Getters and setters
}

public class Car {
    private String make;
    private int model;
    private Engine engine;
    private int horsePower;

   // Getters and setters
}

次に、式を使用して値を挿入するアプリケーションコンテキストを作成します。

<bean id="engine" class="com.baeldung.spring.spel.Engine">
   <property name="capacity" value="3200"/>
   <property name="horsePower" value="250"/>
   <property name="numberOfCylinders" value="6"/>
</bean>
<bean id="someCar" class="com.baeldung.spring.spel.Car">
   <property name="make" value="Some make"/>
   <property name="model" value="Some model"/>
   <property name="engine" value="#{engine}"/>
   <property name="horsePower" value="#{engine.horsePower}"/>
</bean>

someCarBeanを見てください。 someCarengineおよびhorsePowerフィールドは、 enginebeanおよびhorsePowerフィールドへのBean参照である式を使用しますそれぞれ。

アノテーションベースの構成で同じことを行うには、 @Value(“#{expression}”)アノテーションを使用します。

3.2. 構成での演算子の使用

この記事の最初のセクションの各演算子は、XMLおよび注釈ベースの構成で使用できます。

ただし、XMLベースの構成では、山かっこ演算子「<」は使用できないことに注意してください。 代わりに、 lt (未満)や le (以下)などのアルファベットのエイリアスを使用する必要があります。

注釈ベースの構成の場合、そのような制限はありません。

public class SpelOperators {
    private boolean equal;
    private boolean notEqual;
    private boolean greaterThanOrEqual;
    private boolean and;
    private boolean or;
    private String addString;
    
    // Getters and setters
    @Override
    public String toString() {
        // toString which include all fields
    }

次に、spelOperatorsBeanをアプリケーションコンテキストに追加します。

<bean id="spelOperators" class="com.baeldung.spring.spel.SpelOperators">
   <property name="equal" value="#{1 == 1}"/>
   <property name="notEqual" value="#{1 lt 1}"/>
   <property name="greaterThanOrEqual" value="#{someCar.engine.numberOfCylinders >= 6}"/>
   <property name="and" value="#{someCar.horsePower == 250 and someCar.engine.capacity lt 4000}"/>
   <property name="or" value="#{someCar.horsePower > 300 or someCar.engine.capacity > 3000}"/>
   <property name="addString" value="#{someCar.model + ' manufactured by ' + someCar.make}"/>
</bean>

コンテキストからそのBeanを取得すると、値が適切に挿入されたことを確認できます。

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SpelOperators spelOperators = (SpelOperators) context.getBean("spelOperators");

ここで、 spelOperatorsBeanのtoStringメソッドの出力を確認できます。

[equal=true, notEqual=false, greaterThanOrEqual=true, and=true, 
or=true, addString=Some model manufactured by Some make]

4. プログラムによる式の解析

場合によっては、構成のコンテキスト外で式を解析したいことがあります。 幸い、これはSpelExpressionParserを使用して可能です。

前の例で見たすべての演算子を使用できますが、中括弧とハッシュ記号なしで使用する必要があります。 つまり、Spring構成で使用するときに + 演算子で式を使用する場合、構文は#{1 +1}です。 構成外で使用する場合、構文は単純に 1 +1です。

次の例では、前のセクションで定義したCarおよびEngineBeanを使用します。

4.1. ExpressionParserの使用

簡単な例を見てみましょう。

ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue();

ExpressionParser は、式文字列の解析を担当します。 この例では、SpELパーサーは文字列‘AnyString’を式として評価するだけです。 当然のことながら、結果は‘AnyString’になります。

構成でSpELを使用する場合と同様に、SpELを使用して、メソッドを呼び出したり、プロパティにアクセスしたり、コンストラクターを呼び出したりできます。

Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue();

さらに、リテラルを直接操作する代わりに、コンストラクターを呼び出すことができます。

Expression expression = expressionParser.parseExpression("new String('Any string').length()");

同様に、Stringクラスのbytesプロパティにアクセスして、文字列のbyte[]表現を作成することもできます。

Expression expression = expressionParser.parseExpression("'Any string'.bytes");
byte[] result = (byte[]) expression.getValue();

通常のJavaコードと同じように、メソッド呼び出しを連鎖させることができます。

Expression expression = expressionParser.parseExpression("'Any string'.replace(\" \", \"\").length()");
Integer result = (Integer) expression.getValue();

この場合、空白を空の文字列に置き換えたため、結果は9になります。

式の結果をキャストしたくない場合は、一般的な方法を使用できます T getValue(Class desiredResultType) 、ここで、返される必要のあるクラスのタイプを提供できます。

戻り値をdesiredResultTypeにキャストできない場合、EvaluationExceptionがスローされることに注意してください。

Integer result = expression.getValue(Integer.class);

最も一般的な使用法は、特定のオブジェクトインスタンスに対して評価される式文字列を提供することです。

Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);

ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("model");

EvaluationContext context = new StandardEvaluationContext(car);
String result = (String) expression.getValue(context);

この場合、結果はcarオブジェクトのmodelフィールドの値「Model3」と等しくなります。 StandardEvaluationContext クラスは、式が評価されるオブジェクトを指定します。

コンテキストオブジェクトの作成後に変更することはできません。 StandardEvaluationContext は構築にコストがかかり、繰り返し使用すると、キャッシュされた状態が構築され、後続の式の評価をより迅速に実行できるようになります。 キャッシュがあるため、ルートオブジェクトが変更されない場合は、可能な場合はStandardEvaluationContextを再利用することをお勧めします。

ただし、ルートオブジェクトが繰り返し変更される場合は、次の例に示すメカニズムを使用できます。

Expression expression = expressionParser.parseExpression("model");
String result = (String) expression.getValue(car);

ここでは、SpEL式を適用するオブジェクトを表す引数を使用してgetValueメソッドを呼び出します。

以前と同じように、汎用のgetValueメソッドを使用することもできます。

Expression expression = expressionParser.parseExpression("yearOfProduction > 2005");
boolean result = expression.getValue(car, Boolean.class);

4.2. ExpressionParserを使用して値を設定する

式の解析によって返されたExpressionオブジェクトのsetValueメソッドを使用して、オブジェクトに値を設定できます。 SpELが型変換を処理します。 デフォルトでは、SpELはorg.springframework.core.convert.ConversionServiceを使用します。 タイプ間で独自のカスタムコンバーターを作成できます。 ConversionService はジェネリックスに対応しているため、ジェネリックスで使用できます。

実際にそれをどのように行うかを見てみましょう。

Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);

CarPark carPark = new CarPark();
carPark.getCars().add(car);

StandardEvaluationContext context = new StandardEvaluationContext(carPark);

ExpressionParser expressionParser = new SpelExpressionParser();
expressionParser.parseExpression("cars[0].model").setValue(context, "Other model");

結果の車のオブジェクトは、「モデル3」から変更されたモデルその他のモデル」になります。

4.3. パーサー構成

次の例では、このクラスを使用します。

public class CarPark {
    private List<Car> cars = new ArrayList<>();

    // Getter and setter
}

設定することが可能です ExpressionParser コンストラクタを呼び出すことによって SpelParserConfiguration 物体

たとえば、パーサーを構成せずにcarオブジェクトをCarParkクラスのcars配列に追加しようとすると、次のようなエラーが発生します。

EL1025E:(pos 4): The collection has '0' elements, index '0' is invalid

パーサーの動作を変更して、指定したインデックスがnullの場合に要素を自動的に作成できるようにしたり( autoGrowNullReferences 、コンストラクターの最初のパラメーター)、要素に対応するように配列またはリストを自動的に拡張したりできます。初期サイズを超えています( autoGrowCollections 、2番目のパラメーター):

SpelParserConfiguration config = new SpelParserConfiguration(true, true);
StandardEvaluationContext context = new StandardEvaluationContext(carPark);

ExpressionParser expressionParser = new SpelExpressionParser(config);
expressionParser.parseExpression("cars[0]").setValue(context, car);

Car result = carPark.getCars().get(0);

結果のcarオブジェクトは、carParkオブジェクトのcars配列の最初の要素として設定されたcarオブジェクトと等しくなります。前の例。

5. 結論

SpELは、Springポートフォリオのすべての製品で使用できる、強力で十分にサポートされている表現言語です。 これを使用して、Springアプリケーションを構成したり、任意のアプリケーションでより一般的なタスクを実行するパーサーを作成したりできます。

この記事のコードサンプルは、リンクされたGitHubリポジトリで入手できます。