Tue, Jan 3, 2017

Goslings開発メモ - その1: Spring Boot編

Goslings開発メモ - その1: Spring Boot編

Goslings開発メモ - その0: 紹介と概要と設計編」の続き。

Spring Boot編。


Spring Bootとは

Spring BootはSpring FrameworkというJavaのWebアプリケーションフレームワークを簡単に利用するためのツールやライブラリ群。

これを使うと、Webアプリケーションコンテナ(e.g. Tomcat)なしで起動できるSpringアプリケーションを、自動コード生成も設定ファイル作成もせずに作ることができる。 必要な設定は自動で構成され、設定のカスタマイズもアノテーションでできる。

GAになったのが2014年4月なのでかなり新しいものだが、JavaのWebアプリケーションを作るためのものとしては今世界的に最も流行っているもの。

私が昔とあるWebアプリを作った時はSpring RooというRADツールが熱かったが、これはコード自動生成をして開発を助けてくれるもので、なんだか結局あまり流行らなかったようだ。

Goslingsには最新バージョンの1.4.3.RELEASEを使った。

Spring Bootことはじめ

包括的網羅的なドキュメントは「Spring Boot Reference Guide」だが、今回あまり深く学ぶ時間が取れなかったのでこれはちら見した程度。 それよりも、ユースケースごとのチュートリアルが60個以上も載っている「Getting Started Guides」を参考にした。

Goslingsサーバは基本REST APIサーバなので、上記チュートリアルの内「Building a RESTful Web Service」を見ながら以下を実施した。

1. プロジェクト作成

チュートリアルにはGradleプロジェクトのディレクトリ構成を手動で作るところから書いてあるけど、そこはIDEなどで楽できる。 私はEclipseを使っていて、いつのまにかGradleプラグインであるEclipse Buildship: Eclipse Plug-ins for GradleGradle IDE Packがインストールされていたので、これらを使った。

どちらのプラグインでもプロジェクトは作成できるが、Qiitaのこの記事にあるとおり、Gradle IDE Pack(に含まれるGradle (STS) Integration for Eclipse by Pivotal)で作った場合、Gradle Wrapperが生成されないなどの問題があるので、Buildshipの方で作成。 ただ、Gradle IDE Packの方がパッケージ・エクスプローラでの見え方がちょっとよかったので、こちらでプロジェクトをインポートしなおした。

gradle_import.png

(上がBuildshipのやつで、下がGradle IDE Packのやつ)


出来たプロジェクトは以下の感じ。

project_structure.png

2. Spring Boot Gradle plugin適用

Spring Boot Gradle pluginというものがあって、これをプロジェクトに適用すると以下の恩恵を受けられる。

  1. 依存ライブラリ管理機能

    Spring関係のライブラリについて適切なバージョンを設定してくれるので、Gradleビルド設定(i.e. build.gradle)のdependenciesに自分でバージョンを書かなくていい。

  2. 実行可能jar(war)のパッケージング機能

    ビルドされたjar(やwar)を、単独で実行可能になるようにマニフェストやライブラリを詰めて再パッケージングするbootRepackageというGradleタスクが追加される。

  3. プロジェクトから直接アプリを起動する機能

    jarなどのアーティファクトをビルドせずに、プロジェクトから直接アプリを起動できるbootRunというGradleタスクが追加される。


build.gradleに以下の様に書くとSpring Boot Gradle pluginを適用できる。

  • Gradle 2.1より古いバージョン

    buildscript {
      repositories {
        mavenCentral()
      }
      dependencies {
        classpath('org.springframework.boot:spring-boot-gradle-plugin:1.4.3.RELEASE')
      }
    }
    
    
    apply plugin: 'org.springframework.boot'
    

    (apply plugin: 'org.springframework.boot'の部分は、Spring Boot Gradle plugin 1.4.1.RELEASE以前はapply plugin: 'spring-boot'だった。)

  • Gradle 2.1以降

    plugins {
      id 'org.springframework.boot' version '1.4.3.RELEASE'
    }
    

