1. Kotlin

Kotlin (英語) は、JVM(および他のプラットフォーム)をターゲットとする静的に型付けされた言語で、Java で記述された既存のライブラリと非常に優れた相互運用性 (英語) を提供しながら、簡潔でエレガントなコードを記述できます。

Spring Framework は Kotlin のファーストクラスのサポートを提供し、開発者は Spring Framework がネイティブ Kotlin フレームワークであるかのように Kotlin アプリケーションを作成できます。

Kotlin で Spring アプリケーションを構築する最も簡単な方法は、Spring Boot とその専用 Kotlin サポートを活用することです。この包括的なチュートリアルは、start.spring.io (英語) を使用して Kotlin で Spring Boot アプリケーションを作成する方法を説明します。

Spring Framework 5.2, 以降、リファレンスドキュメントのコードサンプルのほとんどは、Java に加えて Kotlin で提供されます。

サポートが必要な場合は、Kotlin Slack (英語) の #spring チャンネルに参加するか、スタックオーバーフロー (英語) のタグとして spring および kotlin で質問してください。

1.1. 要件

Spring Framework は Kotlin 1.3 をサポートし、kotlin-stdlib (英語) (または kotlin-stdlib-jdk8 (英語) などのバリアントの 1 つ)と kotlin-reflect (英語) がクラスパスに存在する必要があります。start.spring.io (英語) で Kotlin プロジェクトをブートストラップする場合、デフォルトで提供されます。

1.2. 拡張

Kotlin 拡張機能 (英語) は、追加機能を使用して既存のクラスを継承する機能を提供します。Spring Framework Kotlin API はこれらの拡張機能を使用して、既存の Spring API に新しい Kotlin 固有の便利な機能を追加します。

Spring Framework KDoc API (英語) は、利用可能なすべての Kotlin 拡張機能と DSL をリストおよびドキュメント化します。

Kotlin 拡張機能を使用するにはインポートする必要があることに注意してください。これは、たとえば、GenericApplicationContext.registerBean Kotlin 拡張が org.springframework.context.support.registerBean がインポートされた場合にのみ利用可能であることを意味します。ただし、静的インポートと同様に、ほとんどの場合、IDE はインポートを自動的に提案する必要があります。

例:Kotlin の型パラメーターを具体化 (英語) は JVM ジェネリック型消去: Oracle (英語) の回避策を提供し、Spring Framework はこの機能を利用するためのいくつかの拡張機能を提供します。これにより、より良い Kotlin API RestTemplate、Spring WebFlux からの新しい WebClient、および他のさまざまな API が可能になります。

Reactor や Spring Data などの他のライブラリも、API の Kotlin 拡張機能を提供するため、全体として Kotlin 開発のエクスペリエンスが向上します。

Java で User オブジェクトのリストを取得するには、通常、次のように記述します。

Flux<User> users  = client.get().retrieve().bodyToFlux(User.class)

Kotlin および Spring Framework 拡張機能を使用すると、代わりに以下を記述できます。

val users = client.get().retrieve().bodyToFlux<User>()
// or (both are equivalent)
val users : Flux<User> = client.get().retrieve().bodyToFlux()

Java と同様に、Kotlin の users は厳密に型指定されていますが、Kotlin の巧妙な型推論により、構文を短くすることができます。

1.3. null セーフ

Kotlin の主要な機能の 1 つは null-safety (英語) です。これは、実行時に有名な NullPointerException にぶつかるのではなく、コンパイル時に null 値をきれいに処理します。これにより、Optional などのラッパーのコストを支払うことなく、null 可能性の宣言と「値または値なし」のセマンティクスを表現することにより、アプリケーションがより安全になります。(Kotlin では、null 許容値を持つ関数構成体を使用できます。Kotlin null-safety の包括的なガイド (英語) を参照してください。)

Java では、その型システムで null 安全性を表現できませんが、Spring Framework は、org.springframework.lang パッケージで宣言されたツールフレンドリーなアノテーションを介して、Spring Framework API 全体の null 安全性を提供します。デフォルトでは、Kotlin で使用される Java API の型はプラットフォーム型 (英語) として認識され、そのために null チェックが緩和されます。JSR-305 アノテーションの Kotlin サポート (英語) および Spring の NULL 可能性アノテーションは、Spring Framework API 全体に対して Kotlin 開発者に null の安全性を提供し、コンパイル時に null 関連の課題を処理するという利点があります。

Reactor や Spring Data などのライブラリは、この機能を活用する null セーフ API を提供します。

-Xjsr305 コンパイラフラグに次のオプションを追加することにより、JSR-305 チェックを構成できます。-Xjsr305={strict|warn|ignore}

kotlin バージョン 1.1 + の場合、デフォルトの動作は -Xjsr305=warn と同じです。strict 値は、Spring API から推論される Kotlin タイプで Spring Framework API null-safety を考慮する必要がありますが、マイナーリリース間でも Spring API nullability 宣言が進化する可能性があり、将来さらに多くのチェックが追加される可能性があるという知識とともに使用する必要があります。

ジェネリック型の引数、可変引数、配列要素の NULL 可能性はまだサポートされていませんが、今後のリリースでサポートされる予定です。最新情報については、このディスカッション: GitHub (英語) を参照してください。

1.4. クラスおよびインターフェース

Spring Framework は、プライマリコンストラクターによる Kotlin クラスのインスタンス化、不変クラスのデータバインディング、デフォルト値を持つ関数オプションパラメーターなど、さまざまな Kotlin コンストラクトをサポートしています。

Kotlin パラメーター名は、専用の KotlinReflectionParameterNameDiscoverer によって認識されます。これにより、コンパイル時に Java 8 -parameters コンパイラーフラグを有効にする必要なく、インターフェースメソッドのパラメーター名を検索できます。

JSON データのシリアライズまたはデシリアライズに必要な Jackson Kotlin モジュール: GitHub (英語) は、クラスパスで検出されると自動的に登録され、Jackson Kotlin モジュールが存在しない状態で Jackson および Kotlin が検出されると、警告メッセージがログに記録されます。

設定クラスはトップレベルまたはネストされているが内部クラスではない (英語) として宣言できます。後者は外部クラスへの参照を必要とするためです。

1.5. アノテーション

Spring Framework は、required 属性を明示的に定義しなくても、Kotlin null-safety (英語) を利用して HTTP パラメーターが必要かどうかを判断します。つまり、@RequestParam name: String? は不要として扱われ、逆に @RequestParam name: String は必要として扱われます。この機能は、Spring メッセージング @Header アノテーションでもサポートされています。

同様に、Spring @Autowired@Bean または @Inject による Bean インジェクションは、この情報を使用して Bean が必要かどうかを判断します。

例: @Autowired lateinit var thing: Thing は、Thing タイプの Bean をアプリケーションコンテキストに登録する必要があることを意味しますが、Bean が存在しない場合、@Autowired lateinit var thing: Thing? はエラーを発生させません。

同じ原則に従って、@Bean fun play(toy: Toy, car: Car?) = Baz(toy, Car) は、タイプ Toy の Bean をアプリケーションコンテキストに登録する必要があることを意味しますが、タイプ Car の Bean は存在する場合と存在しない場合があります。同じ動作は、オートワイヤーされたコンストラクターパラメーターにも適用されます。

プロパティまたはプライマリコンストラクターパラメーターを持つクラスで Bean 検証を使用する場合、この Stack Overflow レスポンス (英語) で説明されているように、@field:NotNull や @get:Size(min=5, max=15) などのアノテーション use-site ターゲット (英語) を使用する必要がある場合があります。

