1クール続けるブログ

とりあえず1クール続けるソフトウェアエンジニアの備忘録

黒べこ本(kotlin)でSpring Boot入門メモ③ (DI・バリデーション)

DIとは

DIはDependency Injectionのことで日本語では依存性の注入と言われる。Springのドキュメントでは基本的には、類似の意味を持つIoC(Inversion of Control)という言葉が使われている。DIは、オブジェクトが依存関係を定義する処理となる。以下に挙げる方法でのみ、オブジェクトが依存する他のオブジェクトの定義を行う。

  • コンストラクタ引数 ex) val regex = Regex("Kotlin")
  • ファクトリメソッドの引数 ex) val mutableList = mutableListOf(1, 2, 3)
  • 返り値のオブジェクトインスタンスに設定されるプロパティ ex) person.age = 25

DIコンテナ

DIコンテナは、Beanを作成するときにそれらの依存関係を注入する。BeanとはSpring IoC コンテナによって管理されるオブジェクトのことです。コンテナによって管理されるオブジェクトとしては以下のものが例としてあげられる。

  • ViewResolverのBean (Controllerの戻り値の文字列を解決してViewを戻す処理を行う)
  • ValidatorのBean
  • DataSourceのBean
  • トランザクションマネージャのBean
  • 型変換のBean
  • Controller・Service・Repositoryなどの業務ロジックに関係したBean

DIコンテナは、ConfigurationにしたがってBeanを作成し、IDを割り振って管理する。インタフェースorg.springframework.context.ApplicationContextがSpring IoCコンテナを表している。

Beanの定義

Configurationの設定の仕方は以下の3種類ある。

<bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
</bean>
  • JavaConfig
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

インスタンスをBeanとしたクラスに@Componentもしくは@Componentをベースにしたアノテーションを付与する(@Controller / @Service / @Repository / @Configuration)
Java.configに@ComponentScan(basePackages = {"hoge.service.impl"})を付与する。ここで指定したベースパッケージ配下のアノテーションをチェックする。
@Autowiredをインスタンス変数に付与することでコンテナ内から、インジェクションする対象となる。

@Controller
@RequestMapping("houses")
class HouseController {

    @Autowired
    private lateinit var shs: HouseFactoryImpl

@Autowiredの使い方

@Autowiredを使用したDependency Injectionの種類は以下の3種類有ります。記述が簡易なフィールド・インジェクションが使用されることが多いと思われます。ただフィールド・インジェクションは、Spring FrameworkのDIコンテナに依存してしまう点と設計の妥当性(依存関係が多くなり過ぎていないか、循環依存になっていないか)が分かりづらいということが有り、必須の依存関係でコンストラクタ・インジェクション、オプションの依存関係をセッター・インジェクションを使用する方がアプリが大規模になる場合に有効であると言えます。

  • コンストラクタ・インジェクション
class Foo @Autowired constructor(dependency: MyDependency, service: MyService) {
    //...
}

ただし、Spring4.3以降でコンストラクタが単一の場合は@Autowiredが省略できるため、以下のようになります

class Foo(dependency: MyDependency, service: MyService) {
    //...
}
  • フィールド・インジェクション
    lateinitはプロパティを遅れて初期化することをコンパイラに知らせるものです。 バッキングフィールドを持つプロパティは初期化が必要となるはずですが、lateinitを付けることでプロパティ宣言時の初期化を避けることが出来ます。
class Foo {
   @Autowired
   private lateinit var dependency: MyDependency
   @Autowired
   private lateinit var service: MyService
}
  • セッターインジェクション
    kotlinでは無し? → 調査中

バリデーション

Formのクラスを作成し、そのクラスのフィールドにアノテーションを付与することで、単項目チェックを行うことができる。Formのクラスは、HTMLなどから受け取る送信formに対応するもの。アノテーションは、Bean Validationの実装であるhibernate validatorを利用する。Bean Validationは値検証のためのJavaの仕様の一つで、hibernate validatorはそのリファレンス実装となっている。 相関チェックは、Validatorインターフェースを実装したチェック用のクラスを作成し、Controllerにインジェクションします。

単項目チェック

class TaskCreateForm {

    @NotBlank
    @Size(max = 20)
    var content: String? = null
}

仕様頻度が高そうなアノテーションは以下

