LoginSignup
0
3

More than 3 years have passed since last update.

SpringBootの分かりやすい例を作りためてみる

Last updated at Posted at 2020-02-26

授業でSpringBootを使うことになったので,使いそうで分かりやすい例を作りためてみることにしました.
また,以下の例は https://github.com/igakilab/springbootsamples_full/ に最新版がUPされています.
以降,例を作成する際の基準として,できる限りシンプルで理解しやすく短いコードで実現できるものを目指しています.
SpringBootのv2.2.4.RELEASE対象.

開発環境

  • vscode-portable-win64-1.42.1-27
    • Debugger for Java
    • EvilInspector
    • Gradle Language Support
    • Japanese Language Pack
    • Language Support for Java
    • Spring Initializr Java Support
  • PortableGit-2.25.0-64
    • PortableGitの/etc/profile.d/bash_profile.shにgradleやJDKへのPATHを通し,vscodeのintegrated terminalとしてPortableGitのbash.exeを指定することで,環境変数を汚さずにgradle buildなどが行えるようになる
  • gradle-6.2
  • Amazon Coretto(OpenJDK)11.0.5_10
  • アプリケーションサーバはSpringBoot標準の組み込みtomcatではなく,組み込みjettyを利用している.SSE利用時の例外処理に関する問題のため.

セットアップ(簡易版)

$ git clone git@github.com:igakilab/springbootsamples.git
Cloning into 'springbootsamples'...
remote: Enumerating objects: 293, done.
remote: Counting objects: 100% (293/293), done.
remote: Compressing objects: 100% (179/179), done.
remote: Total 767 (delta 109), reused 223 (delta 54), pack-reused 474
Receiving objects: 100% (767/767), 233.15 KiB | 700.00 KiB/s, done.
Resolving deltas: 100% (264/264), done.
$ cd springbootsamples
$ git checkout refs/tags/v0.0.3
$ gradle bootRun
:
<=========----> 75% EXECUTING [26s]
> :bootRun

セットアップ(0からやる場合)

Spring Initializrを利用したセットアップ

  • 作成したいアプリの名前でフォルダを作成し,そのフォルダをvscodeで開く
  • 表示->コマンドパレット,を選択し,Spring Initializr:Generate a Gradle Project を実行する
    • Specify project language: Java
    • Input Group Id for your project: jp.ac.hoge.is.{チーム名をアルファベット小文字で}
      • 例:jp.ac.hoge.is.inudaisuki
      • スペースや特殊文字を含めないこと.すべてアルファベット小文字.
      • 必ずしもjp.ac.hogeから始まらなくても良い
    • Input Artifact Id for your project: {アプリ名をアルファベット小文字で}
      • 例:dogland
      • スペースや特殊文字を含めないこと(_は入力できるが,不具合を誘発することがあるので使わないほうが良い).すべてアルファベット小文字.また,セットアップ時に作成したフォルダ名と同じにしておくこと.
    • Specify Spring Boot version.: 2.2.4
    • Search for dependencies
      • 以下を選択
      • Spring Web
      • Thymeleaf
      • Mybatis Framework
      • H2 Database
      • Spring Security
    • フォルダの選択を行うダイヤログが表示されるので,現在のフォルダのひとつ上を指定して,springbootのためのフォルダを作成させる.
    • このとき,すでにあるフォルダを上書き(overwrite)してよいか問い合わせがあるので,OKすること.
    • Successfully Generatedと出ればOK.
  • .gitignore作成
  • build.gradleを修正
    • https://github.com/igakilab/springbootsamples/blob/v0.0.3/build.gradle
    • tomcatでなくjettyを利用する設定を行い,当初はSecurityを使わないのでその設定をコメントアウトする
      • implementation 'org.springframework.boot:spring-boot-starter-jetty'追加
      • configurations追加
      • securityのimplementation設定をコメントアウト

application.properties