1.6. Bean 定義 DSL

Spring Framework は、XML または Java 構成(@Configuration および @Bean)の代替としてラムダを使用することにより、関数方法で Bean を登録することをサポートします。簡単に言うと、FactoryBean として機能するラムダを使用して Bean を登録できます。このメカニズムは、リフレクションまたは CGLIB プロキシを必要としないため、非常に効率的です。

Java では、たとえば、次のように記述できます。

class Foo {}

class Bar {
    private final Foo foo;
    public Bar(Foo foo) {
        this.foo = foo;
    }
}

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class)));

Kotlin では、具体化された型パラメーターと GenericApplicationContext Kotlin 拡張を使用して、代わりに次のように記述できます。

class Foo

class Bar(private val foo: Foo)

val context = GenericApplicationContext().apply {
    registerBean<Foo>()
    registerBean { Bar(it.getBean()) }
}

クラス Bar に単一のコンストラクターがある場合、Bean クラスを指定するだけで、コンストラクターパラメーターはタイプごとにオートワイヤーされます。

val context = GenericApplicationContext().apply {
    registerBean<Foo>()
    registerBean<Bar>()
}

より宣言的なアプローチとより簡潔な構文を可能にするために、Spring Framework は Kotlin Bean 定義 DSL (英語) を提供し、クリーンな宣言 API を介して ApplicationContextInitializer を宣言します。これにより、Bean の登録方法をカスタマイズするためのプロファイルと Environment を処理できます。

次の例では、次のことに注意してください。

  • 通常、型推論により、ref("bazBean") などの Bean 参照の型の指定を回避できます。

  • この例では、Kotlin トップレベル関数を使用して、bean(::myRouter) などの呼び出し可能参照を使用して Bean を宣言することができます。

  • bean<Bar>() または bean(::myRouter) を指定すると、パラメーターはタイプごとにオートワイヤーされます。

  • FooBar Bean は、foobar プロファイルがアクティブな場合にのみ登録されます

class Foo
class Bar(private val foo: Foo)
class Baz(var message: String = "")
class FooBar(private val baz: Baz)

val myBeans = beans {
    bean<Foo>()
    bean<Bar>()
    bean("bazBean") {
        Baz().apply {
            message = "Hello world"
        }
    }
    profile("foobar") {
        bean { FooBar(ref("bazBean")) }
    }
    bean(::myRouter)
}

fun myRouter(foo: Foo, bar: Bar, baz: Baz) = router {
    // ...
}
この DSL はプログラムです。つまり、if 式、for ループ、またはその他の Kotlin 構成体を介して Bean のカスタム登録ロジックを許可します。

次の例に示すように、この beans() 関数を使用して、アプリケーションコンテキストに Bean を登録できます。

val context = GenericApplicationContext().apply {
    myBeans.initialize(this)
    refresh()
}
Spring Boot は JavaConfig に基づいており、まだ関数 Bean 定義の特定のサポートを提供していません: GitHub (英語) が、Spring Boot の ApplicationContextInitializer サポートを通じて関数 Bean 定義を実験的に使用できます。詳細と最新情報については、このスタックオーバーフローの回答 (英語) を参照してください。Spring Fu インキュベーター: GitHub (英語) で開発された実験的な Kofu DSL も参照してください。

1.7. Web

1.7.1. ルーター DSL

Spring Framework には、3 つのフレーバーで利用可能な Kotlin ルーター DSL が付属しています。

これらの DSL を使用すると、次の例に示すように、クリーンで慣用的な Kotlin コードを記述して RouterFunction インスタンスを作成できます。

@Configuration
class RouterRouterConfiguration {

    @Bean
    fun mainRouter(userHandler: UserHandler) = router {
        accept(TEXT_HTML).nest {
            GET("/") { ok().render("index") }
            GET("/sse") { ok().render("sse") }
            GET("/users", userHandler::findAllView)
        }
        "/api".nest {
            accept(APPLICATION_JSON).nest {
                GET("/users", userHandler::findAll)
            }
            accept(TEXT_EVENT_STREAM).nest {
                GET("/users", userHandler::stream)
            }
        }
        resources("/**", ClassPathResource("static/"))
    }
}
この DSL はプログラマチックです。つまり、if 式、for ループ、またはその他の Kotlin 構造を介して Bean のカスタム登録ロジックを許可します。これは、動的データ(たとえば、データベースから)に応じてルートを登録する必要がある場合に役立ちます。

具体例については、MiXiT プロジェクト: GitHub (英語) を参照してください。

1.7.2. MockMvc DSL

Kotlin DSL は、MockMvc Kotlin 拡張を介して提供され、より慣用的な Kotlin API を提供し、発見性を向上させます(静的メソッドの使用なし)。

val mockMvc: MockMvc = ...
mockMvc.get("/person/{name}", "Lee") {
    secure = true
    accept = APPLICATION_JSON
    headers {
        contentLanguage = Locale.FRANCE
    }
    principal = Principal { "foo" }
}.andExpect {
    status { isOk }
    content { contentType(APPLICATION_JSON) }
    jsonPath("$.name") { value("Lee") }
    content { json("""{"someBoolean": false}""", false) }
}.andDo {
    print()
}

1.7.3. Kotlin スクリプトテンプレート

Spring Framework は、JSR-223 (英語) をサポートする ScriptTemplateView (Javadoc) を提供し、スクリプトエンジンを使用してテンプレートをレンダリングします。

kotlin-script-runtime および scripting-jsr223-embeddable 依存関係を活用することにより、そのような機能を使用して、kotlinx.html: GitHub (英語) DSL または Kotlin 複数行補間 String で Kotlin ベースのテンプレートをレンダリングできます。

build.gradle.kts

dependencies {
    compile("org.jetbrains.kotlin:kotlin-script-runtime:${kotlinVersion}")
    runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223-embeddable:${kotlinVersion}")
}

通常、構成は ScriptTemplateConfigurer および ScriptTemplateViewResolver Bean で行われます。

KotlinScriptConfiguration.kt

@Configuration
class KotlinScriptConfiguration {

    @Bean
    fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
        engineName = "kotlin"
        setScripts("scripts/render.kts")
        renderFunction = "render"
        isSharedEngine = false
    }

    @Bean
    fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
        setPrefix("templates/")
        setSuffix(".kts")
    }
}

詳細については、kotlin-script-templating: GitHub (英語) サンプルプロジェクトを参照してください。

1.7.4. Kotlin マルチプラットフォーム直列化

Spring Framework 5.3, 以降、Kotlin マルチプラットフォーム直列化: GitHub (英語) は Spring MVC、Spring WebFlux、および Spring メッセージングでサポートされています。組み込みのサポートは現在、JSON 形式のみを対象としています。

有効にするには、これらの手順: GitHub (英語) に従い、Jackson、GSON、JSONB のいずれもクラスパスに含まれていないことを確認してください。

典型的な Spring BootWeb アプリケーションの場合、これは spring-boot-starter-json の依存関係を除外することで実現できます。

Spring MVC では、他の目的で Jackson、GSON、または JSONB が必要な場合は、クラスパスに保持し、メッセージコンバーターを構成して MappingJackson2HttpMessageConverter を削除し、KotlinSerializationJsonHttpMessageConverter を追加できます。

1.8. コルーチン

