raahii.meのブログのロゴ画像

ウェブログ

GradeでKotlin & Spring Bootのミニマムなプロジェクトをつくる

はじめに

ごくごく当たり前の内容ではありますが、個人的にGradleは難解だなと思っており… KotlinとSpring Bootを使ってweb appを作るときのミニマムな構成についてまとめます。 Gradleの設定に主眼をおいている音で、Spring Boot自体の説明やapp自体の実装の話はないです。

Spring Boot公式のチュートリアル との違いとしては、build scriptに .kts を使っているのとversion catalog を利用しているところですかね。まぁ今どき新規で mavenbuild.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.ktorg.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のアプリケーション構成でした。 シンプルでありながらも

の3つのプラグインについて触れました。

ソースコードは下記においています。

https://github.com/raahii/kotlin-spring-boot-minimum