3. 依存ライブラリ追加

Spring Bootは依存ライブラリの管理も簡易化してくれる。

spring-boot-starter-で始まるスターターと呼ばれるライブラリがいくつか提供されていて、作りたいアプリの種類や機能に応じたものをプロジェクトの依存ライブラリとして追加すると、推移的に諸々の必要なライブラリが追加されるようになっている。 例えば、Thymeleafをテンプレートエンジンに使ったWebアプリを作るならspring-boot-starter-thymeleafJPA (Hibernate)でデータベースアクセスしたい場合はspring-boot-starter-data-jpaを使う。

Webアプリを作るのに最も一般的なのはspring-boot-starter-webで、Goslingsにもこれを使った。 これを使うとSpring MVCでアプリを作ることになる。

また、Spring Boot Actuatorという、アプリをプロダクション環境で運用するための機能を有効にするため、spring-boot-starter-actuatorも使った。 これを有効にすると、Web APIでアプリの状態取得などができるようになる。 例えば、http://<サーバ>/healthにアクセスするとアプリの基本的なヘルス情報がJSONで取得できる。

これら二つのスターターを追加するには、build.gradledependenciesに以下の様に書くだけでいい。

dependencies {
  compile 'org.springframework.boot:spring-boot-starter-web'
  compile 'org.springframework.boot:spring-boot-starter-actuator'
}

前節に書いた通り、Spring Boot Gradle pluginのおかげでバージョンの指定は不要。

4. ディベロッパツール追加

Spring Bootのディベロッパツールを利用すると、以下の恩恵を受けられる。

  1. キャッシュの無効化

    Spring Bootがサポートしているライブラリ(e.g. Thymeleafといったテンプレートエンジン)にはキャッシュ機能を持つものがある。 こうした機能はプロダクション環境では性能改善に有効だが、開発時にはじゃまになる。 ディベロッパツールを使うとデフォルトで様々なキャッシュを無効にしてくれる。

  2. 自動再起動

    クラスパスに含まれるファイルに変更があるとアプリが自動で再起動される。

  3. ライブリロード

    ブラウザのアドオンをインストールすると、アプリに変更があったらブラウザが自動でリロードしてくれるようになる。


ディベロッパツールを追加するには、build.gradledependenciesに以下の様に書くだけでいい。

dependencies {
  compile 'org.springframework.boot:spring-boot-devtools'
}


ディベロッパツールは、アプリがプロダクション環境で起動されたと判定すると自動で無効になるので、アーティファクトに含まれても問題ない。 java -jarで起動されるか、または通常のものではないクラスローダが起動に使われると、プロダクション環境だと判定される。 build.gradleに以下の様に書けば、アーティファクトに含まれないようにもできる。

bootRepackage {
  excludeDevtools = true
}


ディベロッパツールへの推移的依存を避けるためのpropdeps-pluginというプラグインもあるが、Goslingsは他のアプリが依存するようなものではないので使わなかった。


自動再起動については、Eclipseの自動ビルドはデフォルトでgoslings/binにクラスファイルを吐くので、ビルドパスの構成で「デフォルト出力フォルダー」をgoslings/build/classes/mainに変えないと動かなかった。


ここまででベースとなるbuild.gradleができて、以下の様になった。

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVer}"
  }
}