Kotlin コルーチン (英語) は、Kotlin 軽量スレッドであり、ノンブロッキングコードを命令的な方法で記述することができます。言語側では、サスペンド関数は非同期操作の抽象化を提供し、ライブラリ側では kotlinx.coroutines: GitHub (英語) async { } (英語) などの関数や Flow (英語) などの型を提供します。

Spring Framework は、次のスコープでコルーチンのサポートを提供します。

1.8.1. 依存関係

kotlinx-coroutines-core および kotlinx-coroutines-reactor の依存関係がクラスパスにある場合、コルーチンのサポートが有効になります。

build.gradle.kts

dependencies {

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}")
}

バージョン 1.4.0 以上がサポートされています。

1.8.2. Reactive はコルーチンにどのように変換されますか?

戻り値の場合、Reactive API から Coroutines API への変換は次のとおりです。

  • fun handler(): Mono<Void> は suspend fun handler() になります

  • fun handler(): Mono<T> は、Mono を空にできるかどうかに応じて、suspend fun handler(): T または suspend fun handler(): T? になります。(より静的に型付けされるという利点がある)

  • fun handler(): Flux<T> は fun handler(): Flow<T> になります

入力パラメーターの場合:

  • 遅延が必要ない場合、値パラメーターを取得するために中断関数を呼び出すことができるため、fun handler(mono: Mono<T>) は fun handler(value: T) になります。

  • 怠 laz が必要な場合、fun handler(mono: Mono<T>) は fun handler(supplier: suspend () → T) または fun handler(supplier: suspend () → T?) になります

Flow (英語) は、コルーチンの世界で Flux に相当し、ホットストリームまたはコールドストリーム、有限ストリームまたは無限ストリームに適していますが、主な違いは次のとおりです。

コルーチンと並行してコードを実行する方法など、詳細については、Spring、コルーチン、Kotlin Flow とのリアクティブ化 (英語) に関するこのブログ投稿を参照してください。

1.8.3. コントローラー

コルーチン @RestController の例を次に示します。

@RestController
class CoroutinesRestController(client: WebClient, banner: Banner) {

    @GetMapping("/suspend")
    suspend fun suspendingEndpoint(): Banner {
        delay(10)
        return banner
    }

    @GetMapping("/flow")
    fun flowEndpoint() = flow {
        delay(10)
        emit(banner)
        delay(10)
        emit(banner)
    }

    @GetMapping("/deferred")
    fun deferredEndpoint() = GlobalScope.async {
        delay(10)
        banner
    }

    @GetMapping("/sequential")
    suspend fun sequential(): List<Banner> {
        val banner1 = client
                .get()
                .uri("/suspend")
                .accept(MediaType.APPLICATION_JSON)
                .awaitExchange()
                .awaitBody<Banner>()
        val banner2 = client
                .get()
                .uri("/suspend")
                .accept(MediaType.APPLICATION_JSON)
                .awaitExchange()
                .awaitBody<Banner>()
        return listOf(banner1, banner2)
    }

    @GetMapping("/parallel")
    suspend fun parallel(): List<Banner> = coroutineScope {
        val deferredBanner1: Deferred<Banner> = async {
            client
                    .get()
                    .uri("/suspend")
                    .accept(MediaType.APPLICATION_JSON)
                    .awaitExchange()
                    .awaitBody<Banner>()
        }
        val deferredBanner2: Deferred<Banner> = async {
            client
                    .get()
                    .uri("/suspend")
                    .accept(MediaType.APPLICATION_JSON)
                    .awaitExchange()
                    .awaitBody<Banner>()
        }
        listOf(deferredBanner1.await(), deferredBanner2.await())
    }

    @GetMapping("/error")
    suspend fun error() {
        throw IllegalStateException()
    }

    @GetMapping("/cancel")
    suspend fun cancel() {
        throw CancellationException()
    }

}

@Controller によるビューレンダリングもサポートされています。

@Controller
class CoroutinesViewController(banner: Banner) {

    @GetMapping("/")
    suspend fun render(model: Model): String {
        delay(10)
        model["banner"] = banner
        return "index"
    }
}

1.8.4. WebFlux.fn

以下は、coRouter {} (英語) DSL および関連ハンドラーを介して定義されたコルーチンルーターの例です。

@Configuration
class RouterConfiguration {

    @Bean
    fun mainRouter(userHandler: UserHandler) = coRouter {
        GET("/", userHandler::listView)
        GET("/api/user", userHandler::listApi)
    }
}
class UserHandler(builder: WebClient.Builder) {

    private val client = builder.baseUrl("...").build()

    suspend fun listView(request: ServerRequest): ServerResponse =
            ServerResponse.ok().renderAndAwait("users", mapOf("users" to
            client.get().uri("...").awaitExchange().awaitBody<User>()))

    suspend fun listApi(request: ServerRequest): ServerResponse =
                ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyAndAwait(
                client.get().uri("...").awaitExchange().awaitBody<User>())
}

1.8.5. トランザクション

コルーチンのトランザクションは、Spring Framework 5.2. の時点で提供されるリアクティブトランザクション管理のプログラムのバリアントを介してサポートされます

機能を中断するために、TransactionalOperator.executeAndAwait 拡張機能が提供されています。

import org.springframework.transaction.reactive.executeAndAwait

class PersonRepository(private val operator: TransactionalOperator) {

    suspend fun initDatabase() = operator.executeAndAwait {
        insertPerson1()
        insertPerson2()
    }

    private suspend fun insertPerson1() {
        // INSERT SQL statement
    }

    private suspend fun insertPerson2() {
        // INSERT SQL statement
    }
}

Kotlin Flow の場合、Flow<T>.transactional 拡張が提供されます。

import org.springframework.transaction.reactive.transactional

class PersonRepository(private val operator: TransactionalOperator) {

    fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)

    private fun findPeople(): Flow<Person> {
        // SELECT SQL statement
    }

    private suspend fun updatePerson(person: Person): Person {
        // UPDATE SQL statement
    }
}

1.9. Kotlin の Spring プロジェクト

このセクションでは、Kotlin で Spring プロジェクトを開発するための価値のある特定のヒントと推奨事項を提供します。

1.9.1. デフォルトで final

デフォルトでは、Kotlin のすべてのクラスは final (英語) です。クラスの open 修飾子は、Java の final の反対です。他の人がこのクラスから継承できるようにします。これは、オーバーライドするために open としてマークする必要があるという点で、メンバー関数にも適用されます。

Kotlin の JVM フレンドリーな設計は一般に Spring との摩擦がありませんが、この事実が考慮されない場合、この特定の Kotlin 機能はアプリケーションの起動を妨げることがあります。これは、Spring Bean(デフォルトでは技術的な理由で実行時に拡張する必要がある @Configuration アノテーション付きクラスなど)が通常 CGLIB によってプロキシされるためです。回避策は、CGLIB によってプロキシされる Spring Bean の各クラスおよびメンバー関数に open キーワードを追加することです。これはすぐに苦痛になり、コードを簡潔かつ予測可能に保つという Kotlin の原則に反します。

@Configuration(proxyBeanMethods = false) を使用して、構成クラスの CGLIB プロキシを回避することもできます。詳細については、proxyBeanMethods Javadoc を参照してください。

幸い、Kotlin は、kotlin-spring (英語) プラグイン(kotlin-allopen プラグインの構成済みバージョン)を提供します。これは、次のいずれかのアノテーションが付けられた、またはメタアノテーションが付けられた型のクラスとそのメンバー関数を自動的に開きます。

  • @Component

  • @Async

  • @Transactional

  • @Cacheable

