普段の業務ではPHP(Laravel)を使っています。
この間Android開発でKotlinを経験し、一度は静的型付言語でWeb開発をしてみたいとも思っていたのでKotlin×Spring Bootで簡単なWebアプリを作ってみることにしました。
Kotlin触ってて思ったのが、Javaの知識を前提に書かれている情報が多いな〜1ということでした。
自分は基本情報対策くらいしかJavaやってなくて、結構調べなきゃいけなかったりと面倒だったので、できるだけJavaの知識がなくても分かるように書いていこうと思います。
環境
- OS: macOS Catalina
- 言語: Kotlin
- フレームワーク: Spring Boot
- ビルドツール: Gradle
- IDE: Intellij IDEA
フレームワークをSpring Bootにした理由はKotlinでWeb開発するなら一番スタンダードっぽかったからです。
KtorというAPIサーバーに良さそうなフレームワークも気になるので今度試してみたいです。
事前準備
-
JDK(Java Development Kit)をインストールします。
JDKについては次の項目で解説していますので、分からない方はこちらを読んでみてください。 -
Intellij IDEAをインストールし、セットアップをします。
Intellij IDEAはKotlinの開発元であるJetBrainsが作っているIDEなこともあって、ほぼ完璧にkotlinをサポートしています。セットアップ自体は誘導に従えば大丈夫だと思います。参考になりそうな記事はこちらに。
↑自分はどっちもHomebrewでインストールしました。
ちなみにKotlinはIntellij IDEAに付属しているため、別にインストールしなくても使えます。
予備知識
JDK
準備のところでJDKをインストールしましたが、"JDKって何のために必要なんだ?KotlinなんだからKotlin用のコンパイラとかビルドツールがあれば実行できるんじゃないの?"と疑問に思った人もいるかもしれません。
そういう人のためにJavaの実行環境について少し解説します。
図1を見てください。まず、通常のコンパイル言語と違ってJavaのプログラムはコンパイルしてもいきなり実行可能ファイルができる訳ではありません。Javaバイトコードで書かれたJavaクラスファイルと呼ばれるものが生成されます。
そして、バイトコードはJVM(Java Virtual Machine)という各OS用に用意された仮想マシン上で逐一解釈されながら実行されます。この仕組みのおかげで、わざわざ環境ごとにコンパイルを行う必要なく、一度コンパイルするだけでJVMが用意された環境でならどこでも動かせるようになっています。
知ってはいると思いますが、KotlinはJVM言語2です。
その意味するところは、コンパイルされるとJVMが解釈できるJavaバイトコードを生成する言語であるということです。(図2)
図2
つまり、kotlinを実行したり開発する環境ではJVMが動いていなければいけません。
ここでやっとJDKの話が出てきます。
JDKにはJVM・Javaコンパイラ・ライブラリ等のJavaの開発に必要な物のセットです。したがって、準備の項目でJDKをインストールしたという訳です。
おそらく、純粋なKotlinのコードを実行するだけなら、KotlinのコンパイラとJVMがあれば動くと思いますが3、Javaのライブラリを使ったり、Javaと混在して使ったりする場合がほとんどだと思うのでJDKは入れておきましょう。
Gradle
Gradleはビルドツールで、コンパイル・リンク・テストなどを自動で行ってくれます。
ただ、自分は他のビルドツール4を使ったことがないのでここがいいぞ!みたいなことは良く分かってません。
設定をGroovyという言語で行うことは大きな特徴の一つだと思うので、その書き方くらいはある程度分かってるといいと思います。以下の記事が参考になります。
Groovyを知らない人のためのbuild.gradle読み書き入門
Spring Boot
Spring Bootは従来のJavaのフレームワークであるSpring Frameworkを簡単に使えるようにしたフレームワークです。Spring Frameworkの複雑なxmlでの設定をなくし、アノテーションを使うことでコードを簡略化しているのが特徴のようです。
Spring Frameworkを触っていないので、どのくらい楽になったとかは分かりませんが、実際の動きは後々見ていきます。
環境構築
前置きが長くなってしまいましたが、Spring Bootの環境構築をしていきましょう。
プロジェクトの雛形を作成
-
Spring Initializrにアクセスします。
Spring Initializrを使うと簡単にSpring Bootプロジェクトの雛形を作ることができます。CLIもあるみたいです。 -
以下の画像の通り設定。
Groupはパッケージ名、Artifactはビルドして生成される.jarファイルの名前になります。
Dependenciesには以下のものを追加します。- Spring Web: Webアプリ作成に必要な依存関係定義のセット
- Thymleaf: テンプレートエンジン
- Spring Data JPA: ORM
- H2 Database: 軽量な組み込みDB
-
"Generate"をクリックすると、雛形のzipがダウンロードされるので、解凍します。
Intellij IDEAでプロジェクトを設定
-
Intellij IDEAを開き、"Open or Import"をクリックして、先ほど解凍したディレクトリを選択します。
-
"springboot/src/main/kotlin/com/example/springboot/SpringbootApplication.kt"を開いてみます。
このSpringbootApplication.kt
ファイルのトップレベルに定義されたmain
メソッドがプロジェクトのエントリポイントになります。 -
main
メソッドの左側にある緑の▷
マークを押すと、アプリケーションが起動します。
次からは、右上の緑の▷
マークからアプリケーションを起動できるようになります。
ブラウザで登録・表示をできるようにする
今回は簡素ですがブラウザからユーザーを追加、一覧表示できるアプリケーションを作成しました。
成果物
ディレクトリ構造
src
├── kotlin
│ └── com
│ └── example
│ └── springboot
│ ├── SpringbootApplication.kt
│ ├── controller
│ │ └── MainController.kt
│ ├── entity
│ │ └── User.kt
│ └── repository
│ └── UserRepository.kt
└── resources
├── application.properties
└── templates
├── add.html
└── index.html
**コード**
package com.example.springboot.entity
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
@Entity
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int = 0,
var name: String = ""
)
package com.example.springboot.repository
import com.example.springboot.entity.User
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, Int>
package com.example.springboot.controller
import com.example.springboot.entity.User
import com.example.springboot.repository.UserRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
@Controller
class MainController {
@Autowired
lateinit var userRepository: UserRepository
@GetMapping("/")
fun showUsers(model: Model): String {
val users = userRepository.findAll()
model.addAttribute("users", users)
return "index"
}
@GetMapping("/add")
fun showAddPage(): String {
return "add"
}
@PostMapping("/add")
fun addNewUser(@RequestParam name: String): String {
userRepository.save(User(0, name))
return "redirect:/"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1>Users</h1>
<input type="button" onclick="location.href='/add'" value="追加ページ">
<ul>
<li th:each="user : ${users}">[[${user.name}]]</li>
</ul>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Add</title>
</head>
<body>
<h1>Add</h1>
<form action="/add" method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<button type="submit">追加</button>
</form>
</body>
</html>
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:./h2db/db_example
spring.datasource.username=username
spring.datasource.password=password
- トップページの表示
- ユーザー追加ページへの遷移
- ユーザーの追加
1. トップページを表示
まず、ユーザーが/
にアクセスしてきた時に、リクエストを受け取って処理を行うコントローラーから説明します。
package com.example.springboot.controller
略
@Controller
class MainController {
@Autowired
lateinit var userRepository: UserRepository
@GetMapping("/")
fun showUsers(model: Model): String {
val users = userRepository.findAll()
model.addAttribute("users", users)
return "index"
}
略
@Controller
コントローラーのクラス名の前に@Controller
アノテーションもしくは@RestController
をつけます。
後述するテンプレートを使う場合は、@Controller
を使用します。@RestController
を使うと、メソッドの戻り値がそのままレスポンスボディになります。
HTMLを返したい時は@Controller
、APIのエントリポイントにしたいなら@RestController
という認識で大丈夫だと思います。
@Autowired
@Autowired
アノテーションはDIするクラスを指定する為に使います。
今回では、DBからのデータの取得を行うUserRepository
のインスタンスが注入されます。
@GetMapping
@GetMapping("/")
アノテーションは、/
に対してGET
メソッドでリクエストが飛んできた場合、アノテートされたメソッド(今回はshowUsers()
)が呼び出されるということを意味しています。
Model
テンプレートに値を渡したい時は、メソッドの引数にModelを指定し、ModelのaddAttribute()
メソッドの第一引数にテンプレートでの変数名、第二引数に値を渡します。
userRepository.findAll()
DIしたUserRepository
インスタンスの全ユーザーを取得するメソッドを実行しています。
~Repository
クラスはデータの取得を抽象化5するレイヤです。UserRepository
クラスの中身については後述します。
return "index"
テンプレートに"templates/index.html"を指定しています。
@Controller
がついたコントローラー内のメソッドの返り値に文字列を指定すると、そのファイル名のテンプレートをViewに指定します。
それでは、Viewに指定されているテンプレートを見てみましょう。テンプレートエンジンには前述した通り、Thymleafが使われています。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1>Users</h1>
<input type="button" onclick="location.href='/add'" value="追加ページ">
<ul>
<li th:each="user : ${users}">[[${user.name}]]</li>
</ul>
</body>
</html>
${users}
コントローラーから渡された値は${}
で囲って使うことができます。
th:each=""
th:each="各要素を格納する変数名 : ${iterableな変数}"
で繰り返しタグを生成出来ます。
[[${user.name}]]
これをタグで挟むと[[]]
中身がその要素のテキストとして出力されます。
<タグ名 th:text="中身">
も同じことを表します。
次に、データの取得を抽象化するリポジトリを説明します。
package com.example.springboot.repository
import com.example.springboot.entity.User
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, Int>
CrudRepository
Spring Data JPAが用意した、テーブル操作の基本であるCRUD(Create、Read、Upload、Delete)を行うインターフェースです。このインターフェースを実装したインターフェースを用意することでCRUD操作が可能になります。
また、CrudRepository
の第一型引数には操作するエンティティクラス、第二型引数にはエンティティのIDの型を指定します。
はじめは、コントローラーにUserRepository
が注入されているのに、UserRepository
の定義はインターフェースになっていて、どうやって実装クラスなしにインスタンスを生成するのか不思議でしたが、どうやらSpring Data JPAが実行時によしなにやってくれているようです。(適当ですみません)
最後に、リポジトリで使用されていたエンティティの中身をみます。エンティティはDBの1テーブルに対応します。
略
@Entity
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int = 0,
var name: String = ""
)
@Entity
このアノテーションがついたクラスと同名のテーブルがマッピングされます。
@Id
主キーに対応するフィールドに付けます。
@GeneratedValue(strategy = GenerationType.IDENTITY)
MysqlでいうAUTO_INCREMENT
のような指定。
GenerationType.
の後にくる値で意味が変わってきます。
値 | 意味 |
---|---|
IDENTITY | AUTO_INCREMENTを指定 |
SEQUENCE | DBのシーケンシャルオブジェクトを使用する |
TABLE | シーケンシャルな値を管理するテーブルを生成する |
AUTO | DBごとに異なる方法を選択する |
2. ユーザー追加ページへの遷移
次に、ユーザーが"追加ページ"ボタンを押して/add
にアクセスした場合のコントローラー動きを見ます。
略
@Controller
class MainController {
略
@GetMapping("/add")
fun showAddPage(): String {
return "add"
}
略
}
ここでは/add
にGET
メソッドで来たリクエストをshowAddPage()
メソッドにマッピングして、テンプレートに"add.html"を指定しています。
では、そのテンプレートの中身を見ます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Add</title>
</head>
<body>
<h1>Add</h1>
<form action="/add" method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<button type="submit">追加</button>
</form>
</body>
</html>
ここでは特別なことはしておらず、単純にname
という項目のフォームとsubmitボタンがあるだけです。
3. ユーザーの追加
最後に、/add
にPOST
メソッドのリクエストが来た時のコントローラーの動きを確認します。
略
@Controller
class MainController {
@Autowired
lateinit var userRepository: UserRepository
略
@PostMapping("/add")
fun addNewUser(@RequestParam name: String): String {
userRepository.save(User(0, name))
return "redirect:/"
}
}
@PostMapping
前述の@GetMapping
アノテーションから推測できると思いますが、/add
に対してPOST
メソッドでリクエストが飛んできた場合、アノテートされたメソッド(今回はaddNewUser()
)が呼び出されます。
@RequestParam
指定した引数がリクエストパラメータであることを示します。ここではフォームで入力した文字列がnameという変数に入ります。
userRepository.save(User(0, name))
ここではフィールドname
がaddNewUser()
に渡されたname
であるようなインスタンスを生成し、DBに保存しています。
0
になっている箇所はIDで、ここに既存のIDを入れてしまうとUpdateになってしまうので、存在しない0
を渡し、Createするようにしています。
return "redirect:/"
/
にリダイレクトさせるという意味になります。
以上で、今回のアプリケーションで行っている処理については終わりました。ここで今更ですが、application.propeties
で行っているDBの接続情報の設定を少しだけ説明します。
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:./h2db/db_example
spring.datasource.username=username
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
アプリケーションの起動時にDBのスキーマに対する操作を設定します。有効にすると@Entity
が付与されたクラス通りにスキーマを生成してくれます。設定値は以下の通りです。
値 | 意味 |
---|---|
note | 何もしない |
validate | 検証のみ行う |
update | 必要なテーブルがない場合は作成する |
create | 必要なテーブルを作成する。既存のテーブルは削除し、新規作成する |
create-drop | 必要なテーブルを作成する。セッション終了後に削除する |
spring.datasource.driver-class-name=org.h2.Driver
DBの接続ドライバの指定です。
spring.datasource.url=jdbc:h2:./h2db/db_example
DBの接続先URLです。今回はファイルに保存をしています。
spring.datasource.username=username
DBの接続ユーザーです。
spring.datasource.password=password
DBの接続パスワードです。
最後に
初めて静的型付言語でweb開発やってみました。まだまだ規模が小さくてPHP×Laravelとの比較など行えませんが、これから色々いじってみて良いところ、悪いところを探して行こうかなという感じです。
ktorも気になりますし、マイクロサービスなんかも気になるのでこれから勉強して行けたらいいなと思っています。
参考情報
横山恭大, 『入門!実践!サーバーサイドkotlin』, 株式会社インプレス R&D, 2019
Spring Boot と Kotlin を使用した Web アプリケーションの構築
Spring Boot で Thymeleaf 使い方メモ
Spring Boot 使い方メモ