みなさんKotlin使ってますか?
サーバーサイドでも便利なので、自分は最近よく使っています。
API側はよくみるので、今回はサーバーサイドレンダリングの方を書いてみます💪
Kotlinのいいところ
特に、一番上のメリットは大きく、Better Javaとして混在させることもできます。
準備
使うもの
各種ソフトウェア | バージョン |
---|---|
IntelliJ (できればUltimate) | 新しいやつ |
JDK | 1.8.x |
Kotlin | 1.3.x |
Spring Boot | 2.1.x |
謎の圧力によってSpring Bootを使っていきます。Spring Bootの中で使用する機能は、
- spring-boot-starter-web
- spring-boot-starter-thymeleaf
- spring-boot-starter-test
の3つになります。thymeleaf
はテンプレートエンジンです。
プロジェクト作成
IntelliJ Ultimateの場合、新しくプロジェクトを作る際にSpringの選択肢があると思いますが、Communityの場合はSpring Intializrから作る必要があります。
MavenかGradleか好きな方をを選択して、Web
とThymeleaf
を指定します。"Generate Project"を押すと、いい感じの雛形が生成されます。
(ホットリロードしてくれる、Devtools
も便利ですが、IntelliJでは若干設定が必要です)
雛形を見てみる
最初に一つだけ、ソースファイルが生成されるはずなので、そちらを見ていきます。
package com.example.demo import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication open class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) }
一箇所だけ修正点があるのですが、open class DemoApplication
のopen
のところだけ付け足しています。
KotlinのクラスはJavaのfinal class
相当なので、@SpringBootApplication
が子クラスを生成出来るようにopen
を明示的に指定します。
indexページを作る
中身はなんでも良いので、resources/templates
にindex.html
を作っておきます。
(error.html
も作っておくと、5xxエラー時にいかにもなページが出ずに済みます。)
実装
Controllerを書く
シンプルにindexページだけ返すControllerを作ってみます。
import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping @Controller class RootController { private enum class PathTemplate(val path: String) { INDEX("index") } @GetMapping("") fun index(): String { return PathTemplate.INDEX.path } }
enum
で値を持てるので、Javaだと@AllArgsConstructor
と@Getter
でゴリゴリ書いていた定数の部分が楽になります。
この段階で、ビルド(gradleの場合bootRun
)すれば、localhost:8080
で動くはずです。
WebConfigを書く
Spring Applicationが起動した時に読み込む、WebMvcConfigurer
を設定していきます。
今回の場合、css
、js
を配置するため、static
以下のファイルを全てリソースとして追加します。
import org.springframework.context.annotation.Configuration import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @Configuration open class WebConfig : WebMvcConfigurer { override fun addResourceHandlers(registry: ResourceHandlerRegistry) { registry .addResourceHandler("/static/**") .addResourceLocations("classpath:/static/") } }
先ほどと同じ理由で、open
がついています。
オブジェクトをレンダリングする
実際に、オブジェクトをレンダリングしていきます。
controller
からModelAndView
を使ってthymeleaf
に渡していきます。
Modelクラスは色々な実装があり得ると思いますが、Kotlinにはdata
クラスという、単純なプロパティの集まりを表現するクラスがあるのでそれを例にしていきます。
Javaでいうと、Lombokの@Data
におまけがついたような感じです。
data class Item ( val user: String, val message: String, )
Kotlinで記述した際に特徴的なのは、()
の部分はコンストラクタなのですが、逆にコンストラクタから、コンパイラが中のプロパティを推論してくれるので、明示的なプロパティの記述は必要なくなります。
@GetMapping("") fun index(): ModelAndView { val modelAndView = ModelAndView(PathTemplate.INDEX.path) modelAndView.addObject("item", Item("Taro", "Hello!!")) return modelAndView }
ModelAndView
に追加したので、thymeleaf
からそのオブジェクトを参照することができます。
index.html
を編集してみます。
<div> <p> <span th:text="|${item.user}が${item.message}と言っています|">aaa</span> </p> </div>
th:text
の部分が、thymeleaf
が置き換えてくれる部分です。
${}
で文字列展開を行い、またそれを|
で囲うと、文字列展開する部分以外はそのまま出力になります。
DIコンテナを使ってみる
Spring BootにはDIコンテナが内蔵されています。
コンストラクタインジェクションで、実際にDIをしてみます。
Service
クラスを用意します。Spring Bootでは、@Service
をつけることで、DIの対象となります。
interface IItemFetcher { fun fetch() : List<Item>? } @Service class ItemFetcher : IItemFetcher { override func fetch() : List<Item>? { /** 実装 */ } }
interface
を用意しましたが、Spring Bootではinterface
に対しても、実装が一つならば、DIをすることができます。
2つ以上の実装がある場合は、明示的に指定したり、@Profile
などのアノテーションでprofile(application-xxx.yml
)の切り替えを使って、DIできます。
実際に、このService
クラスをDIします。
@Controller class RootController ( private val itemFetcher: IItemFetcher ) { @GetMapping("") fun index(): ModelAndView { val modelAndView = ModelAndView(PathTemplate.INDEX.path) modelAndView.addObject("items", itemFetcher.fetch()) return modelAndView } }
このように、コンストラクタを追加するだけで、itemFetcher
にDIが自動で行われます。
中身は、Javaで@RequiredArgsConstructor
をつけて、各プロパティにコンストラクタインジェクションをしていることと同じです。
// Javaの例 @Controller @RequiredArgsConstructor public final class RootController { // finalなので、RequiredArgsConstructorでこのプロパティをセットするコンストラクタができる @Nonnull private final IItemFetcher itemFetcher; /** ... */ }
リストをレンダリングする
thymeleaf
では、リストもレンダリングすることができます。th:each
というプロパティを使います。
<div> <p th:each="item : ${items}"> <span th:text="|${item.user}が${item.message}と言っています|">aaa</span> </p> </div>
クライアントからはこのようなHTMLが見えます。
<div> <p> <span>TaroがHello!!!と言っています</span> </p> <p> <span>JiroがBye!!!と言っています</span> </p> </div>
Slf4jを使う
残念ながら、Kotlinにはstatic
がないので、Lombokの@Slf4j
は使えません。
代わりに、companion object
を使います。
import org.slf4j.loggerFactory @Controller class RootController ( private val itemFetcher: IItemFetcher ) { companion object { private val logger = LoggerFactory.getLogger(RootController::class.java) } @GetMapping("") fun index(): ModelAndView { logger.info("GET ./") /** ... */ } }
companion object
はオブジェクトであり、static
とは若干違いますが、@JvmStatic
でJavaからstatic
フィールドとして扱うことができます。
おわりに
thymeleaf
など詳しく書けなかった部分は、また今度書きます。
自分は最近Nuxt.js
にも興味があります👀