メタアノテーションのサポートとは、@Configuration@Controller@RestController@Service または @Repository でアノテーションが付けられたタイプは、これらのアノテーションが @Component でメタアノテーションが付けられているため、自動的に開かれることを意味します。

start.spring.io (英語) は、デフォルトで kotlin-spring プラグインを有効にします。実際には、Java のように、open キーワードを追加せずに Kotlin Bean を作成できます。

Spring Framework ドキュメントの Kotlin コードサンプルでは、クラスとそのメンバー関数に open を明示的に指定していません。サンプルは、kotlin-allopen プラグインを使用するプロジェクト用に記述されています。これは、これが最も一般的に使用されるセットアップであるためです。

1.9.2. 永続性のための不変クラスインスタンスの使用

Kotlin では、次の例のように、プライマリコンストラクター内で読み取り専用プロパティを宣言するのが便利であり、ベストプラクティスと見なされます。

class Person(val name: String, val age: Int)

オプションで data キーワード (英語) を追加して、コンパイラーがプライマリコンストラクターで宣言されたすべてのプロパティから次のメンバーを自動的に派生させることができます。

  • equals() および hashCode()

  • "User(name=John, age=42)" 形式の toString() 

  • 宣言の順序でプロパティに対応する componentN() 関数

  • copy() 関数

次の例に示すように、Person プロパティが読み取り専用であっても、個々のプロパティを簡単に変更できます。

data class Person(val name: String, val age: Int)

val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

一般的な永続化技術(JPA など)にはデフォルトのコンストラクターが必要なので、この種の設計はできません。幸い、Kotlin は、JPA アノテーションが付けられたクラスの引数なしの合成コンストラクターを生成する kotlin-jpa (英語) プラグインを提供しているため、この「デフォルトコンストラクター地獄」 (英語) には回避策があります。

この種のメカニズムを他の永続化テクノロジーに活用する必要がある場合は、kotlin-noarg (英語) プラグインを構成できます。

Kay リリーストレインの時点で、Spring Data は Kotlin 不変クラスインスタンスをサポートし、モジュールが Spring Data オブジェクトマッピング(MongoDB、Redis、Cassandra など)を使用する場合、kotlin-noarg プラグインを必要としません。

1.9.3. 依存関係の注入

次の例に示すように、val 読み取り専用(および可能であれば null 不可)プロパティ (英語) を使用してコンストラクター注入を優先することをお勧めします。

@Component
class YourBean(
    private val mongoTemplate: MongoTemplate,
    private val solrClient: SolrClient
)
単一のコンストラクターを持つクラスには、パラメーターが自動的にオートワイヤーされます。そのため、上記の例では明示的な @Autowired constructor は必要ありません。

フィールドインジェクションを本当に使用する必要がある場合は、次の例に示すように、lateinit var コンストラクトを使用できます。

@Component
class YourBean {

    @Autowired
    lateinit var mongoTemplate: MongoTemplate

    @Autowired
    lateinit var solrClient: SolrClient
}

1.9.4. 構成プロパティの注入

Java では、アノテーション(@Value("${property}") など)を使用して構成プロパティを注入できます。ただし、Kotlin では、$ は文字列補間 (英語) に使用される予約文字です。

Kotlin で @Value アノテーションを使用する場合は、@Value("\${property}") を記述して $ 文字をエスケープする必要があります。

Spring Boot を使用する場合、おそらく @Value アノテーションの代わりに @ConfigurationProperties を使用する必要があります。

別の方法として、次の構成 Bean を宣言することにより、プロパティプレースホルダープレフィックスをカスタマイズできます。

@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
    setPlaceholderPrefix("%{")
}

次の例に示すように、${…​} 構文を使用する既存のコード(Spring Boot アクチュエーターや @LocalServerPort など)を構成 Bean でカスタマイズできます。

@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
    setPlaceholderPrefix("%{")
    setIgnoreUnresolvablePlaceholders(true)
}

@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()

1.9.5. チェック済みの例外

Java と Kotlin 例外処理 (英語) は非常に近く、主な違いは Kotlin はすべての例外を未チェックの例外として扱うことです。ただし、プロキシ化されたオブジェクト(たとえば、@Transactional アノテーションが付けられたクラスまたはメソッド)を使用する場合、スローされるチェック済み例外は、デフォルトで UndeclaredThrowableException にラップされます。

Java のようにスローされた元の例外を取得するには、メソッドに @Throws (英語) アノテーションを付けて、スローされたチェック済み例外(@Throws(IOException::class) など)を明示的に指定する必要があります。

1.9.6. アノテーション配列の属性

Kotlin アノテーションはほとんど Java アノテーションに似ていますが、配列属性(Spring で広く使用されています)の動作は異なります。Kotlin のドキュメント (英語) で説明したように、他の属性とは異なり、value 属性名を省略して、vararg パラメーターとして指定できます。

その意味を理解するために、例として @RequestMapping (最も広く使用されている Spring アノテーションの 1 つ)を検討してください。この Java アノテーションは次のように宣言されます。

public @interface RequestMapping {

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    // ...
}

@RequestMapping の一般的な使用例は、ハンドラーメソッドを特定のパスとメソッドにマップすることです。Java では、アノテーション配列属性に単一の値を指定でき、自動的に配列に変換されます。

@RequestMapping(value = "/toys", method = RequestMethod.GET) または @RequestMapping(path = "/toys", method = RequestMethod.GET) を書くことができます。

ただし、Kotlin では、@RequestMapping("/toys", method = [RequestMethod.GET]) または @RequestMapping(path = ["/toys"], method = [RequestMethod.GET]) を作成する必要があります(角括弧は名前付き配列属性で指定する必要があります)。

この特定の method 属性(最も一般的な属性)の代替方法は、@GetMapping@PostMapping などのショートカットアノテーションを使用することです。

@RequestMappingmethod 属性が指定されていない場合、GET メソッドだけでなく、すべての HTTP メソッドが照合されます。

1.9.7. テスト

このセクションでは、Kotlin と Spring Framework を組み合わせたテストについて説明します。推奨されるテストフレームワークは、JUnit 5 (英語) とモック用の Mockk (英語) です。

Spring Boot を使用している場合は、この関連資料を参照してください。
コンストラクター注入

専用セクションで説明したように、JUnit 5 では、lateinit var の代わりに val を使用するために Kotlin で非常に役立つ Bean のコンストラクター注入が可能です。@TestConstructor(autowireMode = AutowireMode.ALL) (Javadoc) を使用して、すべてのパラメーターのオートワイヤーを有効にすることができます。

@SpringJUnitConfig(TestConfig::class)
@TestConstructor(autowireMode = AutowireMode.ALL)
class OrderServiceIntegrationTests(val orderService: OrderService,
                                   val customerService: CustomerService) {

    // tests that use the injected OrderService and CustomerService
}
PER_CLASS ライフサイクル

