ソフトウェアというものは非常に複雑である。 先人達はこの複雑なシステムを分解 (separation of concerns) し、 人間が扱えるようにするために、さまざまな技法をあみだしてきた:
ソフトウェア技術の歴史とは、ほとんど「複雑さを分解する技術の歴史」といってもよい。
自分が思いつく「複雑さを分解する技術」を書き出してみよう (ソフトウェアでなくてもかまわない)。
Spring Framework および Spring Boot (以下まとめて Spring と呼ぶ) は、 その名のとおり「フレームワーク」の一種である。 Spring は Java/Kotlin を使ったアプリケーション開発に使われている。
伝統的な (フレームワークを使わない) プログラミングでは、 アプリケーションの全体的な流れはプログラマが決定する:
これに対して、フレームワークを使ったソフトウェアでは、 プログラマがアプリケーション全体の流れを直接指定しない。 かわりに、フレームワークがプログラマが書いた処理を必要に応じて呼びだす。 これを制御の反転 (Inversion of Control) という。あるいは「ハリウッドの原則 (the Hollywoord Principle)」とも呼ばれる。
Spring フレームワークにおける処理の流れは以下のようになる:
自分が知っている「制御の反転」をおこなっているフレームワークを書き出してみよう。
ちなみに、フロントエンド開発でよく使われる React はライブラリであり、フレームワークではないと明言しているが、 これは React の機能には「制御の反転」がないことを意味している。
この調査によれば、 Spring を使う理由で一番多いのは 「データベースを使ったバックエンドが簡単に書けるから」 である。つまり、Spring は迅速な開発に向いている。
また、Spring はテスト駆動開発 (TDD) にも向いている。 各コードが全体的な処理の流れから分離されているので、 各部分を個別にテストしたり、全体をつなげてテストするといった作業が 非常に簡単にできるようになっている。 このための枠組みが Spring にはあらかじめ組み込まれている。
Springでは非常に多くの機能が利用可能だが、 それらの根幹となっているのは 「依存注入 (Dependency Injection, DI)」 および 「アスペクト指向プログラミング (Aspect-Oriented Programming, AOP)」 という 2つのアイデアである (AOP についての説明は今回は省略する)。
Spring における DI を理解するには、以下の2つの機能を考えればよい:
$ java -version (バージョン確認) ... $ unzip demo.zip (zipを展開) Archive: demo.zip creating: demo/ ... $ cd demo $ ./gradlew bootrun (ビルドおよび実行) ... . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ ...
http://localhost:8080/
にアクセス。
まず、以下のファイルを新しく作成する。
package com.example.demo import org.springframework.stereotype.Component @Component class MyComponent { init { println("*** this is my component ***") } }
上の例における @Component
は「あるある宣言」の一種である。
この後もう一度 ./gradlew bootrun
を実行すると
と表示される。 これは Spring が*** this is my component ***
MyComponent
クラスの「あるある宣言」を検知し、
そのインスタンスを自動的に作成したことを意味する。
では次に、新しいファイルを追加する:
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 を以下のように修正する:
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) の要となる部分である。
Spring における DI は慣れると便利だが、一番とっつきにくい部分でもある。 通常のプログラムであれば、
のように書くところを、 Spring ではこの部分すら自分では書かず、 フレームワークに処理させているのである。mycomponent = MyComponent() mycontroller = MyController(mycomponent)
@Autowired
が省略されていても、
文脈から自動的に「くれくれ宣言」とみなされる。
Spring では、 以上のような「くれくれ宣言」「あるある宣言」を応用することにより、 さまざまな機能を簡単に組み合わせられるようになっている。
たとえば「あるある宣言」の応用例として、 簡単な web サービス (API エンドポイント) を作成してみる。 以下のような新しいファイル 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 を作成することができた。
次に「くれくれ宣言」の応用例として
MyComponent
にSQLデータベースを接続してみる。
まず、トップレベルにある build.gradle.kts ファイルに
以下の2行を追加する:
dependencies { ... implementation("org.springframework.boot:spring-boot-starter-jdbc") runtimeOnly("com.h2database:h2") }
次に、さきほどの MyComponent.kt に新しい「くれくれ宣言」を追加し、 さらに SQL文を実行する部分を付け足す:
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 は便利な反面、使い方を学ぶのに時間がかかるというデメリットがある。
@なんとか
) があり、
どのような場合にどれを使うのか覚えにくい。
@Required
, @PathVariable
, @PropertySource
, ...
@Bean
, @ExceptionHandler
, @Scheduled
, ...
@ComponentScan
, @SpringBootTest
, ...
@
がついているからといって、
必ず「くれくれ宣言」「あるある宣言」のどちらかであるとは限らない。