Spring Boot 超入門

Yusuke Shinyama, Nov. 2023
なぜ Spring Boot はクールなのか?
  1. ソフトウェア技術の歴史
  2. Spring Boot とは
  3. 実際に使ってみる
  4. 応用例
  5. Spring Boot を使う上での注意点

1. ソフトウェア技術の歴史

ソフトウェアというものは非常に複雑である。 先人達はこの複雑なシステムを分解 (separation of concerns) し、 人間が扱えるようにするために、さまざまな技法をあみだしてきた:

ソフトウェア技術の歴史とは、ほとんど「複雑さを分解する技術の歴史」といってもよい。

演習. 分解する技術

自分が思いつく「複雑さを分解する技術」を書き出してみよう (ソフトウェアでなくてもかまわない)。

2. Spring Boot とは

Spring Framework および Spring Boot (以下まとめて Spring と呼ぶ) は、 その名のとおり「フレームワーク」の一種である。 Spring は Java/Kotlin を使ったアプリケーション開発に使われている。

フレームワークとは何か?

伝統的な (フレームワークを使わない) プログラミングでは、 アプリケーションの全体的な流れはプログラマが決定する:

処理1 処理2
通常のプログラム

これに対して、フレームワークを使ったソフトウェアでは、 プログラマがアプリケーション全体の流れを直接指定しない。 かわりに、フレームワークがプログラマが書いた処理を必要に応じて呼びだす。 これを制御の反転 (Inversion of Control) という。あるいは「ハリウッドの原則 (the Hollywoord Principle)」とも呼ばれる。

ハリウッドの原則: "Don't call us. We call you." (我々を呼ぶな。必要なときは、我々が呼ぶ。)

Spring フレームワークにおける処理の流れは以下のようになる:

Spring Boot 処理1 処理2
Spring による呼び出し
演習. 制御の反転

自分が知っている「制御の反転」をおこなっているフレームワークを書き出してみよう。

ちなみに、フロントエンド開発でよく使われる React はライブラリであり、フレームワークではないと明言しているが、 これは React の機能には「制御の反転」がないことを意味している。

Spring を使うことのメリット

この調査によれば、 Spring を使う理由で一番多いのは 「データベースを使ったバックエンドが簡単に書けるから」 である。つまり、Spring は迅速な開発に向いている。

また、Spring はテスト駆動開発 (TDD) にも向いている。 各コードが全体的な処理の流れから分離されているので、 各部分を個別にテストしたり、全体をつなげてテストするといった作業が 非常に簡単にできるようになっている。 このための枠組みが Spring にはあらかじめ組み込まれている。

Springで重要な概念

Springでは非常に多くの機能が利用可能だが、 それらの根幹となっているのは 「依存注入 (Dependency Injection, DI)」 および 「アスペクト指向プログラミング (Aspect-Oriented Programming, AOP)」 という 2つのアイデアである (AOP についての説明は今回は省略する)。

Spring における DI を理解するには、以下の2つの機能を考えればよい:

3. 実際に使ってみる

準備

  1. spring initializr で以下を選択:
  2. ターミナルで以下を実行:
    $ java -version  (バージョン確認)
    ...
    $ unzip demo.zip  (zipを展開)
    Archive:  demo.zip
       creating: demo/
    ...
    $ cd demo
    $ ./gradlew bootrun   (ビルドおよび実行)
    ...
    .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
    ...
    
  3. http://localhost:8080/ にアクセス。
  4. "Whitelabel Error Page" なるものが表示されることを確認。

「あるある宣言」を書いてみる

まず、以下のファイルを新しく作成する。

src/main/kotlin/com/example/demo/MyComponent.kt:
package com.example.demo

import org.springframework.stereotype.Component

@Component
class MyComponent {
    init {
        println("*** this is my component ***")
    }
}

上の例における @Component は「あるある宣言」の一種である。 この後もう一度 ./gradlew bootrun を実行すると

*** this is my component ***
と表示される。 これは Spring が MyComponent クラスの「あるある宣言」を検知し、 そのインスタンスを自動的に作成したことを意味する。

Spring では 「あるある宣言」によって作成されるオブジェクトを "bean" と呼ぶが、Java の世界にはこれとは別の "Java Bean" という用語がある。 Spring における Bean と Java Bean は異なる概念である ことに注意。

では次に、新しいファイルを追加する:

src/main/kotlin/com/example/demo/MyController.kt:
package com.example.demo

import org.springframework.stereotype.Controller

@Controller
class MyController {

    init {
        println("+++ this is my controller +++")
    }
}

上の例における @Controller も「あるある宣言」の一種である。 これを実行すると

+++ this is my controller +++
と表示される。 このように「あるある宣言」には複数の種類が存在する。

「くれくれ宣言」を書いてみる

いま作成した MyController.kt を以下のように修正する:

src/main/kotlin/com/example/demo/MyController.kt:
package com.example.demo

