Edited at

SpringBootに入門する為の助走本(随時更新)


■SpringBootに入門する為の助走本


□ おしながき


  • 環境構築編

  • 取り敢えずStarterProjectを使ってみる編

  • 簡単なWebAPIを作ってみよう編

  • 逆にWebAPIを呼び出してみよう編

  • Thymeleafで画面を作ってみよう編

  • スケジュール機能を使ってみよう編

  • ちょっとしたTips集

※肥大化してきたら整理して大見出し毎に別ページに分冊化してリンク張る形に変更・・・するつもりだったけど、どうしようか、どうしたほうがいいかな?


□ まえがき

興味のない人は 「おしながき」 に書いた見出しまで読み飛ばしちゃっておk!!

きっかけ:

新しいJavaの開発案件でフレームワーク選定してて

(*'▽') SpringBootやろうぜ!!

って事になり、非機能要件について色々と調査・勉強してまして。

調べてプロジェクトで使ってみた感じ

(*'▽') こいつぁ便利で面白いぞ!!

って感じで盛り上がりました。

で、折角なので(プロジェクトでのドキュメンテーションも兼ねて)公益性のためにQiitaに纏めて公開しよう、という話になった次第。

これを書いている人について:


  • Javaの経験は3~4年程度。
    (実務経験Java6~7、趣味でJava8を使っている。もう9とか10とか出ていて割と焦っている)

  • 以前はJavaEE開発をやっていた。
    (JSF:primefaces, JPA:hibernate を一応触った事がある)

  • もともとはC#屋さんで、WebよりWindowsがメインだった人。

  • 本当はJavaよりKotlinに興味ニキだけど、今回は開発の都合でJava使うマン。

  • SpringFramewrokの使用経験はなく、今回初めてSpringBootを使う。

方向性:

上記と似たような経歴の人や、Spring触った事ないけどこれからSpringBootを始めようとしてる人の参考になればコレ幸い。

入門系のページは既に沢山あって情報が充実してるので、それらへの「助走本」を目指す感じ。

以前やってた JavaEE開発(JSF/JPA)に絡めて書いていこうと思うので、そっち方面の経験者には親和性が高くなる、、、かもしれません。


□ 前提(各種バージョン)

ちなみにこの記事でどこまで書くか決めてないけど、今やってる開発は SpringBoot + Thymeleaf + PostgreSQL という構成でやってます。


> 初稿時点での環境情報

使用する各バージョンは以下の通り。

※自宅環境と開発環境でちょっと違うけど、記事の内容的には概ね問題ないと思う。

注意しないといけないのは、JavaとSTSでbitバージョン(x64/x86)を合わせる必要があるという事くらい。

環境
バージョン

開発言語
Java8以上

開発環境
STS:3.9.4 RELEASE e4.7.3a ※

SpringBoot
2.0.2

DB
PostgreSQL10 + pgAdmin4

ちなみに、個々の細かい説明 (いきなり出て来たSTSって何やぁ? とか) は後述します。

【※※ 注意事項 ※※】


  • 初稿からだいぶ日が経っており、既にSTS3系は古くなってます。現状ではSTS4系を使った方が良いです。

  • また、あんま関係ないですけど、最近自宅PCをRyzenの64bitマシンで組み直したので、自宅開発環境も初稿から大分変わっています。


> 現在の環境情報

ということで、SpringBoot自体の説明にはあんま関係ないですけど、現在の環境を改めて記載しときます。

環境
バージョン

開発言語
Java11 (open-jdk 11.0.1)

開発環境
STS:4.0.2 RELEASE

SpringBoot
2.1.1

DB
PostgreSQL10 + pgAdmin4


■環境構築編

SpringBootでの開発では STSというもの を使うようです。

ここではとりあえずその STSというもの を入手して起動する所までをやってみます。


□ SpringBootと統合開発環境

STS(Spring tool suite) には、IDEとしての STS.exe と、プラグインとしての STS plugin の2種類存在します。



  • STS.exe


  • Eclipse + STS plugin


  • IntelliJ + STS plugin

(あ、最近だとVSCodeって選択肢も出てきましたね、ちょっと気になりますぞ。)

まぁ今回は手っ取り早いので STS.exe を使います。

ちなみにこの STS.exe さんですが、何の事はないただの Eclipseベースの拡張IDE です。

要するにSTSを使うということはEclipseにSTSプラグインを適用するのとだいたい同じ事です。


□ JavaのPath確認

STSを使う前にJavaのPathが通っているか確認します。

普通にコマンドプロンプト cmd.exe を立ち上げて、以下のコマンドを叩いてパス通ってるか確認しましょう。

C:\>echo %JAVA_HOME%

C:\openjdk\jdk-11.0.1

C:\>java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)

出て来なかったらパスがおかしいので、システムの詳細設定から環境変数を調べてパスが通っているか確認してください。

そう言えば、本題と関係ないですけど 環境変数の Path って、Windows10から(もしかして知らなかっただけでWindows8から?)編集しやすくなりましたよね。


□ STSの入手

こちらの 公式ダウンロードページ から入手し、zipを好きな所に展開するだけでOKです。