repositories {
  mavenCentral()
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

archivesBaseName = 'goslings'
version = '0.0.1'

[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
sourceCompatibility = 1.8
targetCompatibility = 1.8

bootRepackage {
  excludeDevtools = true
}

dependencies {
  compile 'org.springframework.boot:spring-boot-starter-web'
  compile 'org.springframework.boot:spring-boot-starter-actuator'
  compile 'org.springframework.boot:spring-boot-devtools'
}

5. リソースクラス作成

ここからやっとコーディング。 まずはREST APIで取得するリソースを表現するクラスを作る。

Goslingsの場合、Gitリポジトリのオブジェクトやリファレンスなどがリソースになる。 例えばコミットオブジェクトを表すクラスは以下の様に書いた。

public final class Commit {

  private final String id;
  private final String[] parentIds;
  private final String treeId;

  // 以下、全フィールドをセットするコンストラクタとgetters。

}

(Commit.javaの完全なソースはこれ)

POJOとして書けばいいので、Lombok@Data@Valueを使うと楽だろうが、Goslingsには使わなかった。

6. コントローラ(REST APIコントローラ)作成

クライアントからのHTTPリクエストを処理するクラスはコントローラクラスと呼ばれる。 クライアントからのREST API呼び出しもHTTPリクエストなのでコントローラクラスで処理する。

REST API呼び出しを処理するコントローラクラスは、@RestControllerを付けて宣言して、@RequestMappingを付けたメソッド(リクエストハンドラ)にURL毎の処理を書いてやればいい。

以下の様な感じ。

@RestController
@RequestMapping(
  path="/v1",
  method=RequestMethod.GET
)
public final class RestApiV1Controller {

  // この辺でフィールド定義など

  @RequestMapping(path="{token}/objects/commits")
  public Commit[] getCommits(@PathVariable String token) {
    return objectDao.getCommits(token);
  }

  // 以下他のメソッド

}

(RestApiV1Controller.javaの完全なソースはこちら)

上のコードでは、http://<Goslingsサーバ>/v1/<トークン>/objects/commitsというURLをgetCommitsメソッドで処理するようにしている。 このAPIを呼び出すと、前節で作ったCommitクラスのインスタンスの配列がJSON形式で返ってくる。 (getCommitsの実装については次回書く。)


@RestControllerを付けると以下の二つのアノテーションを付けたのと同じことになる。

  • @Controller: 一般的なコントローラクラスに付けるアノテーション。
  • @ResponseBody: メソッドの戻り値をHTTPレスポンスボディにバインドすることを指示する。これを付けると、戻り値はJackson JSONでJSONに変換されてクライアントに返される。これを付けないと、戻り値はスタティックリソースへのパスなどとして扱われ、View(e.g. Thymeleaf)が処理した結果がクライアントに返される。(参考記事)


見ての通り、URLのパス中の値は@PathVariableを使って取得できる。

ここには書いてないけど、URLクエリパラメータは@RequestParamを使って取得できるし、HttpServletRequestもメソッドの引数として宣言しておけばSpringが渡してくれる。

7. メインクラス作成

最後に、アプリを起動するメインクラスを作る。

@SpringBootApplicationを付けたクラスにmainメソッドを以下の様に定義すればいいだけ。

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}

(Application.javaの完全なソースはこちら)


@SpringBootApplicationを付けると、以下の三つのアノテーションを付けたのと同じことになる。

  • @Configuration (@SpringBootConfiguration): Spring Bean定義を提供するクラスであることを示す。(意味不明。)
  • @EnableAutoConfiguration: Springの自動設定機能を有効にする。この機能は、ライブラリの依存関係から推定して必要な設定をしてくれるもの。例えばtomcat-embedded.jarに依存していたら、TomcatEmbeddedServletContainerFactoryをセットアップしてくれるなど。
  • @ComponentScan: このアノテーションを付けたクラスのパッケージ以下から、@Component@Service@Repository@Controller(など?)が付いたクラスが検索され、Spring Beanとして登録される。XMLのSpring Bean設定ファイルを書かなくてよい。前節で作ったリソースコントローラがこのアノテーションによって利用できるようになる。


@SpringBootApplication、というか@Configurationをつけたクラスはfinalにしてはいけない。 すると実行時にエラーになる。

8. ビルド、実行

以上でとりあえず動くものができた。

gradlew bootRunを実行するとディベロッパツール付きでアプリが動くし、gradlew buildを実行すればbuild/libs/goslings-0.0.1.jarというアーティファクトが生成され、java -jar build/libs/goslings-0.0.1.jarでアプリを起動できる。 (いずれもポートは8080)


今日はここまで。 次回はまたSpring Bootで、DIについて。