import org.springframework.stereotype.Controller
import org.springframework.beans.factory.annotation.Autowired

@Controller
class MyController(@Autowired val component: MyComponent) {

    init {
        println("+++ this is my controller: mycomponent=$component +++")
    }
}

これを実行すると

+++ this is my controller: mycomponent=com.example.demo.MyComponent@72d0f2b4 +++
のように表示される。

この例では、MyController インスタンスを作成するにはまず MyComponent インスタンスが必要である。 ここでコンストラクタの引数に @Autowired と書かれているのが 「くれくれ宣言」である。 これは「MyController が MyComponent に依存している」ことを示す。 Spring はこのような依存 (Dependency) を検知すると、 そこで要求されている型のオブジェクト (MyComponent インスタンス) を自動的に探し出し (必要とあれば作成して)、引数として注入 (Injection) してくれる。 これが Spring における依存注入 (Dependency Injection, DI) の要となる部分である。

MyComponent MyController 依存 注入
Spring における依存注入 (DI)

Spring における DI は慣れると便利だが、一番とっつきにくい部分でもある。 通常のプログラムであれば、

mycomponent = MyComponent()
mycontroller = MyController(mycomponent)
のように書くところを、 Spring ではこの部分すら自分では書かず、 フレームワークに処理させているのである。

上の例では、 実は @Autowired が省略されていても、 文脈から自動的に「くれくれ宣言」とみなされる。

4. 応用例

Spring では、 以上のような「くれくれ宣言」「あるある宣言」を応用することにより、 さまざまな機能を簡単に組み合わせられるようになっている。

あるある宣言の応用例

たとえば「あるある宣言」の応用例として、 簡単な web サービス (API エンドポイント) を作成してみる。 以下のような新しいファイル HelloController.kt を作成すればよい。

src/main/kotlin/com/example/demo/HelloController.kt:
package com.example.demo

import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody

@Controller
class HelloController {

    @GetMapping("/hello")
    @ResponseBody
    fun hello(): String {
        return "Hello, World!"
    }

}

これを実行し、 http://localhost:8080/hello にアクセスすると、ブラウザに

Hello, World!
と表示されるのがわかる。

上の @GetMapping は メソッドに対する「あるある宣言」で、 「GETリクエストに応答するメソッドがここにあるよ」ということを示している。 これを検知すると、Spring Web は hello() メソッドの周囲に HTTP リクエスト・レスポンスを処理する機構をくっつけて 全体を Webサーバとして動作させる。 この仕組みを使って、実質わずか 5行で新しい API を作成することができた。

Spring Web hello() リクエスト レスポンス
Spring Web による APIエンドポイントの作成

くれくれ宣言の応用例

次に「くれくれ宣言」の応用例として MyComponent にSQLデータベースを接続してみる。 まず、トップレベルにある build.gradle.kts ファイルに 以下の2行を追加する:

build.gradle.kts:
dependencies {
        ...
        implementation("org.springframework.boot:spring-boot-starter-jdbc")
        runtimeOnly("com.h2database:h2")
}

次に、さきほどの MyComponent.kt に新しい「くれくれ宣言」を追加し、 さらに SQL文を実行する部分を付け足す:

src/main/kotlin/com/example/demo/MyComponent.kt:
package com.example.demo

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.springframework.jdbc.core.JdbcTemplate

@Component
class MyComponent(@Autowired val jdbcTemplate: JdbcTemplate) {
    init {
        println("*** this is my component ***")
        jdbcTemplate.execute("CREATE TABLE USERS (NAME TEXT)")
        jdbcTemplate.execute("INSERT INTO USERS VALUES ('JOHN')")
        val users = jdbcTemplate.queryForList("SELECT * FROM USERS")
        println(users)
    }

}

これを実行すると、

[{NAME=JOHN}]
のような行が現れているのがわかる。 たったこれだけの追加で、すでにデータベースとの連携が実現できた。 この例のくれくれ宣言では 「JdbcTemplate (インターフェイス) として使えるものがほしい」 と書かれている。 すると Spring は JdbcTemplate インターフェイスとして たまたま利用可能だった H2 データベースを DI する。 H2 はオンメモリの簡易な組み込みデータベースだが、 設定を変えることで PostgreSQL などのデータベースも同様に使うことができる。

Spring Boot H2 (JdbcTemplate) MyComponent 依存 注入
H2 データベースを MyComponent に DI する

5. Spring Boot を使う上での注意点

Spring Boot は便利な反面、使い方を学ぶのに時間がかかるというデメリットがある。

Spring の動作原理

  1. アプリが起動する。
  2. Spring が、アプリ中で使われているすべての クラス、メソッド、変数をスキャンし、各部分の 「くれくれ宣言」「あるある宣言」を洗い出す。
  3. 一致する「くれくれ宣言」「あるある宣言」に対して、 それらのオブジェクトが自動的に作成され、DI される。
  4. 実際の処理が始まる。

Spring を学ぶ上で気をつけること


Yusuke Shinyama