メインページに戻る | Spring Framework & Spring Bootに戻る

Spring Expression Language (SpEL)

Spring Expression Language (SpEL)とは

Spring Expression Language(SpEL)は、Spring Frameworkの一部として提供される強力な式言語です。オブジェクトグラフのクエリや操作を実行するための統一された式言語として設計されており、Spring Frameworkの様々なコンポーネント(Spring Security、Spring Data、Spring Integration など)で使用されています。SpELは、実行時にオブジェクトのプロパティ値へのアクセス、メソッド呼び出し、配列やコレクションの操作、論理演算子や算術演算子の使用など、多様な機能を提供します。

SpELは、特に以下のような状況で有用です:

+------------------------------------------+
|       Spring Expression Language        |
|                                          |
|  +-------------+       +-------------+   |
|  |             |       |             |   |
|  |  Parser     |------>| Expression  |   |
|  |             |       | Tree        |   |
|  +-------------+       +------+------+   |
|                               |          |
|                               v          |
|                        +-------------+   |
|                        |             |   |
|                        | Evaluation  |   |
|                        | Context     |   |
|                        +------+------+   |
|                               |          |
|                               v          |
|                        +-------------+   |
|                        |             |   |
|                        |  Result     |   |
|                        |             |   |
|                        +-------------+   |
|                                          |
+------------------------------------------+
        

図1: Spring Expression Language (SpEL)の処理フロー

SpELの主な目的は以下の通りです:

SpELの主な特徴

SpELの基本構文

SpELの式は通常、#{...}または${...}の形式で記述されます。#{...}はSpEL式を表し、${...}はプロパティプレースホルダを表します。プロパティプレースホルダは、プロパティファイルやシステムプロパティから値を取得するために使用されます。また、#{${...}}のように、プロパティプレースホルダをSpEL式内で使用することもできます。

// 基本的なSpEL式
#{1 + 1}                 // 2を返す
#{'Hello ' + 'World'}    // "Hello World"を返す
#{2 > 1}                 // trueを返す
#{T(java.lang.Math).random() * 100.0} // 0から100の間のランダムな数値を返す

// プロパティプレースホルダ
${app.name}              // app.nameプロパティの値を返す

// プロパティプレースホルダとSpEL式の組み合わせ
#{${app.name} + ' version ' + ${app.version}} // "MyApp version 1.0"のような値を返す

SpELの使用例

1. アノテーションでの使用

SpELは、Spring Frameworkの様々なアノテーションで使用できます。例えば、@Valueアノテーションを使用して、Beanのプロパティに式の評価結果を注入することができます。

@Component
public class ExampleBean {
    
    @Value("#{systemProperties['user.region']}")
    private String region;
    
    @Value("#{systemEnvironment['PATH']}")
    private String path;
    
    @Value("#{T(java.lang.Math).random() * 100.0}")
    private double randomNumber;
    
    @Value("#{messageSource.getMessage('app.name', null, 'Default App Name', null)}")
    private String appName;
    
    // getterとsetter
}

2. XMLベースの設定での使用

SpELは、XMLベースの設定ファイルでも使用できます。

<bean id="exampleBean" class="com.example.ExampleBean">
    <property name="randomNumber" value="#{T(java.lang.Math).random() * 100.0}"/>
    <property name="appName" value="#{messageSource.getMessage('app.name', null, 'Default App Name', null)}"/>
</bean>

3. プログラムでの使用

SpELは、プログラムでも使用できます。ExpressionParserインターフェースとEvaluationContextインターフェースを使用して、式を解析し評価することができます。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello, ' + name");

EvaluationContext context = new StandardEvaluationContext();
context.setVariable("name", "World");

String message = (String) exp.getValue(context);
// "Hello, World"が返される

4. Spring Securityでの使用

SpELは、Spring Securityのアクセス制御式でも使用されています。

@PreAuthorize("hasRole('ADMIN') or #user.id == authentication.principal.id")
public void updateUser(User user) {
    // ユーザー更新ロジック
}

5. Spring Dataでの使用

