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式
#{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は、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
}
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>
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"が返される
SpELは、Spring Securityのアクセス制御式でも使用されています。
@PreAuthorize("hasRole('ADMIN') or #user.id == authentication.principal.id")
public void updateUser(User user) {
// ユーザー更新ロジック
}
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);
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();
}
}
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()}
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は、様々な種類のリテラル値をサポートしています。
// 文字列リテラル
#{'Hello World'}
// 数値リテラル
#{42}
#{3.14159}
// ブールリテラル
#{true}
#{false}
// nullリテラル
#{null}
SpELを使用して、オブジェクトのプロパティや様々なコレクションにアクセスすることができます。
// プロパティアクセス
#{person.name}
#{person['name']}
// 配列アクセス
#{arrayOfInts[0]}
// リストアクセス
#{listOfNames[0]}
// マップアクセス
#{mapOfCounts['apple']}
SpELを使用して、オブジェクトのメソッドを呼び出すことができます。
// メソッド呼び出し
#{person.getName()}
#{listOfNames.size()}
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'}
SpELは、文字列のパターンマッチングのための正規表現をサポートしています。
// 正規表現
#{'5.0' matches '^-?\\d+(\\.\\d+)?$'}
SpELを使用して、クラスを参照し、静的メソッドや静的フィールドにアクセスすることができます。
// クラス参照
#{T(java.lang.Math)}
// 静的メソッドの呼び出し
#{T(java.lang.Math).random()}
#{T(java.lang.Math).PI}
SpELを使用して、新しいオブジェクトを作成することができます。
// コンストラクタ
#{new java.util.Date()}
#{new java.util.ArrayList()}
SpELは、式内での変数の定義と参照をサポートしています。
// 変数
#{#var = 10; #var * 2}
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"が返される
SpELを使用して、Spring Beanを参照することができます。
// Beanの参照
#{userService}
#{userService.findUserById(1)}
SpELは、条件付きロジックを表現するための三項演算子とElvis演算子をサポートしています。
// 三項演算子
#{user.gender == 'M' ? 'Male' : 'Female'}
// Elvis演算子(null条件演算子)
#{user.name ?: 'Anonymous'}
SpELは、nullチェックを簡略化するための安全なナビゲーション演算子をサポートしています。
// 安全なナビゲーション演算子
#{user?.address?.city}
SpELは、コレクションから条件に合う要素を選択する機能をサポートしています。
// コレクション選択
#{users.?[age > 30]} // 30歳より上のユーザーを選択
SpELは、コレクションの各要素から特定のプロパティを抽出する機能をサポートしています。
// コレクション投影
#{users.![name]} // すべてのユーザーの名前を抽出
SpELは、文字列テンプレート内での式の評価をサポートしています。
// テンプレート式
#{'Hello, ' + name + '!'}
@Valueアノテーションでのプロパティ注入、XMLベースの設定など
アクセス制御式、メソッドセキュリティなど
クエリアノテーション、動的クエリなど
メッセージルーティング、フィルタリングなど
ビューテンプレート内での式の評価
動的なビジネスルールの実装、設定の柔軟性の向上など
KotlinでSpELを使用する場合、Javaとは異なるいくつかの注意点があります。以下に、KotlinでSpELを使用する際の主な考慮事項と例を示します。
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
}
KotlinでSpELを使用する際には、以下の点に注意する必要があります:
nullable
として宣言するか、lateinit
を使用する必要があります。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
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 = ""
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
}
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 | 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を効果的に使用するためには、式の複雑さを制限する、エラーハンドリングを考慮する、パフォーマンスを考慮する、セキュリティを考慮する、テストを書く、ドキュメントを書く、型安全性を活用するなどのベストプラクティスを実践することが重要です。