server.jetty.accesslog.filename=jetty-access.log
server.jetty.accesslog.enabled=true
server.jetty.accesslog.date-format=yyyy/mm/dd:HH:mm:ss Z
server.jetty.accesslog.time-zone=JST
server.jetty.accesslog.log-server=true
server.port=8000
spring.datasource.sql-script-encoding=UTF-8
  • プロジェクトの直下にアクセスログが保存されるようになる(logsなどのディレクトリを作成して保存も可能だが,その場合はlogsディレクトリが作成されていないとbuild errorが発生するようになってしまう).LogFormat等は今後要検討
  • ポート番号 server.port=8000 を設定することで, http://localhost:8000/ でSpringBootアプリが動作するようになる

index.htmlの配置

  • src/main/resource/static/index.html を置く.index.htmlの中身はなんでもOK

SpringBootWebアプリの実行方法

  • vscodeからターミナル->新しいターミナル,を選択し,bashのターミナルをエディタ下部に開く
  • build.gradleファイルがあるのと同じディレクトリにいることを確認後,gradle bootRunを実行するとSpringBootアプリがビルドされ,組み込みjettyで起動する
    • gradle buildを実行するとbuild/libs/ 以下に作成されるjarを対象に,java -jar ???.jar でもSpringBootWebアプリケーションを起動できる
  • http://localhost:8000/ にアクセスしたときになにかWebページが表示されていればOK.
  • 終了時は gradle bootRunを実行しているターミナルで,Ctr+Cを実行すれば良い
    • vscode内のターミナルではなく,別のターミナルで実行したときに,Ctr+Cが効かない場合がある.その場合は別のターミナルを開き,build.gradleがあるフォルダでgradle --stop と実行すると良い.

リポジトリの作成とpush

  • git init, git push

Samples(シンプルなGET/POST)

  • HTTP/GET,POSTを利用したWebアプリケーションの作成方法

[Sample1-1]templateを利用したhtmlファイルの表示(HTTP/GET)

  • 特定のURLリクエストに対して静的なhtmlを返すアプリケーション

参考

関連するファイル

関連する機能

動作確認

[Sample1-2] java-html間の値の受け渡し(HTTP/POST, タイムリーフ)

  • Webページのフォームで入力した値をjavaにPOSTし,処理した結果をhtmlで受け取って表示する.

参考

関連するファイル

関連する機能

動作確認

  • http://localhost:8000/sample12 をブラウザで表示する
  • フォーム(テキストボックス)と送信ボタンが表示されるので,数字を入力して送信すると,フォームの下にGET 1111のように値が2行表示されればOK.

Samples(RestAPIの作成)

  • urlでリクエストをかけると,htmlではなく何らかの値や文字列が返ってくるRestAPIを実装する

[Sample2-1] RestControllerの基本的な利用方法

  • Classにapiという名前をつけて,メソッドにrest21という名前をつける.
  • /api/rest21 にGETリクエストすると,Javaのrest21メソッドの返り値がかえってくる(HTMLではない)

参考

関連するファイル

関連する機能

動作確認

  • http://localhost:8000/api/sample21 をブラウで表示する,あるいはbashターミナルで下記のように実行したときに,Hello sample21! と表示されればOK
$ curl -s http://localhost:8000/api/sample21
Hello sample21!
  • curlの-s オプションはプログレス情報を表示しないためのもの

[Sample2-2] パラメータを渡してRestAPIを呼ぶ方法

  • パスパラメータ,クエリパラメータの2種類の方法でRestAPIを呼び,Javaに値を渡す方法
    • RestAPI以外でも使える

参考

関連するファイル

関連する機能

動作確認

  • bashターミナルで下記のようになればOK.ブラウザでも確認できる.
  • 上2つがパスパラメータ,3つ目がクエリパラメータ.
