@making's memo

All Categories All Tags


Spring Framework 5のKotlinサポート

Edit History

Spring 5でフレームワークのコア部分でKotlin対応が入る。

KotlinサポートのポイントはExtension FunctionsReified type parameters

Spring 5での対応がどのようなものを見る前に、この二つのKotlinの言語仕様を知っておくと理解しやすい。

次のJavaコードを例に簡単に説明する。

package com.example;

public class Foo {
    public <T> T create(Class<T> clazz) {
        try {
            return clazz.newInstance();
        }
        catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}

この機能をJavaで使うと

Foo foo = new Foo();
Bar bar = foo.create(Bar.class);

となる。Javaユーザーにとっては特に違和感のない、よくある使い方の一つだと思う。

ではこのコードをそのままKotlinで書くとどうなるか。

val foo = Foo()
val bar = foo.create(Bar::class.java)

Bar::classで得られるのはKClass<Bar>(kotlin.reflect.KClass)であり、Class<Bar>(java.lang.Class)に変換するのに.javaをつける必要がある。

このままでも使えないわけではないが、Javaで書くより簡単になってる?感が出てくる。
Kotlin対応していないJavaライブラリ、フレームワークとはこのような付き合い方になる。

Extension Functions

val bar = foo.create(Bar::class)

って書きたい。でもJavaフレームワーク側で直接Kotlinのクラスを使いたくない。
そんな時にKotlinのExtension Functionsが使うと、あたかもFooクラスにメソッドを追加したかのように見せられる。

FooExtensions.ktというファイルに

package com.example

import kotlin.reflect.KClass


fun <T : Any> Foo.create(kclass: KClass<T>) = create(kclass.java)

を書くとFooクラスのメソッドとしてcreate(KClass<T>)を追加し、中でjava.lang.Classに変換してcreate(java.lang.Class)を呼び出すことができる。

これで

val foo = Foo()
val bar = foo.create(Bar::class)

と素直にKotlinコードを書くことができるようになる。

Reified type parameters

もう一歩進もう。KotlinにはReified type parametersという仕組みがあり、Javaではコンパイル時に消えてしまうジェネリクスの型を、inline展開することでコード中で参照することができる。ということはJavaでは書けなかったTをごにょごにょ...ってのが可能になり、メソッド引数からClassを消すことができる。

これを使ってFooExtensions.ktに次のメソッドを追加する。

inline fun <reified T : Any> Foo.create() = create(T::class.java)

これで

val foo = Foo()
val bar = foo.create<Bar>()

こう書ける。

あるいは左辺の型推論を使って

val foo = Foo()
val bar: Bar = foo.create()

こう書くこともできる。

このようなコードが"idiomatic Kotlin code"(Kotlinらしいコード)と呼ばれる。

Spring 5の"idiomatic Kotlin code"

Springユーザーならすでに気づいているかもしれないが、Springの中にはClass<T> clazzを引数に取るメソッドが多くある。
それらに対して上記のような**Extensions.ktが用意されている。外部ライブラリではなくSpring Framework本体に含まれているのである。

例えば、今まで書いていた

ApplicationContext context = ...;
Bar bar = context.getBean(Bar.class);

val bar = context.getBean(Bar::class)

こう書けるし

val bar = context.getBean<Bean>()

こう書けるし

val bar: Bar = context.getBean()

こう書くこともできる。

今まで書いていた

Long count = jdbcTemplate.queryForObject("SELECT count(*) FROM foo", Long.class);

val count = jdbcTemplate.queryForObject("SELECT count(*) FROM foo", Long::class)

こう書けるし

val count = jdbcTemplate.queryForObject<Long>("SELECT count(*) FROM foo")

こう書けるし

val count: Long = jdbcTemplate.queryForObject("SELECT count(*) FROM foo")

こう書くこともできる。

特に恩恵を受けるのはRestTemplateだろう。

String foo = restTemplate.getForObject("http://example.com", String.class);

val foo: String = restTemplate.getForObject("http://example.com")

書けるのはわかった。

けれども、

List<Foo> foos = restTemplate.exchange("http://api.example.com/foos", HttpMethod.GET, null, new ParameterizedTypeReference<List<Foo>>() { }).getBody();

これが

val foos: List<Foo> = restTemplate.getForObject("http://api.example.com/foos")

こう書けるようになるのは素晴らしくないか。

Spring 5ではこのようなKotlinによる改善が至るところで利用できるようになる。


その他

  • Functional Router Functions
  • Functional Bean Registration

といった、Spring 5自体の全く新しい機能にも初めからKotlinのExtensionsが用意されているし、

org.springframework.ui.ModelにArray like setterが追加され、

model.addAttribute("foo", foo);

model["foo"] = foo

と書けるようになったりする。

この記事を読んだ後なら

https://spring.io/blog/2017/01/04/introducing-kotlin-support-in-spring-framework-5-0

を読めるようになっていると思う。

ちなみにKotlinのメソッドがデフォルトでfinalになるのでopenを明示的につけなけらばいけなかった問題はkotlin-springプラグインで解決されている。

このあたりはSPRING INITIALIZRでLanguageにKotlinを選択することで初めから設定済みになっている。

image

なのですぐにSpring + Kotlinなアプリケーションを始めることができる。

Spring ❤️ Kotlin

Spring 5に備えてKotlinを学んでおきたければKotlinスタートブック -新しいAndroidプログラミングがとっつきやすい。この記事で説明したExtension FunctionsReified type parametersにも触れられている。

Kotlinスタートブック -新しいAndroidプログラミング

このエントリーをはてなブックマークに追加