  • @Length(min=, max=) //文字列長の検査
  • @Max(value=) //数値の最大値の検査
  • @Min(value=) //数値の最小値の検査
  • @NotNull //値がNullでないか
  • @NotEmpty //文字列がNullまたは空文字で無いか
  • @Pattern(regex="regexp", flag=) //正規表現に一致するか否か

以下のようにメソッドの引数としてとったFormにアノテーション@Validatedをつけ、そのすぐ次の引数にBindingResultを取るようにすると、バリデーションが施され、その結果がBindingResultオブジェクトとして表現される。BindingResultオブジェクトのhaserror()メソッドを使用することで検査の結果を得ることが出来ます。

class TaskController(private val taskRepository: JdbcTaskRepository) {

    @PostMapping("")
    fun create(@Validated form: TaskCreateForm,
               bindingResult: BindingResult): String {
        if(bindingResult.hasErrors())
            return "tasks/new"

        val content = requireNotNull(form.content)
        taskRepository.create(content)
        return "redirect:/tasks"
    }

}

次は例外処理周りか永続化について書きます。


参考記事

SpringでField InjectionよりConstructor Injectionが推奨される理由 - abcdefg.....

[随時更新]SpringBoot(with Thymeleaf)チートシート - Qiita

Validator (Java Platform SE 8)

黒べこ本(kotlin)でSpring Boot入門メモ② (Controller・ざっくりThymeleaf)

黒べこ本では、"Hello World"でREST APIのController・"ToDoアプリ"でMVCのControllerを書くことになっています。 Controllerに着目して書いていきます。また、Viewを返す後者で使用されるテンプレートエンジンであるThymeleafも少しだけ知っておこうと思います。

Spring Boot での Controller

そもそもMVCってなんだっけ

ウェブアプリの設計においてポピュラーな選択肢であるModel View Controller (MVC) のこと。アプリケーションロジックを3つに分割することで再利用性や柔軟性を高めている。

例えば、簡単なショッピングリストアプリを作成すると考えてみる。今週買う必要のある商品の名前・数量・価格のみを表示してくれる。

f:id:jrywm121:20180313004328p:plain

Model

Modelはリストの商品データと既に存在するリストを実装する。

View

Viewはリストをユーザに対してどのように見せるかを定義する。モデルから表示するデータを受け取る。

Controller

アプリからの入力を受け取って、それに応じてModelやViewを更新するロジック。 例えば、ショッピングリストアプリには入力フォームや追加・削除ボタンが有ります。これらのアクションにより、Modelのアップデートが必要となります。入力情報は一度、Controllerに送られます。その後、Modelの操作が行われ、更新されたデータがViewへ送られます。 また、Viewを更新し別のデータ形式(アルファベットを昇順→降順 / 価格順に並び替える)にすることもあるでしょう。この場合は、modelへのupdateを必要とせず、直接Controllerがハンドルします。

※ 内容書いてから気づきましたが、MDNを参照したので若干Webフロントエンドよりの解釈となっています。

実際のJavaアプリーションだと以下のような構成になると思われます。 これから始めるSpringのwebアプリケーションの7スライド目を引用 f:id:jrywm121:20180313012920j:plain

Controller関係のアノテーション

Javaが標準で提供しているWEBアプリ(ex: Servlet, JDBC)はプログラムや設定ファイルの記述が面倒。確かに新人研修の時にやったJava EEServletJSPxmlに記述したり、冗長な記述でDispatchしていたような記憶がかすかに有ります。

SpringはJava標準APIをラップして簡単にしてくれる。Configurationの方法はアノテーションXMLJava Configが有りますが、Controller / Service / Dao はアノテーションという手段を取るのが見通し良くデメリットも小さいらしい。 @Controllerコンポーネントと@RestControllerコンポーネントでは、アノテーションを使用してリクエスマッピング、リクエスト入力、例外処理などを表現できる(引用元)。

黒べこ本ではToDoリストのアプリで以下のようにControllerを記述しました(一部分のみです)。

@Controller
@RequestMapping("tasks")
class TaskController(private val taskRepository: InMemoryTaskRepository) {

    @PostMapping("")
    fun create(@Validated form: TaskCreateForm,
               bindingResult: BindingResult): String {
        if(bindingResult.hasErrors())
            return "tasks/new"

        val content = requireNotNull(form.content)
        taskRepository.create(content)
        return "redirect:/tasks"
    }