$ curl -s http://localhost:8000/api/sample22/hoge
受け取ったパラメータはhogeです
$ curl -s http://localhost:8000/api/sample22/hoge/fuga
受け取ったパラメータはhogeとfugaです
$ curl -s http://localhost:8000/api/sample22?param=ora
受け取ったクエリパラメータはoradayo
  • curlの-s オプションはプログレス情報を表示しないためのもの

Samples(ベーシック認証)

[Sample3-1]最もシンプルなベーシック認証

  • 特定のURLにアクセスする際にID・パスワードでの認証を行う

参考

関連するファイル

関連する機能

  • ベーシック認証
    • アカウントの追加
    • 指定したURLリクエストを対象とした認証の追加
  • @Configuration
  • @EnableWebSecurity // securityモジュールを利用するためのアノテーション

動作確認

  • ブラウザで http://localhost:8000/sample3/sample31 にアクセスする
  • ベーシック認証のダイアログが表示されるので,user/password と入力する.
  • Authenticated! と表示されればOK

[Sample3-2]ベーシック認証時にログインユーザ名を取得する方法

参考

関連するファイル

関連する機能

  • Principalを利用したログインユーザ情報の取得
  • タイムリーフを利用したModelMapからの値の取得

動作確認

  • http://localhost:8000/sample3/sample32 にブラウザでアクセスする
  • ベーシック認証のダイアログが表示されるので,user/password と入力する
    • すでにログインしていたらダイアログは表示されない
  • Hello userと表示されたらOK(userはログインID).

[Sample3-3]フォームでのログインとログアウト

  • フォームでログインし,logoutリクエストでログアウトを行うサンプル

参考

関連するファイル

関連する機能

  • SpringBootのformLogin/logout機能の利用

動作確認

  • http://localhost:8000/sample3/sample31 にブラウザでアクセスする
  • SpringBootの提供するログインフォーム(ベーシック認証のダイアログではない)が表示されるので,user/password と入力する.
  • Authenticated! と表示されればOK
  • http://localhost:8000/logout にブラウザでアクセスすると,サインイン情報が削除されて再度ログインフォームが表示される.

[Sample3-4]InMemory認証でのユーザの追加

  • user2,user3,adminユーザを追加してみる

参考

関連するファイル

関連する機能

  • SpringBootのauth.inMemoryAuthentication().withUser

動作確認

  • http://localhost:8000/sample3/sample32 にブラウザでアクセスする
  • SpringBootの提供するログインフォーム(ベーシック認証のダイアログではない)が表示されるので,Sample3BasicAuthConfiguration.javaで実装した下記ユーザでログインできるか確認する
    auth.inMemoryAuthentication().withUser("user").password("{noop}password").roles("USER");
    auth.inMemoryAuthentication().withUser("user2").password("{noop}password2").roles("USER");
    auth.inMemoryAuthentication().withUser("user3").password("{noop}password3").roles("USER");
    auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("ADMIN");
  • Hello user など,ログインしたユーザ名が表示されればOK

Samples(データベースとの連携)

[Sample4-1]DBのテーブル設定,値登録とselectによる取得

  • schema.sqlの内容でテーブルを構築し,data.sqlの内容で初期データを登録する.
  • DBからSELECT文で値を取得し,Fruitsオブジェクトに格納する処理をMybatisのマッパー機能を利用して実装する.

参考

関連するファイル

  • 実装:https://github.com/igakilab/springbootsamples/commit/c13b2a1fd5170dcb24214abe4fb3a2cc5ecb85df
  • Fruits.java
  • FruitsMapper.java
    • Fruitsオブジェクトの内容とDBの内容をFruitsMapperインタフェースを利用して関連付ける
  • schema.sql
    • DBの初期構造(要はCreate Table)
  • data.sql
    • DBの初期データ(Insert)
  • Sample4Controller.java
  • この例ではすでに実装されているが,application.propertiesで文字コード(utf8)の設定(spring.datasource.sql-script-encoding=UTF-8)とbuild.gradleのdependenciesに下記の追記が必要 gradle implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1' runtimeOnly 'com.h2database:h2'
    • これが記述されているとH2DBの場合はあらゆるDB接続関連の設定をSpringBootが自動でやってくれるようになる.