但し、このページの DOWNLOAD STS から落とすと x86(32bit)版 が落ちて来たので、x64(64bit)版が欲しい人は See All Versions リンク先から入手して下さい。

WS000001.JPG

暫く記事の更新が滞ってるうちにバージョンが上がっちょりました。

WS000096.JPG

今は SpringTool4 (現在はsprint-tool-suite 4.1.0) が出ていて、上記のようにダウンロードページもリニューアルされてます。

STSのダウンロードはここから。

WS000098.JPG

VisualStudioCode用のプラグインも出てました、素晴らしい!

WS000097.JPG

前のSTS3系が欲しい人は、ページ下部のリンクから入手可能なようですが、

STS3系は今年、2019年の中頃にEOLになるよって書いてあるので、素直にSTS4系にした方が良さそうです。

(なので、敢えてSTS3系のリンクや画像は貼らないでおきます)


□ STSの起動

zipを落として来たら、好きなフォルダに展開します。

展開したディレクトリの中(僕の場合は C:\springboot\sts-bundle\sts-3.9.4.RELEASE)にある STS.exe をダブルクリック。

この時、Javaへのパスが通っていない場合は怒られるので、前述の通りきちんとパスの確認をしましょう。

こんな EclipseみたいなIDE (ベースがEclipseなので当たり前) が立ち上がります。

WS000556.JPG


□ STSの日本語化

STS.exe はデフォルトでは日本語化されてないので、別途 Pleiades plugin を入れないと英語表記になります。

英語表記だと困るよ、っていう方は Pleiades からプラグインを入手して下さい。


> 日本語化の参考資料


> 【余談】Pleiades適用が物凄く簡単になってた件

ぼくは普段(めんどくさいので)日本語化せずに英語のまま使ってるんですが、ちょっと手順確認の為にと思って最新のPleiades先生を入手して、適用してみようと思ったんです。

WS000382.JPG

WS000383.JPG

WS000384.JPG

WS000385.JPG

WS000386.JPG

WS000387.JPG

あれ?

Eclipseの日本語化ってこんなに簡単だっけ?

なんか setup-pleiades.exe で STS.exe 指定したらすぐおわった。

以前はなんかjarとか手で配置しに行ったりして、結構ダルい作業だった気がするんですけど。

いやぁ、、、すごいですね、Pleiades先生。

物凄く便利になってました!!


■取り敢えずStarterProjectを使ってみる編


□ 新しいプロジェクトを作る

普通にEclipse感覚で、SpringBootの新規プロジェクトを作成してみます。

[File>New>Spring Starter Project] から選択。

WS000563.JPG

若しくは [File>New>Other] で、メニューから [SpringBoot>Spring Starter Project] を選択。

WS000564.JPG

WS000565.JPG

以下、ウィザードに従っていきます。


□ ウィザード1ページめ

プロジェクトの基本情報を入力します。

WS000566.JPG

さて、いきなり入力項目の多い画面が出て来て嫌になりましたね?

ざっくり各項目を説明しますが、結論から言うと「取り合えずデフォルトのまま突き進んでおk」ですので、かったるい説明が嫌な人は次の見出しまでカッ飛んで下さい。


> ServiceURL

これがSpringBootの正体と言っても過言ではない(多分)、SPRING INITIALIZR大先生です。

取り敢えずデフォルトの https://start.spring.io のままで良いです。

SPRING INITIALIZR 大先生については後程詳しく書きます。(今は深い事気にせずゴー)


> Name

プロジェクト名を入力します。

好きな名前を付けて下さい。

今回は面倒くさいのでデフォルトの demo のまま行きます。


> Type:Maven

ビルドツールを選べます。

デフォルトMavenですが、Gradleが好きって人は変更すれば良いよ。


> Packaging:Jar

実行可能形式JARか、普通にデプロイするWARか選べます。

これも詳しくは後述しますが、SpringBootさんは内蔵Tomcatを持っているので、実行可能形式JARにして単独で立ち上げる事が出来るスグレモノなのです。


> JavaVersion

好きなJavaバージョンを選んで下さい。

※面倒くさいのでデフォルトで突き進んでたら 8 選んでたけど気にしない。皆は素直に 9 か 10 選ぶと良いよ。


> Language

Javaの他にKotlinも選べるよ!!

ことりんかわいいよ、ことりん。

※すいません、言ってみたかっただけです。


> Group/Artifact/Version/Description/Package

適当でおk!!

ぽむぽむ(pom.xml)に書き込まれるから変えたくなったら変えればいいよ。

WS000580.JPG


□ ウィザード2ページめ

プロジェクト依存関係を選択します。

WS000574.JPG

プロジェクト依存関係って何やねん?

ぼくも良く解ってませんが、要するにここで使いたいSpringフレームワークを選べという事みたいです。

ここで選んだやつが pom.xml に書き込まれて、後ほど Maven大先生が必要なJar一式を落としてくれる という手筈。

で、取り敢えず今回は「簡単なWebAPIを作ってみよう編」「Thymeleafで画面を作ってみよう編」で使用するので、以下の依存関係を選択します。


  • Template Engines


    • Thymeleaf



  • Web


    • Web



Thymeleafというのは今回使用するテンプレートエンジンです。

