Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その6 )( Thymeleaf を 2.1.5 → 3.0.6 へバージョンアップする2 )
概要
記事一覧はこちらです。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その5 )( Thymeleaf を 2.1.5 → 3.0.6 へバージョンアップする ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 前回に続き Thymeleaf 3 へのバージョンアップを行います。
参照したサイト・書籍
- Read Input hidden tag value from url of website using Jsoup in java
https://stackoverflow.com/questions/13071165/read-input-hidden-tag-value-from-url-of-website-using-jsoup-in-java
目次
- mainparts.html の head タグを th:fragment 化し、head-cssjs.html を削除する(その2)
- LibraryHelper#getSelectedLibrary を変更する
- 全ての各画面用 Thymeleaf テンプレートに
<!--/*@thymesVar id="..." type="..."*/-->
を記述する - WARN ログ
[THYMELEAF][main] Template Mode 'HTML5' is deprecated. Using Template Mode 'HTML' instead.
が出力されないようにする - clean タスク → Rebuild Project → build タスクを実行する
- メモ書き
手順
mainparts.html の head タグを th:fragment 化し、head-cssjs.html を削除する(その2)
残りの各画面用 Thymeleaf テンプレートを変更します。ファイル数が多いので、変更内容は書きません。また全て src/main/resources の下のファイルです。
- templates/booklist/booklist.html
- templates/booklist/complete.html
- templates/booklist/fileupload.html
- templates/confirmresult/confirmresult.html
- templates/lendingapp/lendingapp.html
- templates/lendingapproval/lendingapproval.html
- templates/sessionsample/confirm.html
- templates/sessionsample/first.html
- templates/sessionsample/next.html
- templates/springmvcmemo/beanValidationGroup.html
- templates/textareamemo/display.html
- templates/textareamemo/index.html
- templates/error.html
- templates/login.html
- templates/loginsuccess.html
Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その26 )( jar ファイルを作成して動作確認する2 ) に記載した手順に従って画面を操作し、画面の表示がおかしなところがないことを確認します。
テストで
.andExpect(xpath(...))
を使用しているところを変更します。こちらも変更内容は書きません。全て src/test/java の下のファイルです。- ksbysample.webapp.lending.web.confirmresult.ConfirmresultControllerTest
- ksbysample.webapp.lending.web.lendingapp.LendingappControllerTest
- ksbysample.webapp.lending.web.lendingapproval.LendingapprovalControllerTest
clean タスク → Rebuild Project → build タスクを実行して “BUILD SUCCESSFUL” が出力されることを確認します。
LibraryHelper#getSelectedLibrary を変更する
src/main/java/ksbysample/webapp/lending/helper/library/LibraryHelper.java を リンク先の内容 に変更します。
src/test/java/ksbysample/webapp/lending/helper/library/LibraryHelperTest.java を リンク先の内容 に変更します。変更後、テストが成功することを確認します。
src/main/resources/templates/common/mainparts.html を リンク先の内容 に変更します。
Tomcat を起動して、図書館未選択時に “※図書館が選択されていません"、選択時に "選択中:…” が表示されることを確認します(画面キャプチャは省略します)。
全ての各画面用 Thymeleaf テンプレートに <!--/*@thymesVar id="..." type="..."*/-->
を記述する
以下の Thymeleaf テンプレートに
<!--/*@thymesVar id="..." type="..."*/-->
を追加します。変更内容は書きません。全て src/main/resources の下のファイルです。- templates/admin/library/library.html
- templates/booklist/booklist.html
- templates/booklist/fileupload.html
- templates/confirmresult/confirmresult.html
- templates/lendingapp/lendingapp.html
- templates/lendingapproval/lendingapproval.html
<!--/*@thymesVar id="..." type="..."*/-->
の記述は IntelliJ IDEA だと以下の操作で自動挿入できます。th:object
等で書いている変数の<!--/*@thymesVar id="..." type="..."*/-->
の記述がないと、以下の画像のように赤波線が表示されます。赤波線が表示されている変数にカーソルを移動して Alt+Enter を押すとコンテキストメニューが表示されますので、「Declare external variable in comment annotation」を選択します。
<!--/*@thymesVar id="..." type="..."*/-->
の記述が自動で挿入されますので、あとは type の部分を記入します。
WARN ログ [THYMELEAF][main] Template Mode 'HTML5' is deprecated. Using Template Mode 'HTML' instead.
が出力されないようにする
- src/main/resources/application.properties を リンク先の内容 に変更します。
clean タスク → Rebuild Project → build タスクを実行する
最後に clean タスク → Rebuild Project → build タスクを実行して、ここまでの変更で問題がないことを確認します。
メモ書き
th:block
は末尾に “/” が必ず必要
今回 src/main/resources/templates/common/mainparts.html を修正した時に、Thymeleaf 3 から末尾の “/” は不要になったと考えて、最初 <th:block th:replace="${links} ?: _">
と末尾に “/” を付けずに書いたのですが、これだときちんと動作しませんでした。
例えば「貸出希望書籍 CSV ファイルアップロード」画面(http://localhost:8080/booklist)を表示すると、<th:block th:replace="${links} ?: _"/>
の時は、HTML は以下のように出力されますが、
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>貸出希望書籍 CSV ファイルアップロード</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.4 --> <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"> <!-- Font Awesome Icons --> <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <!-- Ionicons --> <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"> <!-- Theme style --> <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"> <!-- AdminLTE Skins. Choose a skin from the css/skins folder instead of downloading all of them to reduce the load. --> <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="/js/html5shiv.min.js"></script> <script src="/js/respond.min.js"></script> <![endif]--> <!-- ここに各htmlで定義された link タグが追加される --> <link href="/css/fileinput.min.css" rel="stylesheet" type="text/css"> <style type="text/css"> <!-- .jp-gothic { font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif; } .content-wrapper { background-color: #fffafa; } .noselected-library { color: #ff8679 !important; font-size: 100%; font-weight: 700; } .selected-library { color: #ffffff !important; font-size: 100%; font-weight: 700; } .box-body.no-padding { padding-bottom: 10px !important; } .table>tbody>tr>td , .table>tbody>tr>th , .table>tfoot>tr>td , .table>tfoot>tr>th , .table>thead>tr>td , .table>thead>tr>th { padding: 5px; font-size: 90%; } .has-error { background-color: #ffcccc; } --> </style> <!-- ここに各htmlで定義された style タグが追加される --> <style type="text/css"> <!-- .callout ul li { margin-left: -30px; } --> </style> </head>
<th:block th:replace="${links} ?: _">
のように末尾の “/” を付け忘れると、<style type="text/css">
から下が表示されなくなります。しかも何のエラーも出ません(画面の背景色が青くなったのでおかしいことに気づきました)。
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>貸出希望書籍 CSV ファイルアップロード</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.4 --> <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"> <!-- Font Awesome Icons --> <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <!-- Ionicons --> <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"> <!-- Theme style --> <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"> <!-- AdminLTE Skins. Choose a skin from the css/skins folder instead of downloading all of them to reduce the load. --> <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="/js/html5shiv.min.js"></script> <script src="/js/respond.min.js"></script> <![endif]--> <!-- ここに各htmlで定義された link タグが追加される --> <link href="/css/fileinput.min.css" rel="stylesheet" type="text/css"></head>
HTML の meta タグ等は末尾を />
にする必要はなくなりましたが、th:block
は <th:block ... />
のように必ず末尾に “/” を付けましょう。
.andExpect(...)
は lambda 式で .andExpect(mvcResult -> {...})
と書けば AssertJ が利用できる
今回久しぶりに ResultMatcher のクラスを作成して気づきましたが、.andExpect(...)
内に指定する式は、
- MvcResult 型の引数を1つ渡して、戻り値が void 型の lambda 式でよい。
- lambda 式の中で AssertionError の例外が throw されればテストが失敗する。
ということに気づきました(org.springframework.test.web.servlet.ResultMatcher インターフェース参照)。
ということは、.andExpect(mvcResult -> {...})
の形式で書けば、AssertJ を利用することができます。例えば、src/test/java/ksbysample/webapp/lending/web/lendingapproval/LendingapprovalControllerTest.java の lendingAppIdパラメータで指定されたデータが登録されており承認権限を持つユーザならば貸出承認画面が表示される()
メソッドを以下のように変更して、
@Test @TestData("web/lendingapproval/testdata/001") public void lendingAppIdパラメータで指定されたデータが登録されており承認権限を持つユーザならば貸出承認画面が表示される() throws Exception { mvc.authSuzukiHanako.perform(get("/lendingapproval?lendingAppId=105")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapproval/lendingapproval")) .andExpect(model().hasNoErrors()) .andExpect(html("#lendingapprovalForm > div > div > table > tbody > tr").count(3)) .andExpect(mvcResult -> { assertThat(Jsoup.parse(mvcResult.getResponse().getContentAsString()) .select("#lendingapprovalForm > div > div > table > tbody > tr > td:eq(1)")) .extracting(Element::text) .containsOnly("978-4-7741-5377-3", "978-4-7973-4778-4", "978-4-87311-704-1"); }); }
テストを実行すると、問題なく成功します。
.containsOnly(...)
に記述する要素を1つ削除してテストが失敗するように変更すると、
@Test @TestData("web/lendingapproval/testdata/001") public void lendingAppIdパラメータで指定されたデータが登録されており承認権限を持つユーザならば貸出承認画面が表示される() throws Exception { mvc.authSuzukiHanako.perform(get("/lendingapproval?lendingAppId=105")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(view().name("lendingapproval/lendingapproval")) .andExpect(model().hasNoErrors()) .andExpect(html("#lendingapprovalForm > div > div > table > tbody > tr").count(3)) .andExpect(mvcResult -> { assertThat(Jsoup.parse(mvcResult.getResponse().getContentAsString()) .select("#lendingapprovalForm > div > div > table > tbody > tr > td:eq(1)")) .extracting(Element::text) .containsOnly("978-4-7741-5377-3", "978-4-7973-4778-4"); }); }
テストが失敗して、かつ失敗の原因が表示されます。
.andExpect(...)
の中で AssertJ を使うことは可能です、という話でした。
ソースコード
LibraryHelper.java
public String getSelectedLibrary() { String result = null; LibraryForsearch libraryForsearch = libraryForsearchDao.selectSelectedLibrary(); if (libraryForsearch != null) { result = "選択中:" + libraryForsearch.getFormal(); } return result; }
- getSelectedLibrary メソッドで図書館が選択されていない場合には “※図書館が選択されていません” ではなく null を返すようにします。
LibraryHelperTest.java
@Test public void testGetSelectedLibrary_図書館が選択されていない場合() throws Exception { given(libraryForsearchDao.selectSelectedLibrary()).willReturn(null); String result = libraryHelper.getSelectedLibrary(); assertThat(result).isNull(); }
testGetSelectedLibrary_図書館が選択されていない場合()
テストメソッド内のassertThat(result).isEqualTo("※図書館が選択されていません");
→assertThat(result).isNull();
ヘ変更します。
mainparts.html
<!-- Navbar Right Menu --> <div class="navbar-custom-menu"> <p class="navbar-text" th:with="selectedLibrary=${@libraryHelper.getSelectedLibrary()}" th:classappend="${selectedLibrary} ? 'selected-library' : 'noselected-library'" th:text="${selectedLibrary} ?: _">※図書館が選択されていません</p> <ul class="nav navbar-nav"> <li><a href="/logout">ログアウト</a></li> </ul> </div>
th:with="selectedLibrary=${@libraryHelper.getSelectedLibrary()}"
を追加します。th:classappend
の中の${#strings.startsWith(@libraryHelper.getSelectedLibrary(), '※')}
→${selectedLibrary}
に変更します。また'noselected-library' : 'selected-library'
→'selected-library' : 'noselected-library'
に変更します。th:text
の中の${@libraryHelper.getSelectedLibrary()}
→${selectedLibrary} ?: _
に変更します。
application.properties
spring.freemarker.cache=true spring.freemarker.settings.number_format=computer spring.freemarker.charset=UTF-8 spring.freemarker.enabled=false spring.freemarker.prefer-file-system-access=false spring.thymeleaf.mode=HTML valueshelper.classpath.prefix=
spring.thymeleaf.mode=HTML
を追加します。
履歴
2017/05/25
初版発行。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その5 )( Thymeleaf を 2.1.5 → 3.0.6 へバージョンアップする )
概要
記事一覧はこちらです。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その4 )( 1.4系 → 1.5系で変更された点を修正する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 以下の記事で Thymeleaf を 3 へバージョンアップしても問題ないか検証して、特に問題はなかったので今回 3 へバージョンアップします。
参照したサイト・書籍
AttoParser
http://www.attoparser.org/jsoup: Java HTML Parser
https://jsoup.org/sagan/sagan-site/src/it/java/saganx/AuthenticationTests.java https://github.com/spring-io/sagan/blob/master/sagan-site/src/it/java/saganx/AuthenticationTests.java
- Jsoup の使い方を参考にしました。
Convert xPath to JSoup query
http://stackoverflow.com/questions/16335820/convert-xpath-to-jsoup-queryGroovyのヒアドキュメント
http://d.hatena.ne.jp/Kazuhira/20130715/1373878511
目次
- 変更の方針
- build.gradle を変更する
- SpELコンパイラを有効にします
- mainparts.html の head タグを th:fragment 化し、head-cssjs.html を削除する(その1)
- ここで一旦動作確認する
xpath()
の代わりに使用できる HTML 5 チェック用のhtml()
ResultMatcher を作成する- 失敗したテストを変更する
- 続きます。。。
手順
変更の方針
- Thymeleaf は 2.1.5 → 3.0.6 へバージョンアップします。Thymeleaf の関連ライブラリもバージョンアップします。
- SpELコンパイラを有効にします。
- mainparts.html の head タグに th:fragment を追記します。
- head-cssjs.html の内容を mainparts.html の head タグ内にコピーし、head-cssjs.html は削除します。
- mainparts.html の head タグ内の meta, link タグの末尾の “/” は削除します。
- Fragment Expressions, The No-Operation token の機能を利用します。各画面用 Thymeleaf テンプレートの head タグを mainparts.html の head タグを使用するように変更し、各画面用 Thymeleaf テンプレートには画面固有のタグのみ記述して、そのタグが mainparts.html の head タグに反映されるようにします。
- LibraryHelper#getSelectedLibrary では図書館が選択されている時だけ “選択中:…” の文字を返すようにし、"※図書館が選択されていません" の文字列は mainparts.html の head タグ内で The No-Operation token の機能を利用して表示するようにします。
- 全ての各画面用 Thymeleaf テンプレートに
<!--/* @thymesVar id="..." type="..." */-->
が記述されていないので、form タグの上に追加します。
build.gradle を変更する
build.gradle を リンク先のその1の内容 に変更します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
更新後、Project Tool Window の External Libraries を見て、ライブラリが更新されていることを確認します。
BOM に記述されたライブラリのバージョンを変更する方法として今回は Dependency management plugin が提供する bomProperty を使用しましたが、それ以外に build.gradle に ext で記述する方法や、gradle.properties を使用する方法があります。詳細は以下の Web ページに記述されています。
- 5.2 Overriding a version using Gradle
http://docs.spring.io/platform/docs/current/reference/html/getting-started-overriding-versions.html#getting-started-overriding-versions-gradle
SpELコンパイラを有効にします
mainparts.html の head タグを th:fragment 化し、head-cssjs.html を削除する(その1)
src/main/resources/templates/common/mainparts.html を リンク先の内容 に変更します。
各画面用 Thymeleaf テンプレートを1ファイルだけ変更します。src/main/resources/templates/admin/library/library.html を リンク先の内容 に変更します。
ここで一旦動作確認する
ここまでの変更で「検索対象図書館登録」画面は表示できるようになっているはずなので、確認してみます。
library_forsearch テーブルをクリアしてから bootRun で Tomcat を起動した後、http://localhost:8080/ にアクセスして tanaka.taro@sample.com / taro でログインすると「検索対象図書館登録」画面が表示されました。
HTML の <head> ... </head>
タグは以下のように出力されており想定通りです。
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>検索対象図書館登録</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.4 --> <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"> <!-- Font Awesome Icons --> <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <!-- Ionicons --> <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"> <!-- Theme style --> <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"> <!-- AdminLTE Skins. Choose a skin from the css/skins folder instead of downloading all of them to reduce the load. --> <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="/js/html5shiv.min.js"></script> <script src="/js/respond.min.js"></script> <![endif]--> <!-- ここに各htmlで定義された link タグが追加される --> <style type="text/css"> <!-- .jp-gothic { font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif; } .content-wrapper { background-color: #fffafa; } .noselected-library { color: #ff8679 !important; font-size: 100%; font-weight: 700; } .selected-library { color: #ffffff !important; font-size: 100%; font-weight: 700; } .table>tbody>tr>td , .table>tbody>tr>th , .table>tfoot>tr>td , .table>tfoot>tr>th , .table>thead>tr>td , .table>thead>tr>th { padding: 5px; font-size: 90%; } --> </style> <!-- ここに各htmlで定義された style タグが追加される --> </head>
Tomcat を停止します。
次に clean タスク → Rebuild Project → build タスクを実行してみます。
build タスクでいくつかのテストが失敗しました。
Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」も実行してみると、失敗しているテストで org.xml.sax.SAXParseException; lineNumber: 63; columnNumber: 3; 要素タイプ"link"は、対応する終了タグ"</link>"で終了する必要があります。
というメッセージが出力されていることが分かります。
Thymeleaf 3 にバージョンアップしたので SAX は使われなくなったのでは?と思ってテストのコードを見てみると、xpath を使用してチェックしていました。これが SAX を使用しているようです。
xpath()
の代わりに使用できる HTML 5 チェック用の html()
ResultMatcher を作成する
Thymeleaf 3 に合わせて HTML を XML 形式にしないように変更すると xpath()
ResultMatcher が使用できなくなりました。HTML 5 をチェック可能な ResultMatcher を作成することにします。
Thymeleaf 3 ten-minute migration guide に書かれているリンク先の Fragment Expressions を見ると、Thymeleaf 3 で使用されている Parser が AttoParser というライブラリであることが分かります。
AttoParser のドキュメントや Thymeleaf の GitHub を見てみたのですが、ちょっと簡単に使えるライブラリではないように見えます。今回は jQuery と同じような CSS の指定で HTML の要素を取得することができる Jsoup を使用して ResultMatcher を作成することにします。
build.gradle を リンク先のその2の内容 に変更します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
src/test/java/ksbysample/common/test/matcher/HtmlResultMatchers.java を新規作成し、リンク先の内容 を記述します。
テストを作成します。src/groovy/test/ksbysample/common/test/matcher/HtmlResultMatchersTest.groovy を新規作成し、リンク先の内容 を記述します。
作成したテストを実行し、全てのテストが成功することを確認します。
失敗したテストを変更する
src/test/java/ksbysample/webapp/lending/web/admin/library/AdminLibraryControllerTest.java を リンク先の内容 に変更します。
テストを実行し、成功することを確認します。
続きます。。。
1度調査しているのですぐに終わると思っていましたが、以外なところでつまずきました。一旦ここで区切ります。
ソースコード
build.gradle
■その1
dependencyManagement { imports { mavenBom("io.spring.platform:platform-bom:Brussels-SR2") { bomProperty 'guava.version', '21.0' bomProperty 'thymeleaf.version', '3.0.6.RELEASE' bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE' bomProperty 'thymeleaf-layout-dialect.version', '2.2.2' bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1' bomProperty 'thymeleaf-extras-java8time.version', '3.0.0.RELEASE' } } }
- dependencyManagement に以下の記述を追加します。
bomProperty 'thymeleaf.version', '3.0.6.RELEASE'
bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE'
bomProperty 'thymeleaf-layout-dialect.version', '2.2.2'
bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1'
bomProperty 'thymeleaf-extras-java8time.version', '3.0.0.RELEASE'
■その2
dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されるもの // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照 .......... // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの .......... testCompile("com.jayway.jsonpath:json-path:2.2.0") testCompile("org.jsoup:jsoup:1.10.2") testCompile("cglib:cglib-nodep:3.2.5") testCompile("org.spockframework:spock-core:${spockVersion}") { .......... }
- dependencies に以下の2行を追加します。
cglib:cglib-nodep
は Spock で Mock を使用するために必要となるライブラリです。指定しないとテスト実行時にorg.spockframework.mock.CannotCreateMockException: Cannot create mock for class org.springframework.mock.web.MockHttpServletResponse. Mocking of non-interface types requires a code generation library. Please put byte-buddy-1.6.4 or cglib-nodep-3.2 or higher on the class path.
というメッセージが出ます。testCompile("org.jsoup:jsoup:1.10.2")
testCompile("cglib:cglib-nodep:3.2.5")
WebMvcConfig.java
package ksbysample.webapp.lending.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.thymeleaf.spring4.SpringTemplateEngine; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { /** * Thymeleaf 3 のパフォーマンスを向上させるために SpEL コンパイラを有効にする * * @param templateEngine {@link SpringTemplateEngine} オブジェクト */ @Autowired public void configureThymeleafSpringTemplateEngine(SpringTemplateEngine templateEngine) { templateEngine.setEnableSpringELCompiler(true); } }
common/mainparts.html
<head th:fragment="head(title, links, style)"> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title th:replace="${title} ?: _">検索対象図書館登録</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.4 --> <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"> <!-- Font Awesome Icons --> <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <!-- Ionicons --> <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"> <!-- Theme style --> <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"> <!-- AdminLTE Skins. Choose a skin from the css/skins folder instead of downloading all of them to reduce the load. --> <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="/js/html5shiv.min.js"></script> <script src="/js/respond.min.js"></script> <![endif]--> <!-- ここに各htmlで定義された link タグが追加される --> <th:block th:replace="${links} ?: _"/> <style type="text/css"> <!-- .jp-gothic { font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif; } .content-wrapper { background-color: #fffafa; } .noselected-library { color: #ff8679 !important; font-size: 100%; font-weight: 700; } .selected-library { color: #ffffff !important; font-size: 100%; font-weight: 700; } .table>tbody>tr>td , .table>tbody>tr>th , .table>tfoot>tr>td , .table>tfoot>tr>th , .table>thead>tr>td , .table>thead>tr>th { padding: 5px; font-size: 90%; } --> </style> <!-- ここに各htmlで定義された style タグが追加される --> <th:block th:replace="${style} ?: _"/> </head>
- head タグに
th:fragment="head(title, links, style)"
を追加します。 - title タグに
th:replace="${title} ?: _"
を追加します。 <!-- Bootstrap 3.3.4 -->
から</head>
の前までの部分に src/main/resources/templates/common/head-cssjs.html の内容をコピーします。<th:block th:replace="${links} ?: _"/>
を追加します。<th:block th:replace="${style} ?: _"/>
を追加します。- meta, link タグの末尾の “/” を削除します。
admin/library/library.html
<head th:replace="~{common/mainparts :: head(~{::title}, ~{::link}, ~{::style})}"> <title>検索対象図書館登録</title> </head>
- head タグに
th:replace="~{common/mainparts :: head(~{::title}, ~{::link}, ~{::style})}"
を追加します。 <head> ... </head>
の中には<title>検索対象図書館登録</title>
だけ記述します。
HtmlResultMatchers.java
package ksbysample.common.test.matcher; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import java.io.UnsupportedEncodingException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.springframework.test.util.AssertionErrors.assertTrue; /** * Jsoup ( https://jsoup.org/ ) を利用して HTML のチェックをする * {@link org.springframework.test.web.servlet.ResultActions#andExpect(ResultMatcher)} で使用するためのクラス * <p> * <pre>{@code * mockMvc.perform(get("/")) * .andExpect(status().isOk()) * .andExpect(html("#id").text("ここに期待する文字列を記載する")); * .andExpect(html("div > div:eq(1) > a:eq(1)").count(1)); * .andExpect(html(".css-selector").exists()); * }</pre> */ public class HtmlResultMatchers { private String cssQuery; public HtmlResultMatchers(String cssQuery) { this.cssQuery = cssQuery; } /** * {@link HtmlResultMatchers} オブジェクト生成用の static メソッド * * @param cssQuery {@link org.jsoup.nodes.Element#select(String)} に指定する CSS セレクタ * @return {@link HtmlResultMatchers} */ public static HtmlResultMatchers html(String cssQuery) { return new HtmlResultMatchers(cssQuery); } /** * HTML 内の cssQuery で指定された部分の文字列を抽出し、引数で渡された文字列と同じかチェックする * * @param expectedText 文字列の期待値 * @return {@link ResultMatcher} */ public ResultMatcher text(final String expectedText) { return mvcResult -> assertThat(selectFirst(mvcResult).text(), is(expectedText)); } /** * HTML 内の cssQuery で指定された要素数を取得し、引数で渡された数と同じかチェックする * * @param expectedCount 要素数の期待値 * @return {@link ResultMatcher} */ public ResultMatcher count(final int expectedCount) { return mvcResult -> assertThat(select(mvcResult).size(), is(expectedCount)); } /** * HTML 内の cssQuery で指定された要素が存在するかチェックする * * @return {@link ResultMatcher} */ public ResultMatcher exists() { return mvcResult -> assertTrue("cssQuery '" + this.cssQuery + "' does not exist" , select(mvcResult).size() != 0); } /** * HTML 内の cssQuery で指定された要素が存在しないかチェックする * * @return {@link ResultMatcher} */ public ResultMatcher notExists() { return mvcResult -> assertTrue("cssQuery '" + this.cssQuery + "' exists" , select(mvcResult).size() == 0); } private Document parseHtml(MvcResult mvcResult) throws UnsupportedEncodingException { return Jsoup.parse(mvcResult.getResponse().getContentAsString()); } private Elements select(MvcResult mvcResult) throws UnsupportedEncodingException { return parseHtml(mvcResult).select(this.cssQuery); } private Element selectFirst(MvcResult mvcResult) throws UnsupportedEncodingException { Element element = select(mvcResult).first(); if (element == null) { throw new AssertionError( String.format("指定された cssQuery の Element は存在しません ( cssQuery = %s )", this.cssQuery)); } return element; } }
HtmlResultMatchersTest.groovy
package ksbysample.common.test.matcher import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse import org.springframework.test.web.servlet.DefaultMvcResult import spock.lang.Specification import spock.lang.Unroll class HtmlResultMatchersTest extends Specification { def TEST_HTML = '''\ <html> <body> <div id="title">メニュー一覧</div> <div> <ul class="menu"> <li class="item">メニュー1</li> <li class="item">メニュー2</li> </ul> </div> </body> </html> ''' def mvcResult def setup() { def mockRequest = new MockHttpServletRequest() def mockResponse = Mock(MockHttpServletResponse) mockResponse.getContentAsString() >> TEST_HTML this.mvcResult = new DefaultMvcResult(mockRequest, mockResponse) } @Unroll def "html(#cssQuery).text(#text)_要素がありテキストが一致する場合"() { expect: def htmlResultMatchers = HtmlResultMatchers.html(cssQuery) def resultMatcher = htmlResultMatchers.text(text) resultMatcher.match(this.mvcResult) where: cssQuery || text "#title" || "メニュー一覧" "ul.menu li.item:eq(0)" || "メニュー1" } def "html().text()_要素はあるがテキストが一致しない場合 AssertionError が throw される"() { when: def htmlResultMatchers = HtmlResultMatchers.html("#title") def resultMatcher = htmlResultMatchers.text("メニュー") resultMatcher.match(this.mvcResult) then: AssertionError e = thrown() e.getMessage() contains "" } def "html().text()_要素がない場合 AssertionError が throw される"() { when: def htmlResultMatchers = HtmlResultMatchers.html("#titlex") def resultMatcher = htmlResultMatchers.text("メニュー一覧") resultMatcher.match(this.mvcResult) then: AssertionError e = thrown() e.getMessage() contains "指定された cssQuery の Element は存在しません ( cssQuery = #titlex )" } @Unroll def "html(#cssQuery).count(#count)"() { expect: def htmlResultMatchers = HtmlResultMatchers.html(cssQuery) def resultMatcher = htmlResultMatchers.count(count) resultMatcher.match(this.mvcResult) where: cssQuery || count "#title" || 1 "#titlex" || 0 "ul.menu li" || 2 } def "html().exists()_要素がある場合 AssertionError は throw されない"() { when: def htmlResultMatchers = HtmlResultMatchers.html("#title") def resultMatcher = htmlResultMatchers.exists() resultMatcher.match(this.mvcResult) then: notThrown(AssertionError) } def "html().exists()_要素がない場合 AssertionError が throw される"() { when: def htmlResultMatchers = HtmlResultMatchers.html("#titlex") def resultMatcher = htmlResultMatchers.exists() resultMatcher.match(this.mvcResult) then: AssertionError e = thrown() e.getMessage() contains "cssQuery '#titlex' does not exist" } def "html().notExists()_要素がない場合 AssertionError は throw されない"() { when: def htmlResultMatchers = HtmlResultMatchers.html("#titlex") def resultMatcher = htmlResultMatchers.notExists() resultMatcher.match(this.mvcResult) then: notThrown(AssertionError) } def "html().notExists()_要素がある場合 AssertionError は throw される"() { when: def htmlResultMatchers = HtmlResultMatchers.html("#title") def resultMatcher = htmlResultMatchers.notExists() resultMatcher.match(this.mvcResult) then: AssertionError e = thrown() e.getMessage() contains "cssQuery '#title' exists" } }
AdminLibraryControllerTest.java
@Test public void 管理権限を持つユーザは検索対象図書館登録画面を表示できる_図書館未選択時() throws Exception { mvc.authTanakaTaro.perform(get("/admin/library")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(model().hasNoErrors()) .andExpect(html("p.navbar-text.noselected-library").text("※図書館が選択されていません")); } @Test @TestData("web/admin/library/testdata/001") public void 管理権限を持つユーザは検索対象図書館登録画面を表示できる_図書館選択時() throws Exception { mvc.authTanakaTaro.perform(get("/admin/library")) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(model().hasNoErrors()) .andExpect(html("p.navbar-text.selected-library").text("選択中:図書館サンプル")); }
管理権限を持つユーザは検索対象図書館登録画面を表示できる_図書館未選択時()
テストメソッドの.andExpect(xpath("//p[@class='navbar-text noselected-library']").string("※図書館が選択されていません"));
→.andExpect(html("p.navbar-text.noselected-library").text("※図書館が選択されていません"));
に変更します。管理権限を持つユーザは検索対象図書館登録画面を表示できる_図書館選択時()
テストメソッドの.andExpect(xpath("//p[@class='navbar-text selected-library']").string("選択中:図書館サンプル"));
→.andExpect(html("p.navbar-text.selected-library").text("選択中:図書館サンプル"));
に変更します。
履歴
2017/05/23
初版発行。
IntelliJ IDEA を 2017.1.2 → 2017.1.3 へ、Git for Windows を 2.12.2(2) → 2.13.0 へバージョンアップ
IntelliJ IDEA を 2017.1.2 → 2017.1.3 へバージョンアップする
IntelliJ IDEA の 2017.1.3 がリリースされたのでバージョンアップします。
- IntelliJ IDEA 2017.1.3 Release Notes
https://confluence.jetbrains.com/display/IDEADEV/IntelliJ+IDEA+2017.1.3+Release+Notes
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。
「Platform and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。
Plugin の update も表示されたので、チェックしたまま「Update and Restart」ボタンをクリックします。
Patch がダウンロードされて IntelliJ IDEA が再起動します。
メイン画面が表示されます。IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
処理が終了すると Gradle Tool Window のツリーの表示が other グループしかない初期の状態に戻っていますので、左上の「Refresh all Gradle projects」ボタンをクリックして更新します。更新が完了すると build グループ等が表示されます。
IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2017.1.3 へバージョンアップされていることを確認します。
clean タスク実行 → Rebuild Project 実行すると “Can’t force a new processing round. Lombok won’t work.” という Warning が表示されました。
今回 Error-prone の Plugin を Update したので、おそらくその影響でしょう。Error-prone の Plugin を1つ前のバージョンに戻すことにします。
https://plugins.jetbrains.com/plugin/7349-error-prone-compiler-integration にアクセスし VERSION 162.1120 のファイル error-prone-162.1120.zip をダウンロードします。
ダウンロード後 IntelliJ IDEA のメインメニューから「File」-「Settings…」を選択して「Settings」ダイアログを開き、「Plugins」画面の「Install plugin from disk…」ボタンを押してインストールします。
再び clean タスク → Rebuild Project を実行すると今度は正常に終了しました。build タスクを実行して “BUILD SUCCESSFUL” のメッセージが表示されることも確認できました。
Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。
Git for Windows を 2.12.2(2) → 2.13.0 へバージョンアップする
Git for Windows の 2.13.0 がリリースされていたのでバージョンアップします。
https://git-for-windows.github.io/ の「Download」ボタンをクリックして Git-2.13.0-64-bit.exe をダウンロードします。
Git-2.13.0-64-bit.exe を実行します。
「Git 2.13.0 Setup」ダイアログが表示されます。[Next >]ボタンをクリックします。
「Select Components」画面が表示されます。「Git LFS(Large File Support)」だけチェックした状態で [Next >]ボタンをクリックします。
「Adjusting your PATH environment」画面が表示されます。中央の「Use Git from the Windows Command Prompt」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Configuring the line ending conversions」画面が表示されます。「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Install]ボタンをクリックします。
インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View Release Notes」のチェックを外した後、「Finish」ボタンをクリックしてインストーラーを終了します。
コマンドプロンプトを起動して git のバージョンが
git version 2.13.0.windows.1
になっていることを確認します。git-cmd.exe を起動して日本語の表示・入力が問題ないかを確認します。
特に問題はないようですので、2.13.0 で作業を進めたいと思います。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その4 )( 1.4系 → 1.5系で変更された点を修正する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- Release Notes を見て 1.4系 → 1.5系で変更された点を修正します。
参照したサイト・書籍
Spring Boot 1.5 Release Notes
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.5-Release-NotesSpring Boot 1.5の主な変更点
http://qiita.com/kazuki43zoo/items/c26d99bb57888e42eaf1
目次
手順
Devtools excluded by default
spring-boot-devtools
がデフォルトで fat jar から除外されるようになりました。
1.4 系の時のソースに切り戻した後、build.gradle の bootRepackage の中の excludeDevtools = true
をコメントアウトして jar ファイルを生成してみます。
bootRepackage { mainClass = 'ksbysample.webapp.lending.Application' // excludeDevtools = true }
生成された ksbysample-webapp-lending-1.4.6-RELEASE.jar を解凍すると、BOOT-INF\lib の下に spring-boot-devtools-1.4.6.RELEASE.jar が入っています。
excludeDevtools = true
のコメントアウトを元に戻すと、spring-boot-devtools-1.4.6.RELEASE.jar は入りません。
feature/130-issue ブランチに戻して build.gradle から excludeDevtools = true
を削除して jar ファイルを生成してみます。
bootRepackage {
mainClass = 'ksbysample.webapp.lending.Application'
}
生成された ksbysample-webapp-lending-1.5.3-RELEASE.jar を解凍すると spring-boot-devtools-1.5.3.RELEASE.jar は入っていませんでした。
次回は。。。
1.5 では特にソースの変更が必要なところはありませんでした。次回は Thymeleaf を 3系へバージョンアップします。
ソースコード
履歴
2017/05/20
初版発行。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その3 )( Run 'All Tests' with Coverage 実行時に出るエラーを解消する )
概要
記事一覧はこちらです。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その1 )( 概要 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- Run ‘All Tests’ with Coverage 実行時に出るエラーの解消
参照したサイト・書籍
目次
手順
java.lang.IllegalStateException: Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
の下に出力されるメッセージの内、Caused by:
から始まるエラーメッセージは以下のものでした。
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webSecurityConfig': Unsatisfied dependency expressed through method 'setContentNegotationStrategy' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Found unexpected validator configuration. A Spring Boot MVC validator should be registered as bean named 'mvcValidator' and not returned from WebMvcConfigurer.getValidator()
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Found unexpected validator configuration. A Spring Boot MVC validator should be registered as bean named 'mvcValidator' and not returned from WebMvcConfigurer.getValidator()
Caused by: java.lang.IllegalStateException: Found unexpected validator configuration. A Spring Boot MVC validator should be registered as bean named 'mvcValidator' and not returned from WebMvcConfigurer.getValidator()
Spring Boot MVC validator は mvcValidator
という名前の Bean で登録されているべきなのに、WebMvcConfigurer.getValidator()
が返さない、ということのようです。
例外に出力されているソースを追ってみると、org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration#afterPropertiesSet でこのメッセージを出力していることが確認できます。getValidator() == null
でないと Assert に引っかかります。
getValidator()
は org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration#getValidator のことで、以下のコードです。
configurers.getValidator()
は org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite#getValidator のことで、以下のコードです。このメソッドが null を返さないのでエラーになるようですね。
デバッガで確認してみると、確かに null ではなく LocalValidatorFactoryBean を返しています。
自分で実装したコードを見ると、確かに Validator インターフェースの Bean は mvcValidator
ではなく validator
という名前にしています。
そして validator Bean を ksbysample.webapp.lending.config.WebMvcConfig#getValidator で返しています。
Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その20 )( 気になった点を修正する ) の記事で ValidationMessages_ja_JP.properties をやめて messages.properties に1本化するために書いたものでした。
ここまでの調査から対応方法を考えると、以下のようにすれば良い気がします。
- ksbysample.webapp.lending.config.WebMvcConfig#getValidator は null を返さないといけないようなので、ksbysample.webapp.lending.config.WebMvcConfig 自体は削除する。
- validator Bean ではなく mvcValidator Bean にする。
“mvcValidator” で検索してみると org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration#mvcValidator に mvcValidator Bean を取得して mvcValidator Bean を生成する?、というよく分からないコードがありました。
Spring Boot 1.4.6 を使用している時のコードに戻してみると、org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration#mvcValidator は 1.5.3 と同じでした。
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration#afterPropertiesSet は存在しませんでした。
おそらく 1.4 の頃から mvcValidator という名前の Bean として登録しておけばよいだけで、WebMvcConfigurerAdapter の継承クラスで getValidator メソッドを Override する必要はなかった、のではないかと思います。
上に書いた2点の対応方法で良さそうなので、コードを修正します。
まず src/main/java/ksbysample/webapp/lending/config/WebMvcConfig.java を削除します。
次に src/main/java/ksbysample/webapp/lending/config/ApplicationConfig.java を リンク先の内容 に変更します。
最後に以下のクラスのフィールド private final Validator validator;
→ private final Validator mvcValidator;
に変更します。
- src/main/java/ksbysample/webapp/lending/web/springmvcmemo/BeanValidationGroupController.java
- src/test/java/ksbysample/webapp/lending/values/validation/ValuesEnumValidatorTest.java
コードを修正したら clean タスク → Rebuild Project → build タスクを実行してみます。今度は “BUILD SUCCESSFUL” が表示されて成功しました。
Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」も実行してみます。こちらも無事全てのテストが成功しました。
Hibernate Validator を使用しているところで messages.properties に記述した日本語のメッセージが使用されるか確認します。
bootRun で Tomcat を起動した後、Hibernate Validator の @NotBlank
アノテーションで入力チェックをしている
http://localhost:8080/springMvcMemo/beanValidationGroup にアクセスします。
「データ更新」ボタンをクリックすると、日本語のメッセージ「必須の入力項目です。」が表示されました。
mvcValidator Bean をコメントアウトしてから Tomcat を再起動して同じ操作をしてみると、
今度は英語のメッセージが表示されました。mvcValidator Bean を定義するだけで messages.properties の日本語メッセージが使用されるようになるようです。
次回は。。。
1.5 系ではこう書くべきという点があるか確認し、変更した方がよいところを修正します。
ソースコード
ApplicationConfig.java
@Bean public Validator mvcValidator() { LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); localValidatorFactoryBean.setValidationMessageSource(this.messageSource); return localValidatorFactoryBean; }
- メソッド名を
validator
→mvcValidator
に変更します。
履歴
2017/05/17
初版発行。
2017/05/20
* http://localhost:8080/springMvcMemo/beanValidationGroup での動作確認を追加しました。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その2 )( build.gradle の修正 )
概要
記事一覧はこちらです。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その1 )( 概要 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- build.gradle の修正
参照したサイト・書籍
目次
手順
1.5.x ブランチの作成
- master から 1.5.x ブランチを、1.5.x から feature/130-issue ブランチを作成します。
Spring Initializr で 1.5.3 のプロジェクトを作成する
IntelliJ IDEA のメインメニューから「File」-「Close Project」を選択して「Welcome to IntelliJ IDEA」ダイアログに戻ります。
「Welcome to IntelliJ IDEA」ダイアログで「Create New Project」をクリックします。
「New Project」ダイアログが表示されます。画面左側のリストから「Spring Initializr」を選択した後、「Next」ボタンをクリックします。
次の画面が表示されます。「Type」で「Gradle Project」を選択した後、「Next」ボタンをクリックします。
次の画面が表示されます。画面中央上の「Spring Boot」で「1.5.3」を選択してから ksbysample-webapp-lending プロジェクトで使用している以下の項目をチェックした後、「Next」ボタンをクリックします。
次の画面が表示されます。「Project location」を “C:\project-springboot\demo” に変更した後、「Finish」ボタンをクリックします。
「Import Module from Gradle」ダイアログが表示されます。「Create directories for empty content roots automatically」をチェックした後、「OK」ボタンをクリックします。
これでプロジェクトが作成されて以下の build.gradle が作成されました。
buildscript { ext { springBootVersion = '1.5.3.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-data-redis') compile('org.springframework.boot:spring-boot-starter-freemarker') compile('org.springframework.boot:spring-boot-starter-mail') compile('org.springframework.boot:spring-boot-starter-security') compile('org.springframework.session:spring-session') compile('org.springframework.boot:spring-boot-starter-thymeleaf') compile('org.springframework.boot:spring-boot-starter-web') runtime('org.springframework.boot:spring-boot-devtools') compileOnly('org.projectlombok:lombok') testCompile('org.springframework.boot:spring-boot-starter-test') }
1.4.4 で生成した時と異なる点は特にありませんでした。今回は何も反映しません。
build.gradle を修正して build してみる
build.gradle を リンク先の内容 に変更します。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。今回は正常に更新できました。
clean タスク実行 → Rebuild Project 実行 を実行します。こちらも正常に終了しました。
build タスクを実行します。が、大量の
java.lang.IllegalStateException
が発生してテストが全く終わりませんでした。。。Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」も実行してみます。
こちらも
java.lang.IllegalStateException: Failed to load ApplicationContext
というエラーメッセージが出てテストは失敗しました。ほとんどのテストが失敗していたので、途中で中断しています。
次回は。。。
Run ‘All Tests’ with Coverage 実行時に出る java.lang.IllegalStateException: Failed to load ApplicationContext
のエラーの解消→ build タスクの再実行(エラーが出れば解消します)の順で進める予定です。
ソースコード
build.gradle
group 'ksbysample' version '1.5.3-RELEASE' buildscript { ext { springBootVersion = '1.5.3.RELEASE' } repositories { jcenter() maven { url "http://repo.spring.io/repo/" } maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") // for Error Prone ( http://errorprone.info/ ) classpath("net.ltgt.gradle:gradle-errorprone-plugin:0.0.10") // for Grgit classpath("org.ajoberstar:grgit:1.9.2") // Gradle Download Task classpath("de.undercouch:gradle-download-task:3.2.0") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'org.springframework.boot' apply plugin: 'de.undercouch.download' apply plugin: 'groovy' apply plugin: 'net.ltgt.errorprone' apply plugin: 'checkstyle' apply plugin: 'findbugs' sourceCompatibility = 1.8 targetCompatibility = 1.8 task wrapper(type: Wrapper) { gradleVersion = '2.13' } [compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options,-processing,-path'] compileJava.options.compilerArgs += ['-Xep:RemoveUnusedImports:WARN'] // for Doma 2 // JavaクラスとSQLファイルの出力先ディレクトリを同じにする processResources.destinationDir = compileJava.destinationDir // コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する compileJava.dependsOn processResources idea { module { inheritOutputDirs = false outputDir = file("$buildDir/classes/main/") } } configurations { // for Doma 2 domaGenRuntime } checkstyle { configFile = file("${rootProject.projectDir}/config/checkstyle/google_checks.xml") toolVersion = '7.7' sourceSets = [project.sourceSets.main] } findbugs { toolVersion = '3.0.1' sourceSets = [project.sourceSets.main] ignoreFailures = true effort = "max" excludeFilter = file("${rootProject.projectDir}/config/findbugs/findbugs-exclude.xml") } tasks.withType(FindBugs) { reports { xml.enabled = false html.enabled = true } } repositories { jcenter() } dependencyManagement { imports { mavenBom("io.spring.platform:platform-bom:Brussels-SR2") { bomProperty 'guava.version', '21.0' } } } bootRepackage { mainClass = 'ksbysample.webapp.lending.Application' excludeDevtools = true } dependencies { def jdbcDriver = "org.postgresql:postgresql:42.0.0" def spockVersion = "1.1-groovy-2.4" def domaVersion = "2.16.0" def lombokVersion = "1.16.16" def errorproneVersion = "2.0.15" // dependency-management-plugin によりバージョン番号が自動で設定されるもの // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照 compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4") compile("org.thymeleaf.extras:thymeleaf-extras-java8time") compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("org.springframework.boot:spring-boot-starter-freemarker") compile("org.springframework.boot:spring-boot-starter-mail") compile("org.springframework.boot:spring-boot-starter-security") compile("org.springframework.boot:spring-boot-starter-data-redis") compile("org.springframework.boot:spring-boot-starter-amqp") compile("org.springframework.boot:spring-boot-devtools") compile("org.springframework.session:spring-session") compile("org.springframework.retry:spring-retry") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml") compile("com.google.guava:guava") compile("org.apache.commons:commons-lang3") compile("org.codehaus.janino:janino") testCompile("org.springframework.boot:spring-boot-starter-test") testCompile("org.springframework.security:spring-security-test") testCompile("org.yaml:snakeyaml") testCompile("org.mockito:mockito-core") // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの runtime("${jdbcDriver}") compile("com.integralblue:log4jdbc-spring-boot-starter:1.0.1") compile("org.simpleframework:simple-xml:2.7.1") compile("com.univocity:univocity-parsers:2.4.1") testCompile("org.dbunit:dbunit:2.5.3") testCompile("com.icegreen:greenmail:1.5.4") testCompile("org.assertj:assertj-core:3.7.0") testCompile("com.jayway.jsonpath:json-path:2.2.0") testCompile("org.spockframework:spock-core:${spockVersion}") { exclude module: "groovy-all" } testCompile("org.spockframework:spock-spring:${spockVersion}") { exclude module: "groovy-all" } testCompile("com.google.code.findbugs:jsr305:3.0.2") // for lombok compileOnly("org.projectlombok:lombok:${lombokVersion}") testCompileOnly("org.projectlombok:lombok:${lombokVersion}") // for Doma compile("org.seasar.doma:doma:${domaVersion}") domaGenRuntime("org.seasar.doma:doma-gen:${domaVersion}") domaGenRuntime("${jdbcDriver}") // for Error Prone ( http://errorprone.info/ ) errorprone("com.google.errorprone:error_prone_core:${errorproneVersion}") compileOnly("com.google.errorprone:error_prone_annotations:${errorproneVersion}") } bootRun { jvmArgs = ['-Dspring.profiles.active=develop'] } test { jvmArgs = ['-Dspring.profiles.active=unittest'] } // for Doma-Gen task domaGen << { // まず変更が必要なもの def rootPackageName = 'ksbysample.webapp.lending' def daoPackagePath = 'src/main/java/ksbysample/webapp/lending/dao' def dbUrl = 'jdbc:postgresql://localhost/ksbylending' def dbUser = 'ksbylending_user' def dbPassword = 'xxxxxxxx' def tableNamePattern = '.*' // おそらく変更不要なもの def importOfComponentAndAutowiredDomaConfig = "${rootPackageName}.util.doma.ComponentAndAutowiredDomaConfig" def workDirPath = 'work' def workDaoDirPath = "${workDirPath}/dao" // 作業用ディレクトリを削除する clearDir("${workDirPath}") // 現在の Dao インターフェースのバックアップを取得する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/org" } // Dao インターフェース、Entity クラスを生成する ant.taskdef(resource: 'domagentask.properties', classpath: configurations.domaGenRuntime.asPath) ant.gen(url: "${dbUrl}", user: "${dbUser}", password: "${dbPassword}", tableNamePattern: "${tableNamePattern}") { entityConfig(packageName: "${rootPackageName}.entity", useListener: false) daoConfig(packageName: "${rootPackageName}.dao") sqlConfig() } // 生成された Dao インターフェースを作業用ディレクトリにコピーし、 // @ComponentAndAutowiredDomaConfig アノテーションを付加する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/replace" filter { line -> line.replaceAll('import org.seasar.doma.Dao;', "import ${importOfComponentAndAutowiredDomaConfig};\nimport org.seasar.doma.Dao;") .replaceAll('@Dao', '@Dao\n@ComponentAndAutowiredDomaConfig') } } // @ComponentAndAutowiredDomaConfig アノテーションを付加した Dao インターフェースを // dao パッケージへ戻す copy() { from "${workDaoDirPath}/replace" into "${daoPackagePath}" } // 元々 dao パッケージ内にあったファイルを元に戻す copy() { from "${workDaoDirPath}/org" into "${daoPackagePath}" } // 作業用ディレクトリを削除する clearDir("${workDirPath}") // 自動生成したファイルを git add する addGit() } task downloadCssFontsJs << { def staticDirPath = 'src/main/resources/static' def workDirPath = 'work' def adminLTEVersion = '2.2.0' def jQueryVersion = '2.1.4' def fontAwesomeVersion = '4.3.0' def ioniconsVersion = '2.0.1' def html5shivJsVersion = '3.7.2' def respondMinJsVersion = '1.4.2' // 作業用ディレクトリを削除する clearDir("${workDirPath}") // Bootstrap & AdminLTE Dashboard & Control Panel Template downloadAdminLTE("${adminLTEVersion}", "${jQueryVersion}", "${workDirPath}", "${staticDirPath}") // Font Awesome Icons downloadFontAwesome("${fontAwesomeVersion}", "${workDirPath}", "${staticDirPath}") // Ionicons downloadIonicons("${ioniconsVersion}", "${workDirPath}", "${staticDirPath}") // html5shiv.js downloadHtml5shivJs("${html5shivJsVersion}", "${workDirPath}", "${staticDirPath}") // respond.min.js downloadRespondMinJs("${respondMinJsVersion}", "${workDirPath}", "${staticDirPath}") // fileinput.min.js ( v4.2.7 ) downloadBootstrapFileInputMinJs("${workDirPath}", "${staticDirPath}") // 作業用ディレクトリを削除する clearDir("${workDirPath}") // 追加したファイルを git add する addGit() } task printClassWhatNotMakeTest << { def srcDir = new File("src/main/java") def excludePaths = [ "src/main/java/ksbysample/webapp/lending/Application.java" , "src/main/java/ksbysample/webapp/lending/config" , "src/main/java/ksbysample/webapp/lending/cookie" , "src/main/java/ksbysample/webapp/lending/dao" , "src/main/java/ksbysample/webapp/lending/entity" , "src/main/java/ksbysample/webapp/lending/exception" , "src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv" , "src/main/java/ksbysample/webapp/lending/helper/download/DataDownloadHelper.java" , "src/main/java/ksbysample/webapp/lending/helper/page/PagenationHelper.java" , "src/main/java/ksbysample/webapp/lending/security/LendingUser.java" , "src/main/java/ksbysample/webapp/lending/security/RoleAwareAuthenticationSuccessHandler.java" , "src/main/java/ksbysample/webapp/lending/service/calilapi/response" , "src/main/java/ksbysample/webapp/lending/service/file/BooklistCSVRecord.java" , "src/main/java/ksbysample/webapp/lending/service/openweathermapapi" , "src/main/java/ksbysample/webapp/lending/service/queue/InquiringStatusOfBookQueueMessage.java" , "src/main/java/ksbysample/webapp/lending/util/doma" , "src/main/java/ksbysample/webapp/lending/util/velocity/VelocityUtils.java" , "src/main/java/ksbysample/webapp/lending/values/validation/ValuesEnum.java" , "src/main/java/ksbysample/webapp/lending/view/BookListCsvView.java" , "src/main/java/ksbysample/webapp/lending/web/.+/.+Service.java" , "src/main/java/ksbysample/webapp/lending/webapi/common/CommonWebApiResponse.java" , "src/main/java/ksbysample/webapp/lending/webapi/weather" ] def excludeFileNamePatterns = [ ".*EventListener.java" , ".*Dto.java" , ".*Form.java" , ".*Values.java" ] compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns) } /* ----------------------------------------------------------------------------- * メソッド定義部 ---------------------------------------------------------------------------- */ void clearDir(String dirPath) { delete dirPath } void addGit() { def grgit = org.ajoberstar.grgit.Grgit.open(dir: project.projectDir) grgit.add(patterns: ['.']) } void downloadAdminLTE(String adminLTEVersion, String jQueryVersion, String workDirPath, String staticDirPath) { download { src "https://codeload.github.com/almasaeed2010/AdminLTE/zip/v${adminLTEVersion}" dest new File("${workDirPath}/download/AdminLTE-${adminLTEVersion}.zip") } copy { from zipTree("${workDirPath}/download/AdminLTE-${adminLTEVersion}.zip") into "${workDirPath}/unzip" } copy { from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/css" into "${staticDirPath}/css" } copy { from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/fonts" into "${staticDirPath}/fonts" } copy { from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/js" into "${staticDirPath}/js" } copy { from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/dist/css" into "${staticDirPath}/css" } copy { from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/dist/js" into "${staticDirPath}/js" } copy { from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/plugins/jQuery/jQuery-${jQueryVersion}.min.js" into "${staticDirPath}/js" } delete "${staticDirPath}/js/pages" delete "${staticDirPath}/js/demo.js" } void downloadFontAwesome(String fontAwesomeVersion, String workDirPath, String staticDirPath) { download { src "http://fortawesome.github.io/Font-Awesome/assets/font-awesome-${fontAwesomeVersion}.zip" dest new File("${workDirPath}/download/font-awesome-${fontAwesomeVersion}.zip") } copy { from zipTree("${workDirPath}/download/font-awesome-${fontAwesomeVersion}.zip") into "${workDirPath}/unzip" } copy { from "${workDirPath}/unzip/font-awesome-${fontAwesomeVersion}/css/font-awesome.min.css" into "${staticDirPath}/css" } copy { from "${workDirPath}/unzip/font-awesome-${fontAwesomeVersion}/fonts" into "${staticDirPath}/fonts" } } void downloadIonicons(String ioniconsVersion, String workDirPath, String staticDirPath) { download { src "https://codeload.github.com/driftyco/ionicons/zip/v${ioniconsVersion}" dest new File("${workDirPath}/download/ionicons-${ioniconsVersion}.zip") } copy { from zipTree("${workDirPath}/download/ionicons-${ioniconsVersion}.zip") into "${workDirPath}/unzip" } copy { from "${workDirPath}/unzip/ionicons-${ioniconsVersion}/css/ionicons.min.css" into "${staticDirPath}/css" } copy { from "${workDirPath}/unzip/ionicons-${ioniconsVersion}/fonts" into "${staticDirPath}/fonts" } } void downloadHtml5shivJs(String html5shivJsVersion, String workDirPath, String staticDirPath) { download { src "https://oss.maxcdn.com/html5shiv/${html5shivJsVersion}/html5shiv.min.js" dest new File("${workDirPath}/download/html5shiv.min.js") } copy { from "${workDirPath}/download/html5shiv.min.js" into "${staticDirPath}/js" } } void downloadRespondMinJs(String respondMinJsVersion, String workDirPath, String staticDirPath) { download { src "https://oss.maxcdn.com/respond/${respondMinJsVersion}/respond.min.js" dest new File("${workDirPath}/download/respond.min.js") } copy { from "${workDirPath}/download/respond.min.js" into "${staticDirPath}/js" } } void downloadBootstrapFileInputMinJs(String workDirPath, String staticDirPath) { download { src "https://github.com/kartik-v/bootstrap-fileinput/zipball/master" dest new File("${workDirPath}/download/kartik-v-bootstrap-fileinput.zip") } copy { from zipTree("${workDirPath}/download/kartik-v-bootstrap-fileinput.zip") into "${workDirPath}/unzip" } copy { from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/js/fileinput.min.js" into "${staticDirPath}/js" } copy { from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/js/fileinput_locale_ja.js" into "${staticDirPath}/js" } copy { from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/css/fileinput.min.css" into "${staticDirPath}/css" } } def compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns) { def existFlg def testDirAndTestClassNameList = [ ["src/test/java", "Test.java"] , ["src/test/groovy", "Test.groovy"] ] for (srcFile in srcDir.listFiles()) { String srcFilePath = (srcFile.toPath() as String).replaceAll("\\\\", "/") existFlg = false for (exclude in excludePaths) { if (srcFilePath =~ /^${exclude as String}/) { existFlg = true break } } if (existFlg == true) continue for (exclude in excludeFileNamePatterns) { if (srcFilePath =~ /${exclude as String}/) { existFlg = true break } } if (existFlg == true) continue if (srcFile.isDirectory()) { compareSrcAndTestDir(srcFile, excludePaths, excludeFileNamePatterns) } else { def nextFlg = false for (testDirAndTestClassName in testDirAndTestClassNameList) { def testDir = testDirAndTestClassName[0] def testClassName = testDirAndTestClassName[1] String testFilePath = srcFilePath.replaceFirst(/^src\/main\/java/, testDir) .replaceFirst(/\.java$/, testClassName) def testFile = new File(testFilePath) if (testFile.exists()) { nextFlg = true break } } if (nextFlg) continue println(srcFilePath) } } }
- Spring Boot のバージョンアップ対応として以下の点を変更します。
version '1.4.6-RELEASE'
→version '1.5.3-RELEASE'
に変更します。- buildscript の以下の点を変更します。
springBootVersion = '1.4.6.RELEASE'
→springBootVersion = '1.5.3.RELEASE'
に変更します。
- dependencyManagement の以下の点を変更します。
mavenBom("io.spring.platform:platform-bom:Athens-SR5")
→mavenBom("io.spring.platform:platform-bom:Athens-SR5")
に変更します。
- ライブラリを最新バージョンにアップデートするために以下の点を変更します。
def spockVersion = "1.1-groovy-2.4-rc-4"
→def spockVersion = "1.1-groovy-2.4"
に変更します。testCompile("org.assertj:assertj-core:3.6.2")
→testCompile("org.assertj:assertj-core:3.7.0")
に変更します。
履歴
2017/05/14
初版発行。
Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( その1 )( 概要 )
概要
記事一覧はこちらです。
- 「Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る」で作成した Web アプリケーション ( ksbysample-webapp-lending ) の Spring Boot のバージョンを 1.4.6 → 1.5.3 へバージョンアップします。
- 進め方は以下の方針とします。
- Git のブランチは 1.5.x を作成して、そちらで作業します。Spring Boot のバージョンと合わせます。
- 最初に build.gradle を修正します。
- Spring Boot のバージョン番号を 1.5.3 に、Spring IO Platform の BOM を Brussels-SR2 にします。
- Spring Initializr で 1.5.3 のプロジェクトを作成して、修正した方がよさそうな点があれば反映します。
- ライブラリは最新バージョンにアップデートします。
- プロジェクトを build し直してエラーが出る点があれば修正し、まずはここまでで動くようにします。
- その後で 1.5 系ではこう書くべきという点があるか確認し、変更した方がよいところを修正します。
1.5 のリリースノートはこちらです。
Spring Boot 1.5 Release Notes
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.5-Release-Notes
履歴
2017/05/13
初版発行。