SpELは、Spring Dataのクエリアノテーションでも使用されています。

@Query("select u from User u where u.age = ?#{[0]}")
List<User> findUsersByAge(int age);

@Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(@Param("customer") Customer customer);

6. 条件付きBeanの作成

SpELは、条件に基づいてBeanを作成するために使用できます。

@Configuration
public class AppConfig {
    
    @Bean
    @Conditional(ProductionCondition.class)
    @ConditionalOnExpression("#{environment['spring.profiles.active'] == 'prod'}")
    public DataSource productionDataSource() {
        // 本番環境用のデータソース設定
        return new ProductionDataSource();
    }
    
    @Bean
    @ConditionalOnExpression("#{environment['spring.profiles.active'] == 'dev'}")
    public DataSource developmentDataSource() {
        // 開発環境用のデータソース設定
        return new DevelopmentDataSource();
    }
}

7. 複雑なコレクション操作

SpELは、複雑なコレクション操作を簡潔に表現するために使用できます。

// 複数の条件を組み合わせたフィルタリング
#{users.?[age > 30 and salary > 50000]}

// ネストされたプロパティでのフィルタリング
#{departments.?[employees.?[salary > 100000].size() > 0]}

// フィルタリングと投影の組み合わせ
#{departments.?[name == 'IT'].![employees.?[role == 'Developer']]}

// 集計関数の使用
#{users.![salary].sum()}
#{users.![salary].average()}
#{users.?[department == 'IT'].![salary].max()}

8. SpELを使用した動的なJSONの生成

SpELは、動的なJSONの生成にも使用できます。

@RestController
public class UserController {
    
    @GetMapping("/users/{id}")
    public Map<String, Object> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        
        // SpELを使用して動的にJSONを生成
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext(user);
        
        Map<String, Object> result = new HashMap<>();
        result.put("id", parser.parseExpression("id").getValue(context));
        result.put("name", parser.parseExpression("firstName + ' ' + lastName").getValue(context));
        result.put("isAdult", parser.parseExpression("age >= 18").getValue(context));
        result.put("roles", parser.parseExpression("roles.![name]").getValue(context));
        
        return result;
    }
}

SpELの詳細機能

1. リテラル式

SpELは、様々な種類のリテラル値をサポートしています。

// 文字列リテラル
#{'Hello World'}

// 数値リテラル
#{42}
#{3.14159}

// ブールリテラル
#{true}
#{false}

// nullリテラル
#{null}

2. プロパティ、配列、リスト、マップへのアクセス

SpELを使用して、オブジェクトのプロパティや様々なコレクションにアクセスすることができます。

// プロパティアクセス
#{person.name}
#{person['name']}

// 配列アクセス
#{arrayOfInts[0]}

// リストアクセス
#{listOfNames[0]}

// マップアクセス
#{mapOfCounts['apple']}

3. メソッド呼び出し

SpELを使用して、オブジェクトのメソッドを呼び出すことができます。

// メソッド呼び出し
#{person.getName()}
#{listOfNames.size()}

4. 演算子

SpELは、様々な演算子をサポートしています。

// 算術演算子
#{1 + 2}
#{10 - 5}
#{2 * 3}
#{10 / 2}
#{10 % 3}
#{2 ^ 3}  // 2の3乗

// 比較演算子
#{1 == 1}
#{1 != 2}
#{1 < 2}
#{1 <= 2}
#{1 > 0}
#{1 >= 0}

// 論理演算子
#{true and false}
#{true or false}
#{!true}

// 三項演算子
#{1 > 0 ? 'positive' : 'negative'}

5. 正規表現

SpELは、文字列のパターンマッチングのための正規表現をサポートしています。

// 正規表現
#{'5.0' matches '^-?\\d+(\\.\\d+)?$'}

6. クラス参照

SpELを使用して、クラスを参照し、静的メソッドや静的フィールドにアクセスすることができます。

// クラス参照
#{T(java.lang.Math)}

// 静的メソッドの呼び出し
#{T(java.lang.Math).random()}
#{T(java.lang.Math).PI}

7. コンストラクタ