関連する機能

動作確認

  • http://localhost:8000/sample41 にアクセスすると,ターミナルに下記が表示されればOK.ブラウザにはFruits Listとだけ表示される.
さがほのか
10
5.8
いちご
false
  • Sample4Controllerクラスのsample41GetFruitsメソッド内でDBから値を取得してFruitsオブジェクトに格納し,表示している.

[Sample4-2]DBの値を取得し,GETでHTMLに渡して表示する方法

参考

関連するファイル

関連する機能

動作確認

  • ブラウザで http://localhost:8000/sample42 にアクセスすると,ブラウザにFruits Listの下に下記が2回繰り返して表示されればOK.
さがほのか
10
5.8
いちご
false
  • Sample4Controllerクラスのsample42GetFruitsメソッド内でDBから値を取得してFruitsオブジェクトに格納し,ModelMapオブジェクトに渡してhtmlから参照している.

[Sample4-3] フォームでPOSTしたデータをDBに登録する

  • オブジェクトごとPOSTでデータをHTMLからJavaに渡し,insert文をmapperインタフェースを利用して実行する

参考

関連するファイル

関連する機能

動作確認

  • http://localhost:8000/sample43 にブラウザでアクセスし,名前に果物の名前を数に数値を入れて「送信」ボタンをクリックするとターミナルに以下のように入力した果物名や数値が表示されればOK
  • フォームに入力して「送信」すると,id2として(auto increment)送信した内容がDBに登録され,それがそのままフルーツ2としてターミナルに表示される
ぶどう
100
0.0
null
false

動作確認(H2DBコンソール)

  • http://localhost:8000/h2-console にアクセスし,application.propertiesの以下の項目に従って入力する
    • [Saved Settings] Generic H2(Embedded)
    • [Driver Class] org.h2.Driver
    • [JDBR URL] jdbc:h2:mem:testdb
    • [User Name] sa
  • Connectをクリックするとjdbc:h2:mem:testdbに接続される.
  • FRUITSというテーブルが作成されているので選択し,SELECT文などをRunさせると,テーブルにデータがINSERTされていることが確認できる.
  • Sample3BasicAuthConfiguration.javaに設定したようにCSRFとX-Frameの設定を解除しないとh2-consoleは正しくConnectできない.
spring.datasource.url=jdbc:h2:mem:testdb
# H2DBを利用する場合のドライバ名ユーザ名パスワードなし
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

[Sample4-4] DBから複数の値をArrayListで取得し,htmlで表示するサンプル

参考

関連するファイル

関連する機能

動作確認

index:0 id:1 さがほのか 10 5.8 いちご false
index:1 id:2 レモン 100 0.0 false
  • 最初のindexはステータス変数(stat.index)の値.他のステータス変数については参考資料参照.

Samples(非同期呼び出し)

[Sample5-1] @Asyncを利用した非同期処理

  • URLにアクセスすると一瞬でレスポンスがあるが,実際の処理はそのあと行われる

参考

関連するファイル

関連する機能

動作確認

  • curl -i -s -N http://localhost:8000/sample5/sample51 と実行すると,すぐに結果(Tue Feb 25 12:05:50 JST 2020 などの現在時刻)が返ってきて,内部の処理自体は5秒後に完了していればOK.async startからasync endまでgradleを実行したターミナルに表示されている.
    • -i:Respnse Header, Body 両方を出力
    • -s:silent mode
    • -N: No buffer
  • 同じURLにブラウザでアクセスしてもOK

[Sample5-2] Push型の非同期処理

  • ブラウザから対象のAPIを呼び出すと,開いている間Push形式でJava側の処理が終わるたびにデータをブラウザで受け取って表示することができる.
  • サーバ->クライアントへの通信のみ対応.
  • IEとEdgeでは対応してないっぽい.