Kotlin を使用すると、バッククォート間に意味のあるテスト関数名を指定できます(`)。JUnit 5 以降、Kotlin テストクラスは @TestInstance(TestInstance.Lifecycle.PER_CLASS) アノテーションを使用してテストクラスの単一のインスタンス化を有効にできます。これにより、Kotlin に適した非静的メソッドで @BeforeAll および @AfterAll アノテーションを使用できます。

junit.jupiter.testinstance.lifecycle.default = per_class プロパティを持つ junit-platform.properties ファイルのおかげで、デフォルトの動作を PER_CLASS に変更することもできます。

次の例は、非静的メソッドでの @BeforeAll および @AfterAll アノテーションを示しています。

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTests {

  val application = Application(8181)
  val client = WebClient.create("http://localhost:8181")

  @BeforeAll
  fun beforeAll() {
    application.start()
  }

  @Test
  fun `Find all users on HTML page`() {
    client.get().uri("/users")
        .accept(TEXT_HTML)
        .retrieve()
        .bodyToMono<String>()
        .test()
        .expectNextMatches { it.contains("Foo") }
        .verifyComplete()
  }

  @AfterAll
  fun afterAll() {
    application.stop()
  }
}
仕様のようなテスト

JUnit 5 および Kotlin を使用して、仕様のようなテストを作成できます。次の例は、その方法を示しています。

class SpecificationLikeTests {

  @Nested
  @DisplayName("a calculator")
  inner class Calculator {
     val calculator = SampleCalculator()

     @Test
     fun `should return the result of adding the first number to the second number`() {
        val sum = calculator.sum(2, 4)
        assertEquals(6, sum)
     }

     @Test
     fun `should return the result of subtracting the second number from the first number`() {
        val subtract = calculator.subtract(4, 2)
        assertEquals(2, subtract)
     }
  }
}
Kotlin の WebTestClient タイプ推論の課題

型推論の課題 (英語) により、Kotlin expectBody 拡張(.expectBody<String>().isEqualTo("toys") など)を使用する必要があります。これは、Java API での Kotlin の課題の回避策を提供するためです。

関連する SPR-16057 (英語) の課題も参照してください。

1.10. 入門

Kotlin で Spring アプリケーションを構築する方法を学ぶ最も簡単な方法は、専用のチュートリアルに従うことです

1.10.1. start.spring.io

Kotlin で新しい Spring Framework プロジェクトを開始する最も簡単な方法は、start.spring.io (英語) で新しい Spring Boot 2 プロジェクトを作成することです。

1.10.2. Web フレーバーの選択

Spring Framework には、Spring MVCSpring WebFlux の 2 つの異なる Web スタックが付属しています。

Spring WebFlux は、待ち時間、長寿命の接続、ストリーミングシナリオを処理するアプリケーションを作成する場合、または Web 機能 Kotlin DSL を使用する場合に推奨されます。

他のユースケースでは、特に JPA などのブロッキングテクノロジーを使用している場合、Spring MVC とそのアノテーションベースのプログラミングモデルが推奨される選択肢です。

1.11. リソース

Kotlin および Spring Framework を使用してアプリケーションを構築する方法を学ぶ人には、次のリソースをお勧めします。

1.11.1. サンプル

以下の Github プロジェクトは、学んだり、場合によっては拡張したりできる例を提供します。

2. Apache Groovy

Groovy は強力な、オプションで型指定された動的言語であり、静的型付けと静的コンパイル機能を備えています。簡潔な構文を提供し、既存の Java アプリケーションとスムーズに統合します。

Spring Framework は、Groovy ベースの Bean Definition DSL をサポートする専用 ApplicationContext を提供します。詳細については、Groovy Bean 定義 DSL を参照してください。

Groovy で記述された Bean、リフレッシュ可能なスクリプト Bean など、Groovy のさらなるサポートが動的言語サポートで利用可能です。

3. 動的言語サポート

Spring は、Spring で動的言語(Groovy など)を使用して定義されたクラスとオブジェクトを使用するための包括的なサポートを提供します。このサポートにより、サポートされている動的言語で任意の数のクラスを記述し、Spring コンテナーに透過的にインスタンス化、構成、結果オブジェクトの依存性注入を行わせることができます。

Spring のスクリプトサポートは、主に Groovy および BeanShell を対象としています。これらの具体的にサポートされている言語以外に、JSR-223 スクリプトメカニズムは、JZR-223 対応の言語プロバイダー(Spring 4.2 以降)との統合がサポートされています。JRuby。

この動的言語サポートがシナリオですぐに役立つ場合の完全に機能する例を見つけることができます。

3.1. 最初の例

この章の大部分は、動的言語サポートの詳細な説明に関係しています。動的言語サポートのすべてを詳しく説明する前に、動的言語で定義された Bean の簡単な例を見てみましょう。この最初の Bean の動的言語は Groovy です。(この例の基礎は Spring テストスイートから取得されます。サポートされている他の言語で同等の例を表示する場合は、ソースコードを参照してください)。

次の例は、Groovy Bean が実装する Messenger インターフェースを示しています。このインターフェースはプレーン Java で定義されていることに注意してください。Messenger への参照が注入された依存オブジェクトは、基礎となる実装が Groovy スクリプトであることを知りません。次のリストは、Messenger インターフェースを示しています。

package org.springframework.scripting;

public interface Messenger {

    String getMessage();
}

次の例では、Messenger インターフェースに依存するクラスを定義しています。

package org.springframework.scripting;

public class DefaultBookingService implements BookingService {

    private Messenger messenger;

    public void setMessenger(Messenger messenger) {
        this.messenger = messenger;
    }

    public void processBooking() {
        // use the injected Messenger object...
    }
}

次の例では、Groovy で Messenger インターフェースを実装します。

// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;

// import the Messenger interface (written in Java) that is to be implemented
import org.springframework.scripting.Messenger

// define the implementation in Groovy
class GroovyMessenger implements Messenger {

    String message
}

カスタム動的言語タグを使用して動的言語を使用した Bean を定義するには、Spring XML 構成ファイルの先頭に XML スキーマのプリアンブルが必要です。また、IoC コンテナーとして Spring ApplicationContext 実装を使用する必要があります。プレーンな BeanFactory 実装での動的言語を使用した Bean の使用はサポートされていますが、そのためには Spring 内部の接続機能を管理する必要があります。

スキーマベースの構成の詳細については、XML スキーマベースの構成を参照してください。

最後に、次の例は、Groovy で定義された Messenger 実装を DefaultBookingService クラスのインスタンスに注入する Bean 定義を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">

    <!-- this is the bean definition for the Groovy-backed Messenger implementation -->
    <lang:groovy id="messenger" script-source="classpath:Messenger.groovy">
        <lang:property name="message" value="I Can Do The Frug" />
    </lang:groovy>

    <!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger -->
    <bean id="bookingService" class="x.y.DefaultBookingService">
        <property name="messenger" ref="messenger" />
    </bean>

</beans>

bookingService Bean(DefaultBookingService)は、その中に挿入された Messenger インスタンスが Messenger インスタンスであるため、プライベート messenger メンバー変数を通常どおり使用できるようになりました。ここでは特別なことは何も行われていません - 単なる Java と Groovy だけです。

願わくば、前述の XML スニペットは一目瞭然ですが、そうでない場合でも過度に心配不要です。前述の構成の理由と理由の詳細を参照してください。

3.2. 動的言語に支えられた Bean の定義

このセクションでは、サポートされている任意の動的言語で Spring 管理 Bean を定義する方法を正確に説明します。

この章では、サポートされている動的言語の構文とイディオムについては説明していません。例:Groovy を使用してアプリケーション内の特定のクラスを作成する場合、Groovy を既に知っていると想定します。動的言語自体の詳細が必要な場合は、この章の最後にあるその他のリソースを参照してください。

3.2.1. 共通の概念

動的言語を使用した Bean の使用に関する手順は次のとおりです。

  1. 動的言語ソースコードのテストを(自然に)書きます。

  2. 次に、動的言語のソースコード自体を記述します。

  3. XML 構成で適切な <lang:language/> 要素を使用して動的言語ベースの Bean を定義します(Spring API を使用してプログラムでそのような Bean を定義できますが、これを行う方法についてはソースコードを参照する必要があります。この章では、このタイプの詳細設定については説明していません。これは反復的なステップであることに注意してください。動的言語ソースファイルごとに少なくとも 1 つの Bean 定義が必要です(ただし、複数の Bean 定義は同じソースファイルを参照できます)。

最初の 2 つのステップ(動的言語ソースファイルのテストと作成)は、この章の範囲外です。選択した動的言語の言語仕様とリファレンスマニュアルを参照し、動的言語ソースファイルの開発に着手します。ただし、Spring の動的言語サポートは動的言語ソースファイルの内容について(小さな)仮定を行うため、この章の残りの部分を最初に読みたいと思います。

<lang:language/> 要素

前のセクションのリストの最後のステップでは、構成する Bean ごとに 1 つずつ、動的言語ベースの Bean 定義を定義します(これは通常の JavaBean 構成と同じです)。ただし、コンテナーによってインスタンス化および構成されるクラスの完全修飾クラス名を指定する代わりに、<lang:language/> エレメントを使用して、動的言語支援 Bean を定義できます。

サポートされている各言語には、対応する <lang:language/> 要素があります。

  • <lang:groovy/> (Groovy)

  • <lang:bsh/> (BeanShell)

  • <lang:std/> (JSR-223、例 JRuby)

構成に使用できる正確な属性と子要素は、Bean がどの言語で定義されているかによって異なります(この章で後述する言語固有のセクションで詳しく説明します)。

リフレッシュ可能な Bean

Spring の動的言語サポートの(そしておそらく単一の)最も魅力的な付加価値の 1 つは、「リフレッシュ可能な Bean」機能です。

リフレッシュ可能な Bean は、動的言語に対応した Bean です。少量の構成で、動的言語に裏付けされた Bean は、基礎となるソースファイルリソースの変更を監視し、動的言語ソースファイルが変更されると(たとえば、ファイルの変更を保存して、ファイルシステム)。

これにより、任意の数の動的言語ソースファイルをアプリケーションの一部としてデプロイし、(この章で説明するメカニズムを使用して)動的言語ソースファイルに裏付けられた Bean を作成するように Spring コンテナーを構成できます。外部要因が作用します)動的言語ソースファイルを編集し、変更があれば、変更された動的言語ソースファイルに裏付けられた Bean に反映されます。実行中のアプリケーションをシャットダウン(または Web アプリケーションの場合は再デプロイ)する必要はありません。このように修正された動的言語支援 Bean は、変更された動的言語ソースファイルから新しい状態とロジックを選択します。

この機能はデフォルトではオフになっています。

次に、例を参照して、リフレッシュ可能な Bean の使用がいかに簡単かを確認します。リフレッシュ可能な Bean 機能を有効にするには、Bean 定義の <lang:language/> 要素に追加の属性を 1 つだけ指定する必要があります。この章の前のに固執する場合、次の例は、リフレッシュ可能な Bean を実現するために Spring XML 構成で何を変更するかを示しています。

<beans>

    <!-- this bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute -->
    <lang:groovy id="messenger"
            refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks -->
            script-source="classpath:Messenger.groovy">
        <lang:property name="message" value="I Can Do The Frug" />
    </lang:groovy>

    <bean id="bookingService" class="x.y.DefaultBookingService">
        <property name="messenger" ref="messenger" />
    </bean>

</beans>

それは本当にしなければならないすべてです。messenger Bean 定義で定義されている refresh-check-delay 属性は、基になる動的言語ソースファイルに加えられた変更で Bean がリフレッシュされるまでのミリ秒数です。refresh-check-delay 属性に負の値を割り当てることにより、リフレッシュ動作をオフにできます。デフォルトでは、リフレッシュ動作は無効になっていることに注意してください。リフレッシュ動作が望ましくない場合は、属性を定義しないでください。

その後、次のアプリケーションを実行すると、リフレッシュ可能な機能を実行できます。(この次のコードスライスの「フープを介して実行する」ジャンプを許してください) System.in.read() 呼び出しは、プログラムの実行が一時停止するようにのみ存在します(このシナリオの開発者)プログラムが実行を再開したときに動的言語でサポートされる Bean でリフレッシュがトリガーされるように、基になる動的言語ソースファイルを編集します。

次のリストは、このサンプルアプリケーションを示しています。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger.getMessage());
        // pause execution while I go off and make changes to the source file...
        System.in.read();
        System.out.println(messenger.getMessage());
    }
}

次に、この例の目的のために、メッセージが引用符で囲まれるように、Messenger 実装の getMessage() メソッドへのすべての呼び出しを変更する必要があると想定します。次のリストは、プログラムの実行が一時停止したときに Messenger.groovy ソースファイルに対して行う必要のある変更(開発者)を示しています。

package org.springframework.scripting

class GroovyMessenger implements Messenger {

    private String message = "Bingo"

    public String getMessage() {
        // change the implementation to surround the message in quotes
        return "'" + this.message + "'"
    }

    public void setMessage(String message) {
        this.message = message
    }
}

プログラムを実行すると、入力一時停止前の出力は I Can Do The Frug になります。ソースファイルに変更を加えて保存し、プログラムの実行を再開した後、動的言語を使用した Messenger 実装で getMessage() メソッドを呼び出した結果は 'I Can Do The Frug' です(追加の引用符が含まれていることに注意してください)。

refresh-check-delay 値のウィンドウ内で変更が発生した場合、スクリプトを変更してもリフレッシュはトリガーされません。スクリプトへの変更は、動的言語を使用した Bean でメソッドが呼び出されるまで実際には反映されません。メソッドが動的言語に裏付けされた Bean で呼び出された場合にのみ、基礎となるスクリプトソースが変更されたかどうかを確認します。スクリプトのリフレッシュに関連する例外(コンパイルエラーの検出やスクリプトファイルが削除されたことの検出など)により、致命的な例外が呼び出し元のコードに伝播されます。

前述のリフレッシュ可能な Bean の動作は、<lang:inline-script/> 要素表記で定義された動的言語ソースファイルには適用されません(インライン動的言語ソースファイルを参照)。さらに、基礎となるソースファイルへの変更を実際に検出できる Bean にのみ適用されます(たとえば、ファイルシステムに存在する動的言語ソースファイルの最終変更日をチェックするコードによって)。

インライン動的言語ソースファイル

動的言語サポートは、Spring Bean 定義に直接埋め込まれている動的言語ソースファイルにも対応できます。具体的には、<lang:inline-script/> 要素を使用すると、Spring 構成ファイル内で動的言語ソースをすぐに定義できます。例は、インラインスクリプト機能の動作を明確にする場合があります。

<lang:groovy id="messenger">
    <lang:inline-script>

package org.springframework.scripting.groovy;

import org.springframework.scripting.Messenger

class GroovyMessenger implements Messenger {
    String message
}

    </lang:inline-script>
    <lang:property name="message" value="I Can Do The Frug" />
</lang:groovy>

Spring 構成ファイル内で動的言語ソースを定義することをお勧めするかどうかを取り巻く課題を片付けると、いくつかのシナリオで <lang:inline-script/> 要素が役立ちます。たとえば、Spring Validator 実装を Spring MVC Controller にすばやく追加したい場合があります。これは、インラインソースを使用したほんの一瞬の作業です。(そのような例については、スクリプト検証ツールを参照してください。)

動的言語を使用した Bean のコンテキストでのコンストラクター注入について

Spring の動的言語サポートに関して注意すべき非常に重要なことが 1 つあります。つまり、(現在)コンストラクター引数を動的言語を使用した Bean に提供することはできません(したがって、コンストラクターインジェクションは動的言語を使用した Bean には使用できません)。コンストラクターとプロパティ 100% のこの特別な処理を明確にするために、次のコードと構成の組み合わせは機能しません。

機能しないアプローチ
// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;

import org.springframework.scripting.Messenger

class GroovyMessenger implements Messenger {

    GroovyMessenger() {}

    // this constructor is not available for Constructor Injection
    GroovyMessenger(String message) {
        this.message = message;
    }

    String message

    String anotherMessage
}
<lang:groovy id="badMessenger"
    script-source="classpath:Messenger.groovy">
    <!-- this next constructor argument will not be injected into the GroovyMessenger -->
    <!-- in fact, this isn't even allowed according to the schema -->
    <constructor-arg value="This will not work" />

    <!-- only property values are injected into the dynamic-language-backed object -->
    <lang:property name="anotherMessage" value="Passed straight through to the dynamic-language-backed object" />

</lang>

実際には、setter インジェクションは圧倒的多数の開発者に好まれているインジェクションスタイルであるため、この制限は最初に現れるほど重要ではありません(これは別の日に良いことであるかどうかについての議論を残します)。

3.2.2. Groovy Bean

このセクションでは、Spring の Groovy で定義された Bean の使用方法について説明します。

Groovy ホームページには、次の説明が含まれています。

“Groovy は、Java 2 プラットフォーム用のアジャイルな動的言語であり、Python、Ruby、Smalltalk などの言語で多くの人が好む機能の多くを備えており、Java のような構文を使用して Java 開発者が利用できます。”

この章を真っ直ぐ読んだ場合、Groovy-dynamic-language-backed Bean の例をすでに見ています。次に、別の例を考えます(再び Spring テストスイートの例を使用します)。

package org.springframework.scripting;

public interface Calculator {

    int add(int x, int y);
}

次の例では、Groovy で Calculator インターフェースを実装します。

// from the file 'calculator.groovy'
package org.springframework.scripting.groovy

class GroovyCalculator implements Calculator {

    int add(int x, int y) {
        x + y
    }
}

次の Bean 定義では、Groovy で定義された計算機を使用しています。

<!-- from the file 'beans.xml' -->
<beans>
    <lang:groovy id="calculator" script-source="classpath:calculator.groovy"/>
</beans>

最後に、次の小さなアプリケーションが前述の構成を実行します。

package org.springframework.scripting;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        Calculator calc = ctx.getBean("calculator", Calculator.class);
        System.out.println(calc.add(2, 8));
    }
}

上記のプログラムを実行した結果の出力は(当然のことながら) 10 です。(より興味深い例については、より複雑な例については動的言語のショーケースプロジェクトを参照するか、この章の後半の例シナリオを参照してください)。

Groovy ソースファイルごとに複数のクラスを定義しないでください。これは Groovy では完全に合法ですが、(おそらく)悪い習慣です。一貫性のあるアプローチのために、(Spring チームの意見では)ソースファイルごとに 1 つの(パブリック)クラスの標準 Java 規則を考慮する必要があります。

コールバックを使用して Groovy オブジェクトをカスタマイズする

GroovyObjectCustomizer インターフェースは、Groovy-backed Bean を作成するプロセスに追加の作成ロジックをフックできるコールバックです。例:このインターフェースの実装は、必要な初期化メソッドを呼び出したり、デフォルトのプロパティ値を設定したり、カスタム MetaClass を指定したりできます。次のリストは、GroovyObjectCustomizer インターフェース定義を示しています。

public interface GroovyObjectCustomizer {

    void customize(GroovyObject goo);
}

Spring Framework は、Groovy でバックアップされた Bean のインスタンスをインスタンス化し、作成された GroovyObject を指定された GroovyObjectCustomizer (定義されている場合)に渡します。提供されている GroovyObject リファレンスを使用して、好きなことを行うことができます。ほとんどの人がこのコールバックを使用してカスタム MetaClass を設定することを期待しており、次の例はその方法を示しています。

public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer {

    public void customize(GroovyObject goo) {
        DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) {

            public Object invokeMethod(Object object, String methodName, Object[] arguments) {
                System.out.println("Invoking '" + methodName + "'.");
                return super.invokeMethod(object, methodName, arguments);
            }
        };
        metaClass.initialize();
        goo.setMetaClass(metaClass);
    }

}

Groovy でのメタプログラミングの詳細な説明は、Spring リファレンスマニュアルの範囲を超えています。Groovy リファレンスマニュアルの関連セクションを参照するか、オンラインで検索してください。多数の記事がこのトピックに対応しています。実際、Spring 名前空間サポートを使用する場合、次の例に示すように、GroovyObjectCustomizer を使用するのは簡単です。

<!-- define the GroovyObjectCustomizer just like any other bean -->
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>

    <!-- ... and plug it into the desired Groovy bean via the 'customizer-ref' attribute -->
    <lang:groovy id="calculator"
        script-source="classpath:org/springframework/scripting/groovy/Calculator.groovy"
        customizer-ref="tracingCustomizer"/>

Spring 名前空間サポートを使用しない場合でも、次の例に示すように、GroovyObjectCustomizer 機能を引き続き使用できます。

<bean id="calculator" class="org.springframework.scripting.groovy.GroovyScriptFactory">
    <constructor-arg value="classpath:org/springframework/scripting/groovy/Calculator.groovy"/>
    <!-- define the GroovyObjectCustomizer (as an inner bean) -->
    <constructor-arg>
        <bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
    </constructor-arg>
</bean>

<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
Groovy CompilationCustomizer (ImportCustomizer など)または Spring の GroovyObjectCustomizer と同じ場所に完全な Groovy CompilerConfiguration オブジェクトを指定することもできます。さらに、ConfigurableApplicationContext.setClassLoader レベルで Bean のカスタム構成を使用して共通の GroovyClassLoader を設定できます。これは共有 GroovyClassLoader の使用にもつながるため、スクリプト化された Bean が多数ある場合に推奨されます(Bean ごとに分離された GroovyClassLoader インスタンスを避けます)。

3.2.3. BeanShell Bean

このセクションでは、Spring で BeanShell Bean を使用する方法について説明します。

BeanShell ホームページ (英語) には次の説明が含まれています。

BeanShell is a small, free, embeddable Java source interpreter with dynamic language
features, written in Java. BeanShell dynamically runs standard Java syntax and
extends it with common scripting conveniences such as loose types, commands, and method
closures like those in Perl and JavaScript.

Groovy とは対照的に、BeanShell-backed Bean 定義には、いくつかの(小さな)追加の構成が必要です。Spring は、<lang:bsh> 要素の script-interfaces 属性値で指定されたすべてのインターフェースを実装する JDK 動的プロキシを作成するため、Spring での BeanShell 動的言語サポートの実装は興味深いです(これが、少なくとも 1 つのインターフェースを提供する必要がある理由です)属性の値、およびその結果、BeanShell-backed Bean を使用する場合のインターフェースへのプログラム)。つまり、BeanShell-backed オブジェクトのすべてのメソッド呼び出しは、JDK 動的プロキシ呼び出しメカニズムを通過します。

これで、この章で前述した Messenger インターフェースを実装する BeanShell ベースの Bean を使用した完全に機能する例を示すことができます。再び Messenger インターフェースの定義を示します。

package org.springframework.scripting;

public interface Messenger {

    String getMessage();
}

次の例は、Messenger インターフェースの BeanShell「実装」(ここでは大まかに使用します)を示しています。

String message;

String getMessage() {
    return message;
}

void setMessage(String aMessage) {
    message = aMessage;
}

次の例は、上記の「クラス」の「インスタンス」を定義する Spring XML を示しています(ここでも、これらの用語を非常に大まかに使用しています)。

<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh"
    script-interfaces="org.springframework.scripting.Messenger">

    <lang:property name="message" value="Hello World!" />
</lang:bsh>

BeanShell ベースの Bean を使用する場合のシナリオについては、シナリオを参照してください。

3.3. シナリオ

スクリプト言語で Spring マネージド Bean を定義することが有益なシナリオには、さまざまなものがあります。このセクションでは、Spring での動的言語サポートの 2 つの使用例について説明します。

3.3.1. スクリプト化された Spring MVC コントローラー

動的言語を使用した Bean を使用することでメリットが得られるクラスのグループの 1 つは、Spring MVC コントローラーのクラスです。純粋な Spring MVC アプリケーションでは、Web アプリケーションを介したナビゲーションフローは、Spring MVC コントローラー内にカプセル化されたコードによって大幅に決定されます。Web アプリケーションのナビゲーションフローおよびその他のプレゼンテーションレイヤーロジックは、サポートの課題やビジネス要件の変化に対応するために更新する必要があるため、1 つ以上の動的言語ソースファイルを編集して確認することにより、必要な変更を簡単に実行できる可能性があります。変更は実行中のアプリケーションの状態にすぐに反映されます。

Spring などのプロジェクトで採用されている軽量のアーキテクチャモデルでは、通常、非常に薄いプレゼンテーションレイヤーを目指しており、アプリケーションの肉付きの良いビジネスロジックはすべてドメインおよびサービスレイヤークラスに含まれています。Spring MVC コントローラーを動的言語を使用した Bean として開発すると、テキストファイルを編集して保存することにより、プレゼンテーションレイヤーのロジックを変更できます。このような動的言語ソースファイルへの変更は、(構成に応じて)動的言語ソースファイルによってサポートされる Bean に自動的に反映されます。

動的言語ベースの Bean に対する変更のこの自動「ピックアップ」を有効にするには、「リフレッシュ可能な Bean」機能を有効にする必要があります。この機能の完全な取り扱いについては、リフレッシュ可能な Bean を参照してください。

次の例は、Groovy 動的言語を使用して実装された org.springframework.web.servlet.mvc.Controller を示しています。

// from the file '/WEB-INF/groovy/FortuneController.groovy'
package org.springframework.showcase.fortune.web

import org.springframework.showcase.fortune.service.FortuneService
import org.springframework.showcase.fortune.domain.Fortune
import org.springframework.web.servlet.ModelAndView
import org.springframework.web.servlet.mvc.Controller

import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class FortuneController implements Controller {

    @Property FortuneService fortuneService

    ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse httpServletResponse) {
        return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
    }
}
<lang:groovy id="fortune"
        refresh-check-delay="3000"
        script-source="/WEB-INF/groovy/FortuneController.groovy">
    <lang:property name="fortuneService" ref="fortuneService"/>
</lang:groovy>

3.3.2. スクリプト検証ツール

Spring を使用したアプリケーション開発のもう 1 つの分野は、動的言語を使用した Bean によって提供される柔軟性の恩恵を受ける可能性があります。通常の Java とは対照的に、緩やかに型付けされた動的言語(インライン正規表現もサポートしている場合があります)を使用すると、複雑な検証ロジックを簡単に表現できます。

繰り返しますが、バリデーターを動的言語を使用した Bean として開発すると、単純なテキストファイルを編集して保存することで検証ロジックを変更できます。このような変更は(構成に応じて)実行中のアプリケーションの実行に自動的に反映されるため、アプリケーションの再起動は必要ありません。

動的言語を使用した Bean の変更を自動的に「ピックアップ」するには、「リフレッシュ可能な Bean」機能を有効にする必要があります。この機能の完全かつ詳細な処理については、リフレッシュ可能な Bean を参照してください。

次の例は、Groovy 動的言語を使用して実装された Spring org.springframework.validation.Validator を示しています(Validator インターフェースの説明については、Spring の Validator インターフェースを使用した検証を参照してください)。

import org.springframework.validation.Validator
import org.springframework.validation.Errors
import org.springframework.beans.TestBean

class TestBeanValidator implements Validator {

    boolean supports(Class clazz) {
        return TestBean.class.isAssignableFrom(clazz)
    }

    void validate(Object bean, Errors errors) {
        if(bean.name?.trim()?.size() > 0) {
            return
        }
        errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
    }
}

3.4. 追加の詳細

この最後のセクションには、動的言語サポートに関連する追加の詳細が含まれています。

3.4.1. AOP —  スクリプト Bean のアドバイス

Spring AOP フレームワークを使用して、スクリプト化された Bean をアドバイスできます。Spring AOP フレームワークは、アドバイスされている Bean がスクリプト化された Bean である可能性があることを実際に認識していないため、使用する(または使用を目的とする)AOP ユースケースおよび機能はすべて、スクリプト化された Bean で動作します。スクリプト化された Bean をアドバイスする場合、クラスベースのプロキシを使用できません。インターフェースベースのプロキシを使用する必要があります。

スクリプト化された Bean のアドバイスに限定されません。サポートされている動的言語でアスペクト自体を記述し、そのような Bean を使用して他の Spring Bean に助言することもできます。ただし、これは実際には動的言語サポートの高度な使用になります。

3.4.2. スコーピング

すぐにはわからない場合は、他の Bean と同じ方法でスクリプト Bean をスコープできます。さまざまな <lang:language/> 要素の scope 属性を使用すると、通常の Bean と同様に、基になるスクリプト化された Bean のスコープを制御できます。(デフォルトのスコープは、「通常の」Bean と同様に singleton です。)

次の例では、scope 属性を使用して、プロトタイプとしてスコープ指定された Groovy Bean を定義しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger" script-source="classpath:Messenger.groovy" scope="prototype">
        <lang:property name="message" value="I Can Do The RoboCop" />
    </lang:groovy>

    <bean id="bookingService" class="x.y.DefaultBookingService">
        <property name="messenger" ref="messenger" />
    </bean>

</beans>

Spring Framework のスコーピングサポートの詳細については、IoC コンテナーBean スコープを参照してください。

3.4.3. lang XML スキーマ

Spring XML 構成の lang 要素は、Spring コンテナー内の Bean として動的言語(Groovy や BeanShell など)で作成されたオブジェクトの公開を処理します。

これらの要素(および動的言語サポート)は、動的言語サポートで包括的にカバーされています。このサポートおよび lang 要素の詳細については、その章を参照してください。

lang スキーマの要素を使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です。次のスニペットのテキストは正しいスキーマを参照しているため、lang 名前空間のタグを使用できます。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">

    <!-- bean definitions here -->

</beans>

3.5. その他のリソース

次のリンクは、この章で参照されているさまざまな動的言語に関する詳細なリソースに移動します。