JavaEE開発で使ったJSF(primefaces)みたいなもんです。

まぁあれだ、(jarが) 足りなくなったら (pomに) 足すだけ やから。

あんま深く考えずにサクッと進みましょう、勢い大事、勢い。

今の所DBに接続する所までは考えてませんが、必要になったら入れましょう。


□ ウィザード3ページめ(さいご)

設定確認。

WS000575.JPG

なんか良く解んない画面が出て来ましたね?


見易く整形してみた。

https://start.spring.io/starter.zip

?name=demo
&groupId=com.example
&artifactId=demo
&version=0.0.1-SNAPSHOT
&description=Demo+project+for+Spring+Boot
&packageName=com.example.demo
&type=maven-project
&packaging=jar
&javaVersion=1.8
&language=java
&bootVersion=2.0.2.RELEASE
&dependencies=thymeleaf
&dependencies=web

どうやらこれは Spring Initializr と呼ばれるもののようです。

※ ところでどうでも良いですけど、Initializer じゃなくて Initializr なんですね

詳しい説明は後でやります。

取り敢えず Finish を押してウィザードを完了しましょう。


□ プロジェクトが出来上がります。

暫し待てば demo プロジェクトが出来上がります。

プロジェクトエクスプローラを開いてみるとこんな感じ。

WS000577.JPG

pom.xml を開いてみると、依存関係選択のページで選択した奴が書かれてますね。

プロジェクトツリーの Maven Dependencies を開くと、Maven大先生が依存関係解決で落としてくれたJarがワンサカ詰まっております。

WS000578.JPG


■簡単なWebAPIを作ってみよう編


□ 取り敢えずHelloWorldする

まぁまずは適当にコード書いて動かしてみましょう。


> controller用のパッケージを作ります。

最初は demo パッケージしか無いので com.exapmle.demo.controller を作ります。

WS000584.JPG

WS000585.JPG


> WebAPI用のControllerを作ります。

適当に好きな名前でクラスを作って @RestController アノテーションを付けます。


RestAPI用のControllerを作成。


package com.example.demo.controller;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class WebApiController {
// 内容は後で実装するよ。
}



> RequestMappingを設定します。


メソッドを作って文字列を返してみる。

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("api")
public class WebApiController {

@RequestMapping("hello")
private String hello() {
return "SpringBoot!";
}

}


で、これにアクセスするために application.properties(最初にデフォルトで作られているので、リソース検索してください)に好きなポート番号を設定。


application.propertis

server.port=8080



> 動かしてアクセスしてみる。

先の実装でこんな感じで設定しました。


  • クラスに @RequestMapping をアノテートして引数に "api" と設定

  • メソッドに @RequestMapping をアノテートして引数に "hello" と設定

  • アプリケーション設定に server.port=8080 と設定

なので、このAPIのURLは http://localhost:8080/api/hello となります。

プロジェクトのコンテキストメニューから Run AS > Spring Boot Application を選択すると内蔵tomcatが立ち上がって、裏で色々動いてくれます。(超らくちん!)

WS000588.JPG

ブラウザで先のURLにアクセスすると、文字列が返されます。

WS000589.JPG

「取り敢えず動かしてみる」と言うだけなら1クラス実装するだけで良い、というお手軽さに鼻血が出そうです。

とは言え、固定のプレーンテキストを返されても嬉しくはない、この後色々と作り込んでいき、戻りをJSONにしたり、幾つかの方法でパラメータを渡してみたりしましょう。


□ パラメータを指定する


> パスパラメータ

@PathVariable

    @RequestMapping("/test/{param}")

private String testPathVariable( @PathVariable String param ) {
return "受け取ったパラメータ:" + param;
}

このように、@RequestMapping でマッピングするURLの中に {xxxx}を仕込んでおくと、@PathVariable でアノテートしたパラメータにバインドしてくれます。

ちなみに @PathVariable(name) を省略した場合は、仮引数名と同じ name を指定したのと同じ事になります。

つまり、上記のコードは以下のように記述する事も可能です。

    @RequestMapping("/test/{param}")