SpELを使用して、新しいオブジェクトを作成することができます。

// コンストラクタ
#{new java.util.Date()}
#{new java.util.ArrayList()}

8. 変数

SpELは、式内での変数の定義と参照をサポートしています。

// 変数
#{#var = 10; #var * 2}

9. 関数

SpELは、カスタム関数の定義と呼び出しをサポートしています。

// 関数の登録
public class StringUtils {
    public static String reverseString(String input) {
        return new StringBuilder(input).reverse().toString();
    }
}

// 関数の使用
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverseString", 
    StringUtils.class.getDeclaredMethod("reverseString", String.class));

String reversed = parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
// "olleh"が返される

10. Beanの参照

SpELを使用して、Spring Beanを参照することができます。

// Beanの参照
#{userService}
#{userService.findUserById(1)}

11. 三項演算子とElvis演算子

SpELは、条件付きロジックを表現するための三項演算子とElvis演算子をサポートしています。

// 三項演算子
#{user.gender == 'M' ? 'Male' : 'Female'}

// Elvis演算子(null条件演算子)
#{user.name ?: 'Anonymous'}

12. 安全なナビゲーション演算子

SpELは、nullチェックを簡略化するための安全なナビゲーション演算子をサポートしています。

// 安全なナビゲーション演算子
#{user?.address?.city}

13. コレクション選択

SpELは、コレクションから条件に合う要素を選択する機能をサポートしています。

// コレクション選択
#{users.?[age > 30]}  // 30歳より上のユーザーを選択

14. コレクション投影

SpELは、コレクションの各要素から特定のプロパティを抽出する機能をサポートしています。

// コレクション投影
#{users.![name]}  // すべてのユーザーの名前を抽出

15. テンプレート式

SpELは、文字列テンプレート内での式の評価をサポートしています。

// テンプレート式
#{'Hello, ' + name + '!'}

SpELの使用場面

Spring Core

@Valueアノテーションでのプロパティ注入、XMLベースの設定など

Spring Security

アクセス制御式、メソッドセキュリティなど

Spring Data

クエリアノテーション、動的クエリなど

Spring Integration

メッセージルーティング、フィルタリングなど

Thymeleaf

ビューテンプレート内での式の評価

カスタムアプリケーション

動的なビジネスルールの実装、設定の柔軟性の向上など

KotlinでのSpELの使用

KotlinでSpELを使用する場合、Javaとは異なるいくつかの注意点があります。以下に、KotlinでSpELを使用する際の主な考慮事項と例を示します。

1. Kotlinの基本的な使用例

KotlinでもJavaと同様に、@Valueアノテーションなどを使用してSpEL式を記述できます。

@Component
class ExampleBean {
    
    @Value("#{systemProperties['user.region']}")
    lateinit var region: String
    
    @Value("#{T(java.lang.Math).random() * 100.0}")
    var randomNumber: Double = 0.0
    
    @Value("#{messageSource.getMessage('app.name', null, 'Default App Name', null)}")
    lateinit var appName: String
}

2. Kotlinの特性に関する注意点

KotlinでSpELを使用する際には、以下の点に注意する必要があります:

3. Kotlinのコンパニオンオブジェクトへのアクセス

Kotlinのコンパニオンオブジェクトにアクセスするには、以下のようにします:

// Kotlinのクラス定義
class MathUtils {
    companion object {
        const val PI = 3.14159
        fun square(n: Double): Double = n * n
    }
}

// SpEL式でのアクセス
@Value("#{T(com.example.MathUtils).square(2.0)}")
private val squaredValue: Double = 0.0

@Value("#{T(com.example.MathUtils).PI}")
private val piValue: Double = 0.0

4. Kotlinの拡張関数の使用

Kotlinの拡張関数をSpEL式内で使用するには、通常、その拡張関数をJavaから呼び出せる形式にする必要があります:

// Kotlinの拡張関数
fun String.reverseAndUppercase(): String = this.reversed().uppercase()

// 拡張関数をJavaから呼び出せるようにするユーティリティクラス
class StringUtils {
    companion object {
        @JvmStatic
        fun reverseAndUppercase(str: String): String = str.reverseAndUppercase()
    }
}