    @GetMapping("{id}/edit")
    fun edit(@PathVariable("id") id: Long,
             form: TaskUpdateForm): String {
        val task = taskRepository.findById(id) ?: throw NotFoundException()
        form.content = task.content
        form.done = task.done
        return "tasks/edit"
    }
@Controller

このアノテーションは、@Componentと同じようにコンテナで管理するように指定するもの。Beanとして扱われ、@AutowiredアノテーションがControllerの使用者側に付けられることによってInjectionされる。

@RequestMapping

RequestをController内のメソッドにマッピングするのに使用されます。URL、HTTPメソッド、要求パラメータ、ヘッダー、およびメディアタイプによって一致するさまざまな属性があります。これをクラスレベルで使用して共有マッピングを表現したり、メソッドレベルで特定のエンドポイントマッピングに絞り込むことができます。

@GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping

@RequestMappingとHTTPメソッドの組み合わせ。()内で指定するURI正規表現でも書ける。引数を取らない場合は()内にパスの文字列のみを記述すれば良いが、引数を2つ以上取る場合には、パラメータとなる変数名を記述しなくてはいけない。
path: エンドポイント / consumes: Content-Type / produces: Accept / headers: request header / params: request parameter

@PostMapping(path = "/pets", consumes = "application/json", produces = "application/json;charset=UTF-8", headers = "myHeader=myValue",  params = "myParam=myValue")
@PathVariable

pathの中で{ }で定義した変数には、このアノテーションを使用することでアクセス出来ます。URI変数はString変数でない場合には、自動的に適切な型に変換されるか、 もし出来ない場合にはTypeMismatchExceptionが発生します。int, long, Dateはデフォルトでサポートされています。

@Validated

このアノテーションが付与された引数は、標準のBean検査が行われ、もし検査にひっかかってしまった場合にはデフォルトでは400(Bad Request)を返すようにしている。BindingResult型の引数を取ることで、ローカルで処理をすることも可能。

redirect:(Path)

指定されたパスにリダイレクトする。

ハンドルに関するアノテーション
  • @RequestParam : リクエストパラメータの値を取る
  • @RequestHeader : リクエストヘッダーの値を取る
  • @CookieValue : Cookieの値を取る
  • @ModelAttribute : モデル(インスタンス)の値を取る。既にModelメソッドで追加されているモデルから もしくは @SessionAttributes経由のHTTPセッションから もしくは デフォルトのコンストラクタの呼び出しから 等々
  • @SessionAttribute : 既存のセッション属性にアクセスする
  • @RequestAttribute : 作成された既存のリクエスト属性にアクセス
  • @RequestBody : リクエストボディにアクセス
  • @ResponseBody : 関数の戻り値をシリアライズします。@ResponseBody + @Controller = @RestController

戻り値がString型だけど・・・

ビューの論理名からビューの物理名を解決する。 "tasks/new" → "/resources/templates/tasks/new.html" Thymeleaf用のViewResolverクラスがあり、それが名前解決をしてくれる。

Viewに相当するThymeleaf

Thymeleafとは

ThymeleafはJavaのテンプレートエンジンライブラリ。XML/XHTML/HTML5で書かれたテンプレートを変換して、アプリケーションのデータやテキストを表示することができる。JSPの代替技術として近年注目されている

Thymeleafの基本

黒べこ本の中で書くコードは以下となります。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>タスク一覧</title>
    <link rel="stylesheet" href="webjars/bootstrap/4.0.0-2/css/bootstrap.min.css">
</head>
<body>
<a th:href="@{tasks/new}">作成</a>
<p th:if="${tasks.isEmpty()}">タスクがありません</p>
<ul th:unless="${tasks.isEmpty()}">
    <li th:each="task: ${tasks}">
        <a th:href="@{tasks/{id}/edit(id=${task.id})}">
            <span th:unless="${task.done}" th:text="${task.content}"></span>
            <s th:if="${task.done}" th:text="${task.content}"></s>
        </a>
    </li>
</ul>
</body>
</html>
@{...} : リンク式

/で始めると、レスポンスされるhtmlにはコンテキストパスが補完されます。コンテキストパスは絶対パスで記述する際に必要となるもので、Applicationサーバの資源の位置を表す。相対パスで指定する場合は環境によって上手く通らないケースが出てくるため危険。

${...} : 変数式

コンテキストのModelにaddされているインスタンスを指定して展開することが出来ます。事前にController側で以下のコードのように、してあげる必要があります。