参考

関連するファイル

関連する機能

  • Java側
    • emitter.send()
    • 対象メソッドの引数に与えられたSseEmitterにsend()で引数として他クラスのオブジェクトや文字列を与えることで,javascript側でオブジェクトの場合はjsonオブジェクトとして,文字列の場合は文字列として受け取ることができる.
    • emitter.complete()
    • これを呼び出すことで,emitterを利用して呼び出し処理が明示的に終了される.多分呼び出さないとemitterが終了されないままで,再利用できなくなる気がする.
    • また,これが呼び出されていると,JS側でEventSourceに指定されたapiが何度も呼び出されて,streamingメソッドも何度も呼び出されるが,complete()していないと,streamingメソッドは再度呼び出されることはなくなる(正確には呼ばれていてもemitterが機能しない?).
    • 発生する例外について
    • asyncService.async()内の処理を実行している最中にブラウザが終了する等でクライアントとの接続が切れた場合,以下のような例外が発生する.
      • [組み込みTomcat] java.lang.IllegalStateException: Calling [asyncError()] is not valid for a request with Async state [MUST_DISPATCH]
      • [組み込みjetty]org.eclipse.jetty.io.EofException: null
      • TomcatのほうはSpringbootで発生してる?のか,catchできない.
      • jettyのほうはemitter.send()でIOExceptionとして発生するので,catchすればクライアント切断時の処理を正しく実装できる.
  • EventSource(JS側)
    • Server-Sent EventsをJSで受け取るために使う.URLを引数に与えると,emitter.send()で送信された内容を送信されるたびにon.messageで受け取ることができる
    • sse.onmessage
    • EventSourceからメッセージが送られてきたら(MessageEvent),function(evt)を処理する.
    • EventSourceはサーバから接続が切断されても自動的に再接続が行われるらしい.回避しようと思ったら,処理終了時や例外発生時(切断時に例外が発生する)にsse.close()を呼び出すと良い.
    sse.onerror = function (evt) {
    console.log("error!!");
    sse.close();
  }

動作確認

  • curl -i -s -N http://localhost:8000/sample5/sample52 と実行すると下記のようなヘッダやレスポンスが返る
    • -i:Respnse Header, Body 両方を出力
    • -s:silent mode
    • -N: No buffer.これがついていないとデータがまとめて返ってくる
    • text/event-streamになっていること,dataがemitter.sendのタイミングで送信されてくることを確認する
HTTP/1.1 200 OK
Date: Wed, 26 Feb 2020 00:29:08 GMT
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Content-Type: text/event-stream;charset=UTF-8
Transfer-Encoding: chunked

data:{"id":0,"name":"メロン","num":100,"weight":1.7,"details":null,"sent":false}

data:{"id":0,"name":"メロン","num":100,"weight":1.7,"details":null,"sent":false}

data:{"id":0,"name":"メロン","num":100,"weight":1.7,"details":null,"sent":false}

data:{"id":0,"name":"メロン","num":100,"weight":1.7,"details":null,"sent":false}
  • ブラウザでhttp://localhost:8000/sample5/sample52にアクセスすると↑と同様emitter.sendのタイミングでデータが送信される.10回送信された時点で終了する.
  • ブラウザでhttp://localhost:8000/sample52-1.htmlにアクセスするとasyncService.async52()の処理がすべて完了後(約20秒後)にブラウザにすべてのデータ(10個分)が表示される.emitterがほぼ意味がない状態.
  • ブラウザでhttp://localhost:8000/sample52-2.htmlにアクセスすると,emitter.sendが実行されるたびにデータが画面に1つずつ表示される.また,一通り終わったあと続けてsample52が再呼び出しされるため,ブラウザを表示している限りデータを受信し続ける.
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3