// SpEL式での使用
@Value("#{T(com.example.StringUtils).reverseAndUppercase('hello')}")
private val reversedUppercase: String = ""

5. Kotlinのデータクラスとの連携

KotlinのデータクラスはSpEL式内でも問題なく使用できます:

// Kotlinのデータクラス
data class User(val id: Long, val name: String, val age: Int)

// SpEL式での使用
@Component
class UserService {
    
    @Value("#{userRepository.findById(1).orElse(null)}")
    lateinit var user: User
    
    @Value("#{user != null and user.age >= 18}")
    var isAdult: Boolean = false
}

6. Kotlinの関数型プログラミング機能との連携

Kotlinの高階関数やラムダ式をSpEL式内で直接使用することは難しいですが、これらの機能を使用するメソッドを定義し、そのメソッドをSpEL式から呼び出すことができます:

// Kotlinの高階関数を使用するメソッド
@Component
class ListProcessor {
    fun processAndFilter(list: List<Int>): List<Int> {
        return list.filter { it > 0 }.map { it * 2 }
    }
}

// SpEL式での使用
@Value("#{listProcessor.processAndFilter(listOf(-2, -1, 0, 1, 2))}")
lateinit var processedList: List<Int>  // [2, 4]が返される

SpELのベストプラクティス

  1. 式の複雑さを制限する: 複雑な式は理解しにくく、保守が困難になります。複雑なロジックはJavaコードに移動することを検討してください。
  2. エラーハンドリングを考慮する: SpEL式の評価中にエラーが発生する可能性があります。適切なエラーハンドリングを実装してください。
  3. パフォーマンスを考慮する: SpEL式の評価はJavaコードよりも遅い場合があります。パフォーマンスが重要な場合は、式をキャッシュすることを検討してください。
  4. セキュリティを考慮する: ユーザー入力に基づいてSpEL式を構築する場合は、セキュリティリスクに注意してください。
  5. テストを書く: SpEL式をテストして、期待通りに動作することを確認してください。
  6. ドキュメントを書く: 複雑なSpEL式はドキュメント化して、他の開発者が理解しやすくしてください。
  7. 型安全性を活用する: SpELは型安全な式評価をサポートしています。可能な限り型情報を提供して、型安全性を向上させてください。
  8. Kotlinを使用する場合は言語の特性を考慮する: Kotlinの特性(Null安全性、プロパティアクセス、コンパニオンオブジェクトなど)を考慮して、SpEL式を設計してください。

アンチパターン

SpELとJSTL/EL/OGNLの比較

特徴 SpEL JSTL/EL OGNL
主な用途 Spring Framework全体 JSPページ Struts, WebWork
機能の豊富さ 非常に豊富 基本的 豊富
型安全性 高い 低い 中程度
拡張性 高い 低い 中程度
パフォーマンス 中程度 高い 中程度

まとめ

Spring Expression Language(SpEL)は、Spring Frameworkの一部として提供される強力な式言語です。オブジェクトグラフのクエリや操作を実行するための統一された式言語として設計されており、Spring Frameworkの様々なコンポーネントで使用されています。SpELは、リテラル式、プロパティアクセス、メソッド呼び出し、演算子、正規表現、クラス参照、コンストラクタ、変数、関数、Beanの参照、三項演算子、安全なナビゲーション演算子、コレクション選択、コレクション投影、テンプレート式など、多様な機能を提供します。

SpELの主な利点は以下の通りです:

SpELは、Spring Core、Spring Security、Spring Data、Spring Integration、Thymeleafなど、Spring Frameworkの様々なコンポーネントで使用されています。また、カスタムアプリケーションでも、動的なビジネスルールの実装や設定の柔軟性の向上などに活用できます。SpELを効果的に使用するためには、式の複雑さを制限する、エラーハンドリングを考慮する、パフォーマンスを考慮する、セキュリティを考慮する、テストを書く、ドキュメントを書く、型安全性を活用するなどのベストプラクティスを実践することが重要です。