    @GetMapping("")
    fun index(model: Model): String {
        val tasks = taskRepository.findAll()
        // 直下の部分。第一引数に取っている名前でView側は参照する。
        model.addAttribute("tasks", tasks)
        return "tasks/index"
    }
th:if=${condition} th:unless=${condition}

conditionが正であれば、if記述のブロックはActiveとなる。 conditionが負であれば、unless記述のブロックはActiveとなる。

th:text

サーバーで実行した時は、th:text属性を指定したタグに挟まれた部分が置き換えられます。htmlで直書きしている内容は上書かれるので、基本的にはモックとして扱われる際のデフォルト値

@{path1/{変数}/path2(変数=代入値}

パスに変数を埋め込む

黒べこ本では先ほど挙げたものの他にこのようなコードも書きます

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>タスク作成</title>
</head>
<body>
<form th:method="post" th:action="@{./}" th:object="${taskCreateForm}">
    <div>
        <label>
            内容:<input type="text" th:field="*{content}">
        </label>
        <p th:if="${#fields.hasErrors('content')}" th:errors="*{content}">エラーメッセージ</p>
    </div>
    <div>
        <input type="submit" value="作成" />
    </div>
</form>
</body>
</html>
*{...} : 選択変数式

上記のコードでは、*{content}は本来${taskCreateForm.content}と記述しなくてはいけないのですが、なんども同じような記述が生まれてしまう状況が起きやすいです。そのため簡略化して書けるよう選択変数式があります。親要素でth:object="${taskCreateForm}"と宣言されていれば、*{プロパティ名}で子要素はコンテキストにアクセスすることが可能です。

#fields

#を先頭に付けたものはユーティリティメソッドと呼ばれるもので他にはdateやcalender、sessionに保管されている値を参照するものなどがある。この#fieldsは、Validate後の各プロパティで発生したエラーメッセージを参照している。${#fields.errors('【プロパティ名】')}でアクセスできる。結果はリストになっているので、ループで回せば各エラーメッセージを取得できる。

次回はDIとValidateについてメモしていこうと思います。


参考にさせていただきましたサイト様

MVC architecture - App Center | MDN

これから始めるSpringのwebアプリケーション

Springを何となく使ってる人が抑えるべきポイント

Web on Servlet Stack

Tutorial: Using Thymeleaf (ja)

必要最小限のサンプルでThymeleafを完全マスター - Java EE 事始め!

Spring Boot で Thymeleaf 使い方メモ - Qiita

黒べこ本(kotlin)でSpring Boot入門メモ① (Spring Boot概要・ざっくりGradle)

春が来たのでSpring Bootに入門しました。

今まではC#を書いていたのですが、次の仕事からJavaを使うことになりそうだったので、アクロ本結城浩さんのJava言語で学ぶデザインパターン入門で勉強しています。デザインパターン本の方は、言語仕様が古かったりするんですが、Java8以降ならこう書けるなというものを見つけていく作業が楽しかったりします。

その流れでSpringも勉強しようと思ったのですが、どうせならJavaと一緒にkotlinも勉強したいと思っていたので、いっそのことkotlinでSpringに入門するのもありかもと。ちょうどそのころに、書店でKotlin Webアプリケーション 新しいサーバサイドプログラミングを見つけて購入し、kotlinでSpring入門することとなりました。 ちなみにサーバサイドkotlinに興味を持ったのは、engineer meeting podcastのvol.104で既にkotlinで書かれたサービスがいくつか動いていることを知ったからです。

Spring Bootとは

Spring Bootを理解するためにSpringは避けて通れない

SpringSpring Frameworkをベースとしたプロジェクトの集合体

From configuration to security, web apps to big data – whatever the infrastructure needs of your application may be, there is a Spring Project to help you build it. Start small and use just what you need – Spring is modular by design.(公式サイトより)

Configurationからセキュリティ、ウェブアプリ、ビッグデータまでアプリケーションのinfrastructureに必要なものはSpringのプロジェクトで用意することができる。Springはモジュールごとに分かれているため、使いたい機能だけを選んで小さく始めることができる。

SpringのベースとなっているSpring Framework

Spring FrameworkはDIコンテナを主に特徴にあげられる様々なレイヤに対するフレームワークコンポーネントの集まり

Core support for dependency injection, transaction management, web applications, data access, messaging, testing and more.(公式サイトより)

Spring Bootの立ち位置

Spring Bootは、Spring Frameworkの持つ設定する項目が多すぎて本来の開発に集中できないという問題点を解決するため、Spring FrameworkにCoCを導入したもの。CoC = Convention over Configurationのこと。「設定より規約」ということで規約どおりなら無駄な設定ファイルを省略できるようにすることで無駄な設定を少なくする。 では、Springにおける設定ファイルとはなにか → Bean(コンテナ内のインスタンス)の設定。

Beanは大きく分けて2種類有る。

Spring Bootでは前者があらかじめ定義済みとなっている。 まとめると以下のような感じ。

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run". (公式サイトより)

"動かせる"ProductionレベルのSpringをベースとしたアプリケーションを簡単に作ることが出来る。

Spring Initializrとは

Spring Initializrは、Spring Bootの雛形を作ってくれるそう。ビルドツールや言語、バージョンによって変えなければいけない設定などで苦労することがないため、よりスピーディーに開発出来る。
ページ下部の"switch to the full version"を選択することで、パッケージングやJavaのバージョンの選択を行うことが出来る。また、依存ライブラリを一覧から選択することも可能となる。依存ライブラリの中で、赤文字になっているものは、選択しているSpring Bootのバージョンをサポートするバージョンのライブラリが無いこと示している。

入力項目

  • Group : パッケージ名(通例ドメイン名を逆にしたものから始まる)
  • Artifact : プロジェクト名
  • Dependencies : 開発に必要なコンポーネントを選ぶ

Gradleとは

Gradleはビルドツール

ビルドツールは以下のことをやってくれる。Java製アプリを Eclipse から実行したことしかない新人に「ビルドツールとは?」を説明してみる…そして CI へより引用

  • ビルドを構成するタスクを定義し,それを実行する.例えば以下の様なタスクを行う.
    - ソースファイルのコンパイル
    - テストや静的解析の実行,およびその結果のレポート出力
    - リリース用アプリケーションイメージの構築
    
  • ビルドを構成するタスク間の依存関係を管理し,そのタスクのインプットとなる上流タスクの結果やソースコードなどの変更有無からタスク- の実行有無を判断する.
  • ライブラリの依存関係を管理する.(これを行うことに特化した「パッケージマネージャー」というカテゴリに属するツールもあります.有名なのは JavaScript の npm や,RubyRubyGems など.)
  • タスクのインプットとなるファイルを監視し,変更があった場合に自動的にタスクを実行する. (Grunt における watch や,Gradle における continuous build など)

Angular開発におけるwebpack + npm的存在ではないでしょうか。

Gradleの特徴とは

GroovyベースのDSLを用いたビルドスクリプトJavaのビルドツールとしては他にAntやMavenなどが有名ですが、それらはxmlベースのため大きく違う点と言えます。 Groovyは、Javaプラットフォーム上で動作する動的プログラミング言語です。DSLDomain Specific Language(ドメイン固有言語)の略で、メタプログラミングで使われます。 メタプログラミングはロジックを直接コーディングするのではなく,あるパターンをもったロジックを生成する高位ロジックを定義する方法(Wikipediaより)のようです。

Gradle has been counted in the top 20 open-source projects and is trusted by millions of developers to build software for billions of people

LinkedInやNETFLIXなどの大きな企業からも信頼を得ているようです。

Gradleの設定ファイルの構成

Gradleの設定ファイルはbuild.gradleです。 黒べこ本の手順で作成されたbuild.Gradleは以下です。コメントでそれぞれに注釈を付けました。

// build.gradle自体が依存するライブラリの設定を行なっています
buildscript {
    // ext ブロックは変数を定義するためのブロック
    ext {
        kotlinVersion = '1.2.20'
        springBootVersion = '2.0.0.RELEASE'
    }
    // repositories ブロックは依存性を解決する際に、Mavenリポジトリとしてアクセスする先を指定する
    repositories {
        mavenCentral()
    }
    // dependencies ブロックは依存ライブラリを指定するブロック
    dependencies {
        // groupId : artifactId : version
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    }
}

// ビルド設定で利用するプラグインの宣言
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

// プロジェクト自体のバージョンや依存するJavaのバージョン
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

// Production Codeのコンパイル
compileKotlin {
    // kotlinOptionsで追加のコンパイルオプションを指定する
    kotlinOptions {
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "1.8"
    }
}
// Test codeのコンパイル
compileTestKotlin {
    kotlinOptions {
        // freeCompilerArgs:追加のコンパイル引数リスト
        // JSR-305 annotation によるNullチェックをErrorとして出力
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "1.8"
    }
}

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('com.fasterxml.jackson.module:jackson-module-kotlin')
    compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    compile("org.jetbrains.kotlin:kotlin-reflect")
    compile('org.springframework.boot:spring-boot-starter-jdbc')
    compile('org.webjars:bootstrap:4.0.0-2')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.assertj:assertj-core:3.8.0')
    // runtime:プロダクトのクラスを実行するときに必要になる依存関係
    runtime('com.h2database:h2')
}

次回はSpringの簡単な動きとThymeleafについて整理したいと思います。


参考にさせていただきましたサイト様

Spring Bootの本当の理解ポイント #jjug

第1回 DSLとは?:今そこにある“DSL”|gihyo.jp … 技術評論社

これから始めるSpringのwebアプリケーション

Gradle初心者によるGradle事始め - Qiita

Gradle を始めてみよう - Qiita

Kotlin 1.1.50 is out | Kotlin Blog