はじめに
ごくごく当たり前の内容ではありますが、個人的にGradleは難解だなと思っており… KotlinとSpring Bootを使ってweb appを作るときのミニマムな構成についてまとめます。 Gradleの設定に主眼をおいている音で、Spring Boot自体の説明やapp自体の実装の話はないです。
Spring Boot公式のチュートリアル との違いとしては、build scriptに .kts
を使っているのとversion catalog を利用しているところですかね。まぁ今どき新規で maven
や build.gradle
を使ったりはしないと思いますが、version catalogはお好みでという感じです。
1. Gradle プロジェクトの初期化
gradleコマンドで行います。今回はJava17を使った単一のアプリケーションのプロジェクトとしています。
❯ sdk current gradle
Using gradle version 8.12
❯ gradle init
Select type of build to generate:
1: Application
2: Library
3: Gradle plugin
4: Basic (build structure only)
Enter selection (default: Application) [1..4] 1
Select implementation language:
1: Java
2: Kotlin
3: Groovy
4: Scala
5: C++
6: Swift
Enter selection (default: Java) [1..6] 2
Enter target Java version (min: 7, default: 21): 17
Project name (default: kotlin-spring-boot-minimum):
Select application structure:
1: Single application project
2: Application and library project
Enter selection (default: Single application project) [1..2] 1
Select build script DSL:
1: Kotlin
2: Groovy
Enter selection (default: Kotlin) [1..2] 1
Select test framework:
1: kotlin.test
2: JUnit Jupiter
Enter selection (default: kotlin.test) [1..2] 2
Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] no
> Task :init
Learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.12/samples/sample_building_kotlin_applications.html
BUILD SUCCESSFUL in 40s
1 actionable task: 1 executed
完了すると最初はこんなディレクトリ構成となります。ルートに settings.gradle.kts
、appの中に build.gradle.kts
、gradleの中にversion catalog向けの libs.versions.toml
があります。
❯ tree
.
├── app
│ ├── build.gradle.kts
│ └── src
│ ├── main
│ │ ├── kotlin
│ │ │ └── org
│ │ │ └── example
│ │ │ └── App.kt
│ │ └── resources
│ └── test
│ ├── kotlin
│ │ └── org
│ │ └── example
│ │ └── AppTest.kt
│ └── resources
├── gradle
│ ├── libs.versions.toml
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
15 directories, 10 files
ref. 9a9031a
また。app/src/main/kotlin
以下のディレクトリはデフォルトだとpackage構成に従ってディレクトリが掘られています。
今回だと App.kt
が org.example
というpackageにあるので、パスが org/example/App.kt
となっています。
ただ、これはJavaの慣習であり、Kotlinの Coding conventions では共通部分の階層は消すのがおすすめされています。ディレクトリが謎に深いというのはJavaあるあるですが、無くて良いと思うので削除します。
❯ tree
.
├── app
│ ├── build.gradle.kts
│ └── src
│ ├── main
│ │ ├── kotlin
│ │ │ └── App.kt
│ │ └── resources
│ └── test
│ ├── kotlin
│ │ └── AppTest.kt
│ └── resources
...
幾分すっきりします。
2. 依存を確認する
先にも触れたように、依存するライブラリやGradleプラグインは gradle/libs.versions.toml
に書いてあります。version catalogsという機能で、依存を集約して記述できます。従来はbuildSrc
を使って共通化したりしていましたが、こちら方が書き方が統一されて良いと思います。中身はこんな感じです。
# This file was generated by the Gradle 'init' task.
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
[versions]
guava = "33.3.1-jre"
junit-jupiter = "5.11.1"
[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version = "2.0.21" }
これはあくまでカタログなので、実際に依存を宣言する際には implementation(libs.guava)
のように libs
から取ってきて使います。
3. Gradleプラグインの確認
肝心なbulid scriptを確認していきます。settings.gradle.kts
はGradle projectの定義やrepositoryの設定といった全体に共通な設定を書くファイルで、今回はほぼ何も書かれていないのでスルーします。
大事なのは app/build.gradle.kts
です。最初の段階ではこのように org.jetbrains.kotlin.jvm プラグインと application プラグインが適用されています。
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
alias(libs.plugins.kotlin.jvm)
// Apply the application plugin to add support for building a CLI application in Java.
application
}
まず org.jetbrains.kotlin.jvm プラグインは、Kotlinのソースをビルドするために必要なプラグインです。デフォルトでsrc/main/kotlin
または src/main/java
に入っているKotlinのコードをコンパイルしてくれます(参考)。
そしてapplication プラグインはJavaアプリケーションを実行するためのプラグインです(参考)。このプラグインを導入していると、Gradleに run
というタスクが生えるので、おもむろにそれを実行してみると、Hello World!
と出力されます。
❯ ./gradlew :app:run -q
Hello World!
App.kt
をentrypointとしてアプリケーションを実行する設定になっているため、main関数が実行されています。
build.gradle.kts
:
application {
// Define the main class for the application.
mainClass = "org.example.AppKt"
}
App.kt
:
/*
* This source file was generated by the Gradle 'init' task
*/
package org.example
class App {
val greeting: String
get() {
return "Hello World!"
}
}
fun main() {
println(App().greeting)
}
4. Spring Boot向けのGradleプラグインの導入
先に紹介した初期装備の application プラグインはそのまま使っても良いのですが、org.springframework.boot というSpring Bootアプリケーション向けの専用のプラグインがあるためこれで置き換えます。
version catalogに依存を追加し、ktsにpluginを追加します。このあたりからはdiffになるので簡単のためGitHubのcommit URLを記載します。04f7afa
すると bootRun
というタスクが生えるので、run
の代わりにこれを用います。他にも bootJar
というタスクも生えており、appをjarに固めて簡単にデプロイすることができるようになっています、便利。
プラグインの準備ができたので、アプリケーションの方にSpring Bootの依存(spring-boot-starter-web)を追加し、App.kt
をHello Worldの例からSpring Bootのアプリケーションにしていきます。8fdea82
これで準備が整いました。bootRunを実行するとSpring Boot appが起動します。
❯ ./gradlew :app:bootRun
Calculating task graph as no cached configuration is available for tasks: :app:bootRun
> Task :app:bootRun FAILED
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.2)
...
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Configuration class 'App' may not be final. Remove the final modifier to continue.
Offending resource: org.example.App
...
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:bootRun'.
> Process 'command '/Users/raahii/.sdkman/candidates/java/17.0.7-tem/bin/java'' finished with non-zero exit value 1
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 9s
3 actionable tasks: 1 executed, 2 up-to-date
Configuration cache entry stored.
と、思ったら失敗してしまいました。エラー原因を見ると、App classがfinalになっていて駄目だよと怒られています。 だめな理由はさておき、Kotlinのクラスはデフォルトでfinalになるためそれが問題のようです。
これを解決するために App class を open class App
に書き換えても良いですが、今後も開発する上でたくさんのクラスにopenをつけなければならず不便です。そこで org.jetbrains.kotlin.plugin.spring プラグインを導入します。これを使うことで @Component
等のSpringのアノテーションが付加されたクラスが自動的にopenになります (詳しくはこのブログが参考になりそう)。cd63c34
再度実行すると起動に成功します。めでたし。
5. エンドポイントを追加してHTTP requestを投げてみる
例えば App.kt にcontrollerを下記のように適当なコントローラーを足してHTTP requestを投げると、responseが返ってきます。
@RestController
class Controller {
@GetMapping("/hello")
fun hello(): String =
"Hello, world!"
}
❯ curl 'http://localhost:8080/hello'
Hello, world!⏎
さいごに
以上がミニマムなKotlin x Spring Bootのアプリケーション構成でした。 シンプルでありながらも
- org.jetbrains.kotlin.jvm でKotlin sourceをビルドする
- org.springframework.boot でbootRun/bootJar taskを手にする
- org.jetbrains.kotlin.plugin.spring でall openにする
の3つのプラグインについて触れました。
ソースコードは下記においています。