private String testPathVariable( @PathVariable("param") String param ) {

    @RequestMapping("/test/{hoge}")

private String testPathVariable( @PathVariable("hoge") String param ) {


> リクエストパラメータ

@RequestParam

    @RequestMapping("/test")

private String testRequestParam( @RequestParam() String param ) {
return "受け取ったパラメータ:" + param;
}

REST API 作るなら基本的に前述の @PathVariable の方を使用するかと思いますが、念の為、普通に(?)URLのクエリパラメータを使うパターンも載せておきます。

強いて言えば、ダミーのHTMLで適当に<form>だけ置いて、パラメータを<input:text>でsubmit掛ける場合とかに使えると思います。

こちらも先の例と同様に、@RequestParam(name) を省略した場合は、仮引数名と同じ name を指定したのと同じ事になります。

つまり、上記のコードは以下のように記述する事も可能です。

    @RequestMapping("/test")

// /test?param=hoge とした場合、paramには "hoge" がバインドされる。
private String testRequestParam( @RequestParam("param") String param ) {
return "受け取ったパラメータ:" + param;
}

    @RequestMapping("/test")

// /test?name=hoge とした場合、paramには "hoge" がバインドされる。
private String testRequestParam( @RequestParam("name") String param ) {
return "受け取ったパラメータ:" + param;
}


余談:

と、ここまで書いてちょっと不安になったのが GET + RequestBody ってホントに出来ないのか?

いやいやそんな話は無いよなぁ、絶対無いよなぁ、と思ってぐぐってみたら面白い記事が見付かったので、貼っておきます。


> リクエストボディ

@RequestBody

通常、リクエストボディを送信する以上、メソッドは POST になるので、上の2パターンとはちょっと指定が変わります。

(と言っても、上のパターンでも GETPOST を明示的に指定出来るのを省略してるだけなので、実際は同じですが)

    @RequestMapping(value = "/test", method = RequestMethod.POST)

private String testRequestBody( @RequestBody String body ) {
log.info( body );
return "受け取ったリクエストボディ:" + body;
}


> 全部纏めたコード

以上、取り敢えず最低限おさえておくべき、パラメータの受け取り3パターンはこんな感じ。

今回は String 一個だけという最小構成気味な形だけに限りましたが、まぁ色々出来るんでその辺は色々やってみれば良いと思います。

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* REST API のサンプルコントローラ.
*
* <p>
* {@code application.properties#server.port = 9999} と設定していると仮定して、
* {@code "localhost:9999/sample/api/test/*"} にアクセスする。
* </p>
*/

@RestController
@RequestMapping("/sample/api")
public class SampleRestApiController {
private static final Logger log = LoggerFactory.getLogger( SampleRestApiController.class );

@RequestMapping("/test/{param}")
private String testPathVariable( @PathVariable String param ) {
log.info( param );
return "受け取ったパラメータ:" + param;
}

@RequestMapping("/test")
private String testRequestParam( @RequestParam() String param ) {
log.info( param );
return "受け取ったパラメータ:" + param;
}

@RequestMapping(value = "/test", method = RequestMethod.POST)
private String testRequestBody( @RequestBody String body ) {
log.info( body );
return "受け取ったリクエストボディ:" + body;
}
}


□ HttpMethodを指定する


/** 登録:CRUDでいう <b>C:CREATE</b> を行うAPI */
@RequestMapping(value="/resource", method=RequestMethod.POST)
private String create(@RequestBody String data) {
return "登録だよ";
}
/** 参照:CRUDでいう <b>R:READ</b> を行うAPI */
@RequestMapping(value="/resource/{id}", method=RequestMethod.GET)
private String read(@PathVariable String id) {
return "参照だよ";
}
/** 削除:CRUDでいう <b>D:DELETE</b> を行うAPI */
@RequestMapping(value="/resource/{id}", method=RequestMethod.DELETE)
private String delete(@PathVariable String id) {
return "削除だよ";
}

/** 更新:CRUDでいう <b>U:UPDATE</b> を行うAPI */
// ※PUTを使うのかPATCHを使うのかと言うのはまた別な話として、、、
@RequestMapping(value="/resource/{id}", method=RequestMethod.PUT)
private String update(@PathVariable String id, @RequestBody String data) {
return "更新だよ";
}

ところで、ソース内のコメントにも記載した通り 更新のAPIに PUT を使うのか PATCH を使うのか と言う話ですが。

この話題についてはこの辺が参考になるかと。


□ 戻り値をString以外にしてみる



与太話

別に戻り値 String のままでも、中身をJSON形式文字列にしてやればJSONデータを返せるものの、

「本当にJSON形式文字列だけが返却されるのか?」

「どころかHTMLが返って来てたりして?」

「と見せ掛けてエラーメッセージが文字列で返されるかも?」

「HTMLといったな、スマンありゃXMLだった」

「JSONだと思った!?残念、CSV形式文字列でした~!!」

など、不安が尽きませんよね。

この String の中身は本当にJSONなのか!?

と言う疑念を抱いてわざわざコードパスを舐めるように追いかけるのは苦痛だし、時間の無駄です。

結論から言って、Beanを返すだけ です。

SpringBoot先生のRestControllerでは、いわゆるJavaBean(POJO)を素直に返すとそのJSONを返すようにしてくれます。

(裏ではjacksonを使ってstringifyしてるはず)


> JavaBeanを返してよしなにしてもらう。


public static class HogeMogeBean {
private String hoge;
private int moge;

// property と ctor は省略。
}

@RequestMapping("hogemoge")
public HogeMogeBean hogemoge() {
return new HogeMogeBean( "ほげ", 1234 );
}

http://localhost:8080/api/hogemoge にアクセスして実行するとこんな感じ。


レスポンス

{"hoge":"ほげ","moge":1234}



レスポンスヘッダ

HTTP/1.1 200

Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: アクセス日時

ちゃんと application/json;charset=UTF-8 も付けてくれてます。


> JSON文字列にちゃんとヘッダ付けてあげる。

逆に、StringでJSON文字列を返してる場合にこのヘッダを付けたい場合はこんな感じ。

    @RequestMapping(value = "hogemoge2", 

produces = MediaType.APPLICATION_JSON_VALUE)
public String string() throws Exception {
HogeMogeBean bean = new HogeMogeBean("もげ", 297);
String json = new ObjectMapper().writeValueAsString(bean);
return json;
}

アクセスするとこう。


レスポンス

{"hoge":"もげ","moge":297}



レスポンスヘッダ

HTTP/1.1 200

Content-Type: application/json;charset=UTF-8
Content-Length: 28
Date: アクセス日時


> めんどくさいのでマップで返しちゃう。

機能設計としての是非は置いといて、さくっと HashMap を返しても大丈夫。

    @RequestMapping("hogemoge3")

public Map<String, Object> map() {
Map<String, Object> map = new HashMap<>();
map.put("hoge", "ぴよ");
map.put("moge", 999);
return map;
}


レスポンス

{"moge":999,"hoge":"ぴよ"}



レスポンスヘッダ

HTTP/1.1 200

Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: アクセス日時


> ファイルを返す。

org.springframework.core.io.FileSystemResource てのを使うと楽に実装できるらしい。

    @RequestMapping( value = "hogemoge4", 

produces = MediaType.APPLICATION_OCTET_STREAM_VALUE )
public Resource file() {
return new FileSystemResource( new File("C:\\test\\hogemoge.png") );
}

試しに画像を置いてアクセスしてみたところ、画像が表示された。


レスポンスヘッダ

HTTP/1.1 200

Accept-Ranges: bytes
Content-Type: image/png
Content-Length: 1568934
Date: アクセス日時

きちんと Content-Type も image/png になってる。

試しにテキストファイルに変更してみたらちゃんと text/plain になってくれた。

これは便利。

下記参考ページにも書いてあるけど、いちいち自分で HttpServletResponse 使ってうにょうにょしなくて良いのが素晴らしい。

というかそのへんを上手いことラップしてるフレームワークなんだから、Http何某の類を直接触るのは極力避けたいですよね。

参考:


□ 共通のエラーハンドラを用意しておく。

いわゆる 集約例外ハンドラ の実装です。

RestController に @ExceptionHandler でアノテートしたメソッドを用意します。

これで、コントローラで発生した未トラップの例外(いわゆるunhandling-exception)を纏めて処理できます。

C#とかで言う所の AppDomain.UnhandledException みたいな使い方が出来るアレですね。


例外ハンドラ

    @ExceptionHandler

private ResponseEntity<String> onError( Exception ex ) {

log.error( ex.getMessage(), ex );

HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
String json = JsonMapper.map()
.put( "message", "API エラー" )
.put( "detail", ex.getMessage() )
.put( "status", status.value() )
.stringify();

return new ResponseEntity<String>( json, status );
}


※ 登場する JsonMapper っていうのはぼくが com.fasterxml.jackson.databind.ObjectMapper をラップして作ったユーティリティです。ここでは単にJSON文字列を作ってるだけです。

コイツの検証用にこんなエンドポイントを追加。


検証用エンドポイント

    @RequestMapping("test/ex")

public String testException() throws Exception {

throw new RuntimeException( "エラー発生" );
}


http://localhost:8080/api/test/ex にアクセスしてみるとこんな感じ。


レスポンス

{"detail":"エラー発生","message":"API エラー","status":500}



詳細

Request URL: http://localhost:8080/api/test/ex

Request Method: GET
Status Code: 500
Remote Address: [::1]:8080
Referrer Policy: no-referrer-when-downgrade


★ RestController/SpringMVC まわりでの参考ページ ★

この記事はなんと言っても 助走本 を目指していますので、

走り出したらこの辺の豊富な素晴らしいドキュメントを読みに行きましょう。

■Spring公式■

まぁまずは公式ガイド。

■Controller関連■

SpringMVCやコントローラ周辺の参考資料としてはこの辺が勉強になりました。

■Security関連■

セキュリティ系に関してはこの辺の記事。

■その他Tips系■

■POSTが必要なエンドポイントの動作検証@PowerShell

普通にSpringBootの範囲内でやろうとすると、後述するThymeleafとかで検証用の画面を作ってPOSTするなり、なんか別なプログラム書いてHttpClient使ってPOSTするなり、或いは素直にPostman使うなりになるかと思いますが、PowerShellを使ってコールするというぼく好みの手法が紹介されてました。

一応、Postmanの記事も置いておきます。


■逆にWebAPIを呼び出してみよう編

外部のAPIをコールするHTTPクライアントとして RestTemplate というものを使います。

※JdbsTemplateといい、RestTemplateといい、SpringさんはHogeHogeTemplateってのが好きみたいですね。


□ 取り敢えず適当なAPIをコールします。

無料で使えて、且つ面倒な認証が必要ない適当なAPIを探して、試しにGETしてみます。


お天気WebサービスのAPIを叩いて返すだけの簡単なお仕事。


/**
* @return 東京の天気情報
*
* @see <a href="http://weather.livedoor.com/weather_hacks/webservice">お天気Webサービス - livedoor</a>
*/

@RequestMapping( value="weather/tokyo"
, produces=MediaType.APPLICATION_JSON_VALUE
, method=RequestMethod.GET)
private String call() {

// "http://localhost:8080/api/weather/tokyo" でアクセス。

RestTemplate rest = new RestTemplate();

final String cityCode = "130010"; // 東京のCityCode
final String endpoint = "http://weather.livedoor.com/forecast/webservice/json/v1";

final String url = endpoint + "?city=" + cityCode;

// 直接Beanクラスにマップ出来るけど今回はめんどくさいのでStringで。
ResponseEntity<String> response = rest.getForEntity(url, String.class);

String json = response.getBody();

return decode(json);
}

// いわゆる日本語の2バイト文字がunicodeエスケープされてるので解除。
private static String decode(String string) {
return StringEscapeUtils.unescapeJava(string);
}


実用的にするなら取ってきた情報をそのまま返すんじゃなくて、きちんとBeanにマップした上で必要な情報だけ返すとか、複数API組み合わせていい感じに纏めて返すとか、付加価値的な物があった方が良いけど。

取り敢えずここではAPIを単品で叩いてみるってだけ。

http://localhost:8080/api/weather/tokyo にアクセスするとこんな感じになりました。

WS000851.JPG


返却されたデータのjson.description.text

,"description":{"text":" 本州付近は高気圧に覆われていますが、東海道沖から伊豆諸島南部は気圧

の谷となっています。

【関東甲信地方】
関東甲信地方は、晴れまたは薄曇りとなっています。

21日は、高気圧に覆われてはじめ晴れる所もありますが、気圧の谷や湿
った空気の影響で次第に曇るでしょう。夜は、伊豆諸島と沿岸部を中心に、
雨や雷雨となる所がある見込みです。

22日は、前線や気圧の谷の影響により曇りや雨で、伊豆諸島と沿岸部で
は、はじめ雷を伴う所があるでしょう。夜は、冬型の気圧配置となるため、
おおむね晴れますが、長野県北部と関東地方北部の山沿いでは雨や雪の降る
所があり、長野県では雷を伴う所がある見込みです。

関東近海では、21日から22日にかけて、うねりを伴って波が高いでし
ょう。船舶は高波に注意してください。

【東京地方】
21日は、はじめ晴れますが次第に曇りとなり、夜遅くには雨となるでし
ょう。
22日は、曇りで、昼前から昼過ぎにかけて雨となる見込みです。"
, ...


ちなみに、使ったAPIが返してくるJSON文字列内の日本語部分がunicodeエスケープされており、元に戻すのに apache-StringEscapeUtils を使ってるので、pomに追加してimportして下さい。


pom.xml

<dependency>

<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.6</version>
</dependency>


import

import org.apache.commons.text.StringEscapeUtils;



□ exchangeで色々と細かく指定しながらコールする。

ここまでで紹介した機能で、非常にカンタンなGETやPOSTは出来るようになりました。

が、現実的な難易度の実装で行くと、前述の説明で書いたような 「特定URLに向けてHttpMethodを指定してアクセスするだけ」 というレベルで済む事はまぁまず無いと思います。

実際の開発になると 「認証情報をヘッダに含めてリクエスト飛ばす」 など、他にも色々とやらないといけない事が出て来ますよね。


> RequestEntity/ResponseEntity

リクエストやレスポンスに関して、細かくやりたいことがある場合は、それぞれ org.springframework.http.RequestEntityorg.springframework.http.ResponseEntity を使用します。

実際にコード見せた方が解り易いと思うので、 ヘッダを指定してJSON文字列を指定エンドポイントにPOSTする簡単な実装例 を示します。


import

import java.net.URI;

import java.util.Map;

import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;



指定エンドポイントに指定のヘッダ情報を付与してPOSTする実装例


// 後述する幾つかの差し替え実装は省略。(詳しくはこの後の「RestTemplateラッパー三点セット」のあたりを参照)
private final RestTemplate rest = new RestTemplate();

/**
* POST実装例.
*
* 指定したエンドポイントに対して {@code json} データをPOSTし、結果を返す。
*
* @param url エンドポイント
* @param headers リクエストヘッダ
* @param json 送信するJSON文字列
* @return 正常に通信出来た場合はレスポンスのJSON文字列を、<br>
* 正常に通信出来なかった場合は {@code null} を返す。
*/

public String post(String url, Map<String, String> headers, String json) {

RequestEntity.BodyBuilder builder = RequestEntity.post( uri( url ) );

for ( String name : headers.keySet() ) {
String header = headers.get( name );
builder.header( name, header );
}

RequestEntity<String> request = builder
.contentType( MediaType.APPLICATION_JSON_UTF8 )
.body( json );

ResponseEntity<String> response = this.rest.exchange(
request,
String.class );

return response.getStatusCode().is2xxSuccessful() ? response.getBody() : null;
}

private static final URI uri( String url ) {
try {
return new URI( url );
}
// 検査例外はうざいのでランタイム例外でラップして再スロー。
catch ( Exception ex ) {
throw new RuntimeException( ex );
}
}


RequestEntity には #post()#get() というHTTPメソッドに対応したbuilderを生成するAPIがあり、何となく使い方は見てすぐ解るかと思います。

上記はPOSTの実装例ですが、GETやDELETEでも大きな違いはありません。

こんな感じで、例えば ヘッダに Authorization Barer 認証トークンを埋めてリクエストする、みたいな事に対応します。


□ 4xx系/5xx系で例外を発生させなくする方法。

RestTemplate先生は、デフォルトのままだと2xx系以外のHttpStatusコードが返ってきた時に強制的に例外を発生させてきます。

HttpStatus
スローされる例外

4xx
HttpClientErrorException

5xx
HttpServerErrorException

xxx
UnknownHttpStatusCodeException

基本的にはこれで困る事は少ないと思いますが、システム要件的にもっと詳細情報を拾う必要があったり、例外ルートではなく正常ルートできちんと処理したいというケースも有り得ます。

(実際、今回の開発では外部APIコールに対する詳細情報が必要だったので、例外発生だと不都合でした)

こういった場合は、RestTemplateのデフォルトエラーハンドラを差し替え てやります。


RestTemplateをラップしたHttpClientクラス


private static class QuietlyHandler extends DefaultResponseErrorHandler {

@Override
public void handleError( ClientHttpResponse response ) throws IOException {
// 何もしない
}
}

protected final RestTemplate rest;

public HttpClient() {
this.rest = new RestTemplate();
this.rest.setErrorHandler( new QuietlyHandler() );
}


デフォルトで使用されている DefaultResponseErrorHandler を継承して #handleError をオーバーライドして NOP(なにもしない)処理で上書き しています。

(デフォルト実装ではここで4xx/5xx系の場合に前述の例外をスローする処理が働きます)

あとは、使用する RestTemplate インスタンスの ErrorHandler プロパティに自前のエラーハンドラを設定してやるだけ。

これで、4xx/5xx系のステータスコードの場合でも、2xx系と同じ正常ルートでレスポンスを受け取れるようになります。

なお、この場合はステータスコードが 2xx系 なのかそれ以外なのか判定する必要があるので ResponseEntity.StatusCodeプロパティ で取得できる HttpStatus の各判定を参照します。


ResponseEntity.StatusCode


HttpStatus status = response.getStatusCode();
boolean information = status.is1xxInformational();
boolean success = status.is2xxSuccessful();
boolean redirection = status.is3xxRedirection();
boolean clientError = status.is4xxClientError();
boolean serverError = status.is5xxServerError();
boolean error = status.isError(); // clientError || serverError

参考:


□ PATCH だけちょっと注意。

本件は先に参考を載せておきます。

という事で、大変便利な RestTemplate 先生ですが、せっかく patchForObjectpatchForEntity といった いかにもPATCHメソッドが使えそう なAPIがあるのに デフォルトでは使えない っていうね。

で、StackOverflow でトム(仮)が「ApacheのHttpComponentsClientHttpRequestFactoryを使えば良いよ」と言ってpomを貼ってくれてますが、SpringFramework3.1 からは org.springframework.http.client.HttpComponentsClientHttpRequestFactory ってのが追加されてて、どうやらコイツが内部的にApacheの某を使っているのでこれを使えば良さそう。


ClientHttpRequestFactory implementation that uses Apache HttpComponents HttpClient to create requests.


雑な和訳


コイツはリクエストを生成するのに「Apache HttpComponents HttpClient」を使用したタイプのClientHttpRequestFactory実装だぜ。


的な事が書いてあります。

詳しく知りたい人はChrome先生に翻訳して貰えばいいと思います。

(最近の翻訳ってメチャクチャ精度上がってるよね)

ということで、前置きが長くなったけどRestTemplateでPATCHを使いたい場合はこんな感じ。


import

import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;



RestTemplateでPATCHを使用する。

        RestTemplate rest = new RestTemplate();

HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

rest.setRequestFactory(requestFactory);


これでも別に問題なく動くけど、下記のタイムアウト設定の方もあわせてやっといたほうが良いですね。


□ タイムアウト設定を弄る方法。

タイムアウト設定に関しても、RestTemplate に設定するのではなく、上記PATCHの時に使った RequestFactory に対して設定します。


RestTemplateへのtimeoutの設定


RestTemplate rest = new RestTemplate();

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connectTimeout);
requestFactory.setReadTimeout(readTimeout);

rest.setRequestFactory(requestFactory);


PATCHを使う必要がないのであれば、デフォルトの SimpleClientHttpRequestFactory を使えば追加のjarが不要ですね。

参考:


□ RestTemplateラッパー三点セット

という事で、それなりの規模の開発をするのであれば、RestTemplateを各所で直接使用するのではなく、ラップした共通部品を作って以下の3点セット対応をした方が無難かと思われます。


  • デフォルトエラーハンドラの差し替え

  • PATCH対応

  • タイムアウト設定

まぁ、その辺は開発指針やプロジェクトの体制にもよりますけど。


■Thymeleafで画面を作ってみよう編


□ SpringBootがサポートするテンプレートエンジン

今回は Thymeleaf(タイムリーフ:タイムの葉っぱ) を使用します。


  • タイムリーフの利点


    • 独自タグを使用しない(メタ属性を仕込む)ため、HTMLを汚さない。

    • HTMLを汚さないので、そのままブラウザで表示できる。

    • そのままブラウザで表示できるので、デザイナと実装の親和性が高い。(と、いわれている)

    • 値をバインドするEL式が比較的わかりやすく、学習コストが低い。(たぶん。JSFとかやってた人なら「ふーん、なるほどね」って感じで見れると思う。)


    • 日本語のドキュメント が充実している。
      (英語が苦手なフレンズでもあんしん!!)




□ とにもかくにも画面だしてみよっか。

取り敢えずバックエンドのモデル(コントローラ)なしで出来る、まっさらなハリボテ画面だけ出してみましょう。

resource/template/index.html を作成し、内容をこんな感じにします。


index.html

<!DOCTYPE HTML>

<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>SpringBoot - テスト用画面</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="UTF-8" />
<style type="text/css">
form {
border-style: solid;
border-color: black;
border-width: 1px;
margin: 5px;
padding: 10px;
}
</style>
</head>
<body>

<h1 th:text="'これはThymeleafですか?'">html-見出し1</h1>
はい、SpringBootで推奨されています。

</body>
</html>


兎にも角にも表示させてみましょう。

デプロイ(っていうか Run as SpringBootApp)して http://localhost:8080 にアクセスします。

すると、こんな画面が出た筈。

WS000205.JPG

次に、 この resource/template/index.html を直接ブラウザで 開いてみましょう。

今度は、こんな画面が出た筈。

WS000208.JPG


<h1 th:text="'これはThymeleafですか?'">html-見出し1</h1>

この部分に違いが出ていますね。

直接ブラウザでhtmlファイルを表示した場合、th:text という属性は標準HTMLに存在しないなので華麗にスルーされ、普通にタグボディに設定されている html-見出し1 という見出しが表示されます。

対して、localhost で(つまりサーバ側のテンプレートエンジンThymeleafを通して)表示したHTMLレスポンスでは、th:text属性値が処理されてタグボディに埋め込まれて これはThymeleafですか? という見出しが表示された、という形です。

と言ってもこれだけやっても別に何も嬉しくない。

サーバ側で動的な値を埋め込んだりしてこそ意味があるので、なんとなーくその辺を追加していきましょう。


■スケジュール機能を使ってみよう編


■ちょっとしたTips集

ログの設定とか、実行可能JARのビルドとか、その辺を追記予定。


□ 自分の書いたプログラムのデバッグログを出す方法。

ログ自体の設定(application.propertieslogback-spring.xml)の話ではなく、ログレベルの設定の話。

アプリ全体のログレベル設定じゃなく、自分の書いたプログラムのログレベル設定にぷちハマったので書いておきます。

結論から言って普通に application.properties に設定出来るんですが 「えっ、そんなのアリなの?」 とちょっと意外だったので。

ルートレベルから指定する方法とかは普通にサラッと紹介されてると思うんですが。


application.properties

logging.level.root=INFO


これだと自分の組んだプログラムにDEBUGレベルが適用できなかったり、

そもそもルートにDEBUGレベル指定するとどっかで永久ループハマって立ち上がらなかったり、

なんか意味解らん動きをしたんですけど、細かい説明が書かれてる記事が無くて困ったんですよね。

で、自分のプログラムのDEBUGログを有効にするにはどうやらこうするようです。


application.properties

logging.level.root=INFO

logging.level.sugaryo.t4j.demo=DEBUG
! ^^^^^^^^^^^^^^^^
! この部分は自分のアプリケーションの名前空間になります

この設定だと自分のアプリケーション全体にログレベルが掛かりますが、更にピンポイントで設定も出来るようです。

WS000355.JPG

この設定で実際にログを出してみました。


ログ出力テストコード

        log.info( "INFO ログ" );

log.debug( "DEBUG ログ" );
log.warn( "WARN ログ" );
log.error( "ERROR ログ" );


ログ出力結果

2019-05-16 03:09:46.617  INFO 10036 --- [nio-8080-exec-1] s.t4j.demo.controller.WebApiController   : INFO ログ

2019-05-16 03:09:46.617 DEBUG 10036 --- [nio-8080-exec-1] s.t4j.demo.controller.WebApiController : DEBUG ログ
2019-05-16 03:09:46.617 WARN 10036 --- [nio-8080-exec-1] s.t4j.demo.controller.WebApiController : WARN ログ
2019-05-16 03:09:46.617 ERROR 10036 --- [nio-8080-exec-1] s.t4j.demo.controller.WebApiController : ERROR ログ

はい、こんな感じでちゃんとDEBUGレベルでログが出てますね。

この辺ってSpringFramework経験者からすると常識なんでしょうけど、SpringBootから入った自分にはハマりポイントでした。


□ 実行可能JARとして設定してビルドする。

実装中に動作確認する意味で動かすには、前述の通り Run AS > Spring Boot Application メニューで起動すると思いますが、リリース用とかで実行可能JARをビルドしたいだけの場合。

びっくりしたんですが、デフォルトのままだとダメでした。

実行可能JARとしてビルドするため pom.xml に以下の設定追加が必要になります。

build.plugins.plugin.configuration.executable = true


pom.xml(変更前)

    <build>

<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

こいつを


pom.xml(変更後)

    <build>

<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>

こうします。

この設定をやっておかないと、CentOSに持ってって service コマンドでサービス登録して動かそうと思った時に上手くいきませんでした。

あとは Run As > Maven build でビルドするとJARが出来上がるんで、持ってってください。


あとがき「SpringBootってなんだろう?」