Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その18 )( 図書館一覧取得 WebAPI の作成2 )
概要
Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その17 )( 図書館一覧取得 WebAPI の作成 ) の続きです。
参照したサイト・書籍
Spring Framework 4.1 -- Spring MVC Improvements
https://spring.io/blog/2014/07/28/spring-framework-4-1-spring-mvc-improvementsSpring Rest and Jsonp
http://stackoverflow.com/questions/26974835/spring-rest-and-jsonp- こちらも JSONP 対応に関する Q&A です。
HowToDoInJava - Spring beans autowiring concepts
http://howtodoinjava.com/2013/05/08/spring-beans-autowiring-concepts/Autowired members must be defined in the valid spring bean(@Component/@Service,etc.)
の Warning の原因を調査していた時に見つけた Autowired についての説明が書かれたページです。分かりやすそうだったのでメモしておきます。
HowToDoInJava - Spring Tutorials
http://howtodoinjava.com/category/frameworks/java-spring-tutorials/- Spring beans autowiring concepts のページの上部にあるパンくずリストの「SPRING」をクリックするとこのページが表示されました。Spring Framework のいろいろなサンプルが掲載されています。
- 読んでおくとよさそうなサイトに思えたので、メモしておきます。
ihiroky's blog - jenkins で実行する gradle スクリプト
http://ihiroky.hatenablog.com/entry/2012/11/22/121720- Gradle でコンパイルする時に -Xlint:all オプションを付ける設定を参考にしました。
FasterXML/jackson
https://github.com/FasterXML/jacksonA Piece of My Code - Spring Jackson library example to consume JSON returned by REST service
http://apieceofmycode.blogspot.jp/2013/05/spring-jackson-library-example-to.html- Jackson の使い方を参考にしました。
serialize/deserialize java 8 java.time with Jackson JSON mapper
http://stackoverflow.com/questions/27952472/serialize-deserialize-java-8-java-time-with-jackson-json-mapper- Jackson で JSON で渡された日時の文字列を LocalDateTime に変換するための方法を調査した時に参照しました。
FasterXML/jackson-datatype-jsr310
https://github.com/FasterXML/jackson-datatype-jsr310
目次
- 図書館一覧取得 WebAPI を JSONP に対応させる
- 一旦ここで commit
- RestTemplate をいろいろ試してみる
- commit、Push、Pull Request、マージ
- 次回は。。。
手順
図書館一覧取得 WebAPI を JSONP に対応させる
WebAPI を JSONP に対応させるには @ControllerAdvice を付加したクラスを定義すればよいだけのようです。src/main/java/ksbysample/webapp/lending/config の下に JsonpAdvice.java を作成します。作成後、リンク先の内容 に変更します。
Spring Framework 4.1 -- Spring MVC Improvements に書かれている JsonpAdvice クラスをそのまま使用しています。
JsonpAdvice のコンストラクタに記述した "callback" が JSONP で返すか否かを判断する URLパラメータになります。callback パラメータがないと JSON で返し、callback パラメータがあると JSONP で返ります。
動作確認します。Gradle projects View から bootRun タスクを実行して Tomcat を起動します。
ブラウザを起動して
http://localhost:8080/webapi/library/getLibraryList?pref=%e6%9d%b1%e4%ba%ac%e9%83%bd
にアクセスします。callback パラメータがないので JSON で返っています。次に
http://localhost:8080/webapi/library/getLibraryList?pref=%e6%9d%b1%e4%ba%ac%e9%83%bd&callback=func
にアクセスします。callback パラメータがあるので JSONP で返っています。Ctrl+F2 を押して Tomcat を停止します。
一旦ここで commit
- 一旦ここで commit します。が、いくつか警告が出たので確認します。
Autowired members must be defined in the valid spring bean(@Component/@Service,etc.) という Warning が出る
CalilApiServiceTest.java の以下の部分で Warning が出るのですが、なぜ @Service で定義しているのにこの Warning が出るのか疑問です。。。 原因を調査してみます。
@Autowired private CalilApiService calilApiService;
- stackoverflow に IntelliJ IDEA shows errors when using Spring @Autowired という Question がありました。
- @SuppressWarnings("SpringJavaAutowiringInspection") を付ければ出なくなると書かれており、確かに付けると出なくなるのは以前付けたことがあるので知っていますが、@Autowired だけで済ませたいのと原因を書いたものではないので無視します。
- 「File」->「Project Structure...」で「Project Structure」ダイアログを開いて、その中の「Facets」に「Spring」が登録されているのが原因と書かれている Answer がありました。「Facets」から「Spring」を削除すると確かに commit 時に Warning は出なくなるのですが、今度は IntelliJ IDEA の Spring View が表示されなくなりました。Spring MVC View に RequestMapping の URL 一覧が表示される機能は結構気に入っているので、原因は分かりましたがこの対策はなしですね。。。
- ちょうど調査している時に stackoverflow のタイトル画像を見たら Ten. Million. Questions 記念の以下の画像に変わっていました。最初は偽サイトを見ているのかと思ってしまいましたよ。。。
他にも調査しましたが、結論としては @Autowired を使わず @Resources 等の別のアノテーションに変えるか、チェックを無効にするかの対応になるようです。今回はチェックを無効にする方で対応します。
- Warning の位置に表示される電球アイコンをクリックしてコンテキストメニューを表示した後、「Inspection 'Autowired members defined in the invalid spring bean' options」->「Edit inspection profile setting」を選択します。
- 「Inspections」ダイアログが表示されるので、「Autowired members defined in the invalid」の項目のチェックを外して「OK」ボタンをクリックします。
AssertJ の assertThatThrownBy は lambda で書ける
Warning:(35, 28) Anonymous new ThrowableAssert.ThrowingCallable() can be replaced with lambda
という Warning が表示されました。lambda で書き直せるようなので、IntelliJ IDEA にソースを変更してもらいます。src/test/java/ksbysample/webapp/lending/service/calilapi の下の CalilApiServiceTest.java は リンク先の内容 になりました。src/test/java/ksbysample/webapp/lending/security の下の LendingUserDetailsServiceTest.java も リンク先の内容 に変更します。
全てのテストを実行してみるが、なぜかエラー発生。。。
commit 前に作成したテストが全て正常に実行されるか確認してみたところ、なぜかエラーが発生しました。
原因を確認したところ、user_info.txt のテストデータで有効期間切れを想定していないユーザが有効期限切れになっていたためでした。テストデータを修正します。
src/test/resources/testdata/base の下の user_info.txt を リンク先の内容 に変更します。
再度テストを実行したところ、全て成功しました。
clean & build をしてみたところ、エラー発生。。。
Gradle projects View から clean タスクを実行 → IntelliJ IDEA のメニューから「Build」->「Rebuild Project」を実行 → Gradle projects View から build タスクを実行したところ、build タスク実行時にエラーが発生しました。
compile と test でエラーが発生しています。
まずは compile の方から対応します。build.gradle を リンク先のその1の内容 に変更し、compile 時に -Xlint オプションが付くようにします。
build タスクを実行すると以下のログが出力されましたので、修正します。
- 直列化可能なクラスLendingUserには、serialVersionUIDが定義されていません
- 直列化可能なクラスLendingUserDetailsには、serialVersionUIDが定義されていません
- 無検査メソッド呼出し: クラス CommonWebApiResponseのメソッド setContentは指定された型に適用されます response.setContent(Collections.EMPTY_LIST);
- 無検査変換 response.setContent(Collections.EMPTY_LIST);
※「警告: これらの注釈を要求するプロセッサはありませんでした」という警告も出ましたが、これは無視します。
serialVersionUID フィールドを IntelliJ IDEA に自動生成してもらうために設定を変更します。IntelliJ IDEA のメインメニューから「File」-「Settings...」を選択します。
「Settings」ダイアログが表示されたら、画面左側で「Editor」-「Inspections」を選択し、画面中央に表示されるリストから「Serialization issues」-「Serializable class without 'serialVersionUID'」をチェックします。チェック後、「OK」ボタンをクリックしてダイアログを閉じます。
src/main/java/ksbysample/webapp/lending/security の下の LendingUser.java を開き、クラス名にカーソルを移動した後 Alt+Enter キーを押します。コンテキストメニューが表示されますので、「Add 'serialVersionUID' field」を選択します。
src/main/java/ksbysample/webapp/lending/security の下の LendingUserDetails.java にも同じ操作を行い、serialVersionUID を追加します。
src/main/java/ksbysample/webapp/lending/webapi/library の下の LibraryController.java を リンク先の内容 に変更します。
再度 clean タスク、「Build」->「Rebuild Project」、build タスクを実行し、「警告: これらの注釈を要求するプロセッサはありませんでした」以外のログが出力されていないことを確認します。
次に test の方を対応します。ログを見ると
Caused by: java.lang.NoClassDefFoundError
かCaused by: java.lang.ClassNotFoundException
が出力されており class がないことが原因のようです。このままでは何のクラスがないのか分かりません。詳細がログに出力されるよう --debug オプションを付けて build タスクを実行し直します。IntelliJ IDEA のメニューから「Run」->「Edit Configurations...」を選択し、「Run/Debug Configurations」ダイアログを表示します。
「Run/Debug Configurations」ダイアログで画面左側のリストから「Defaults」-「Gradle」を選択した後、画面右側の「Script parameters」に "--debug" を入力して「OK」ボタンをクリックします。
Gradle projects View で build タスクを選択後コンテキストメニューを表示し、「Run 'ksbysample-webapp-le...'」を選択します。
build タスクが debug モードで実行されて大量のログが出力されますので、終了するまで待ちます。
build タスクが終了した後、出力されたログを確認したところ
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/security/web/access/expression/WebSecurityExpressionHandler
というログが出力されていました。IntelliJ IDEA で Shiftキーを2回押して「Search Everywhere」を表示した後、"WebSecurityExpressionHandler" を入力して検索すると、Spring Security 3.2.7 では Interface として存在していましたが 4.0.1 ではなくなっていることが分かりました。test 実行時に Spring Security 3.2.7 の jar ファイルが使用されていないと思われます。
出力されたログに
Using application classpath
という行が出力されており、指定されている jar ファイルを確認すると spring-security-core-4.0.1.RELEASE.jar は指定されていましたが、spring-security-core-3.2.7.RELEASE.jar は指定されていませんでした。classpath に spring-security-core-3.2.7.RELEASE.jar が指定されればよいはずなのでやり方を調べたり試したりした結果、build.gradle の testCompile で 3.2.7.RELEASE を明記すれば classpath に指定されて、かつ test も成功するようになりました。
build.gradle を リンク先のその2の内容 に変更します。
Gradle projects View の左上にある「Refresh all Gradle projects」アイコンをクリックして、変更した build.gradle の内容を反映します。
clean タスク、「Build」->「Rebuild Project」、build タスクを実行して BUILD SUCCESSFUL のメッセージが出力されることを確認します。
commitする
- commit します。
RestTemplate をいろいろ試してみる
OpenWeatherMap の Weather API ( http://openweathermap.org/api ) を利用して RestTemplate で JSON, JSONP で結果を取得してみます。Weather API のデータフォーマットは Web 上では "available in JSON, XML, or HTML format" と書かれていますが、callback パラメータを付ければ JSONP で返ります。
RestTemplate で JSON を返す WebAPI を呼び出してみる
5 day / 3 hour forecast を呼び出して JSON でデータを取得してみます。
src/main/java/ksbysample/webapp/lending/service の下に openweathermapapi パッケージを作成します。
JSON 内の日時の文字列を Java のクラス内の LocalDateTime 型のフィールドに変換できるようにします。build.gradle を リンク先のその3の内容 に変更します。
Gradle projects View の左上にある「Refresh all Gradle projects」アイコンをクリックして、変更した build.gradle の内容を反映します。
5 day / 3 hour forecast のデータ用クラスを作成します。src/main/java/ksbysample/webapp/lending/service/openweathermapapi の下に FiveDayThreeHourForecastData.java, CityData.java, ForecastData.java を作成します。作成後、リンク先の内容 に変更します。
src/main/java/ksbysample/webapp/lending/service/openweathermapapi の下に OpenWeatherMapApiService.java を作成します。作成後、リンク先の内容 に変更します。
テストクラスを作成して動作を確認します。src/main/java/ksbysample/webapp/lending/service/openweathermapapi の下の OpenWeatherMapApiService.java で「Create Test」ダイアログを表示し、テストクラスを作成します。
src/test/java/ksbysample/webapp/lending/service/openweathermapapi の下に OpenWeatherMapApiServiceTest.java が作成されます。
src/test/java/ksbysample/webapp/lending/service/openweathermapapi の下の OpenWeatherMapApiServiceTest.java を リンク先の内容 に変更します。
テストを実行します。OpenWeatherMapApiServiceTest クラスのクラス名にカーソルを移動し、コンテキストメニューを表示後「Run 'OpenWeatherMapApiSer...' with Coverage」を選択します。
テストが成功することが確認できます。
System.out.println で出力されたデータの内容が問題ないかも確認します。src/test/java/ksbysample/webapp/lending/service/openweathermapapi の下に data.js を作成し、出力された文字列をコピー&ペーストした後、Ctrl+Alt+L を押してフォーマットします ( きちんとフォーマットシてくれるわけではありませんが Lombok の @ToString アノテーションで出力した文字列は IntelliJ IDEA の .js ファイル用のフォーマットがある程度見やすくなると思っています )。
ブラウザから http://api.openweathermap.org/data/2.5/forecast?q=Tokyo にアクセスした結果と比較して見ましたが、問題なさそうです。
data.js は削除し、OpenWeatherMapApiServiceTest.java の中の System.out.println している部分はコメントアウトします。
一旦ここで commit します。
commit、Push、Pull Request、マージ
コマンドラインから以下のコマンドを実行して commit を1つにまとめます。
> git rebase -i HEAD~2
> git commit --amend -m "#21 図書館一覧取得 WebAPI を作成しました。"GitHub へ Push、1.0.x-make-webapi-getlibrary -> 1.0.x へ Pull Request、1.0.x でマージ、1.0.x-make-webapi-getlibrary ブランチを削除、をします。
次回は。。。
JDK 8u60 が出たので変更する予定です。Map の初期化が楽になっているらしいので、それも試してみたいと思います。
その後は RestTemplate で JSONP を返す WebAPI を呼び出す方法を今回調べていて分からなかったので、もう少し調べてみます。また WebAPI で XML で返す方法も調べてまとめてみる予定です。
ソースコード
JsonpAdvice.java
package ksbysample.webapp.lending.config; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice; @ControllerAdvice public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice { public JsonpAdvice() { super("callback"); } }
CalilApiServiceTest.java
@Test public void testGetLibraryList_都道府県名が間違っている場合() throws Exception { assertThatThrownBy(() -> { Libraries libraries = calilApiService.getLibraryList("東a京都"); }).isInstanceOf(ValueRequiredException.class); }
- 前のはなんか分かりにくい書き方で AssertJ を使ってもいまひとつ便利だと思えなかったのですが、これなら分かりやすいです。
LendingUserDetailsServiceTest.java
@Test public void 存在しないユーザならばUsernameNotFoundExceptionが発生する() { assertThatThrownBy(() -> { UserDetails userDetails = userDetailsService.loadUserByUsername(MAILADDR_TEST_TARO); }).isInstanceOf(UsernameNotFoundException.class) .hasMessage(messageSource.getMessage("UserInfoUserDetailsService.usernameNotFound" , null, LocaleContextHolder.getLocale())); }
- lambda の記述に変更しました。
user_info.txt
user_id,username,password,mail_address,enabled,cnt_badcredentials,expired_account,expired_password 1,"tanaka taro",$2a$10$LKKepbcPCiT82NxSIdzJr.9ph.786Mxvr.VoXFl4hNcaaAn9u7jje,tanaka.taro@sample.com,1,0,"2015-10-17 12:46:14.790000","2015-08-18 12:46:30.354000" 2,"suzuki hanako",$2a$10$.fiPEZ155Rl41/e.mdM3A.mG0iEQNPmhjFL/aIiV8dZnXsCd.oqji,suzuki.hanako@test.co.jp,1,0,"2015-09-30 22:19:02.783000","2015-08-31 22:19:22.176000" 3,"kimura masao",$2a$10$yP1dLPIq9j7WQVH6ruSwkepf8jIkPxTtncbSnYM0/jAGQ4HCQO8R.,kimura.masao@test.co.jp,0,0,"2015-12-31 22:30:54.425000","2015-10-15 22:31:03.316000" 4,"endo yoko",$2a$10$PVFe8Lh1Pkjc54DWS9mJL.q407x51ZK8MSXhwuTF9zxCnnt80LKwy,endo.yoko@sample.com,1,0,"2015-01-10 22:31:55.454000","2015-12-31 22:32:11.886000" 5,"sato masahiko",$2a$10$qIU0kM/p1pa7KSIjF6YA4eORd2wL1Eo6TlvH./DmPs7D.xXQPEq7a,sato.masahiko@sample.com,1,0,"2015-12-31 22:34:14.827000","2014-08-05 22:34:22.818000" 6,"takahasi naoko",$2a$10$iXp/d4wXmfaLKTjQKBvik.kETgx4nQ.FL1NjYt4ALJOGSyVOSchW6,takahasi.naoko@test.co.jp,1,0,"2015-12-01 22:39:48.475000","2015-11-10 22:39:55.422000" 7,"kato hiroshi",$2a$10$g5dtFTtNBdJO30aHg50rluGNa2pEAzArcwYkYyCG91ElBZPs9sDi2,kato.hiroshi@sample.com,0,5,"2014-01-01 15:58:53.295000","2013-12-31 15:59:07.668000"
- user_id = 1 のデータの expired_password の値を
2015-08-18 12:46:30.354000
→2016-08-18 12:46:30.354000
へ変更します。
build.gradle
■その1
apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'spring-boot' apply plugin: 'io.spring.dependency-management' apply plugin: 'de.undercouch.download' sourceCompatibility = 1.8 targetCompatibility = 1.8 compileJava.options.compilerArgs = ['-Xlint:all'] // for Doma 2 // JavaクラスとSQLファイルの出力先ディレクトリを同じにする processResources.destinationDir = compileJava.destinationDir // コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する compileJava.dependsOn processResources jar { baseName = 'ksbysample-webapp-lending' version = '1.0.0-SNAPSHOT' }
- sourceCompatibility と targetCompatibility の位置を変更しました。
compileJava.options.compilerArgs = ['-Xlint:all']
を追加しました。
■その2
dependencies { def jdbcDriver = "org.postgresql:postgresql:9.4-1201-jdbc41" // spring-boot-gradle-plugin によりバージョン番号が自動で設定されるもの // Appendix E. Dependency versions ( http://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照 compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity3") compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("org.springframework.boot:spring-boot-starter-velocity") compile("org.springframework.boot:spring-boot-starter-mail") compile("org.springframework.boot:spring-boot-starter-security") compile("org.springframework.boot:spring-boot-starter-redis") compile("org.codehaus.janino:janino") testCompile("org.springframework.boot:spring-boot-starter-test") // (ここから) gradle でテストを実行した場合に spring-security-test-4.0.1.RELEASE.jar しか classpath に指定されず // テストが失敗したため、3.2.7.RELEASE を明記している testCompile("org.springframework.security:spring-security-core:3.2.7.RELEASE") testCompile("org.springframework.security:spring-security-web:3.2.7.RELEASE") // (ここまで) ------------------------------------------------------------------------------------------------------ testCompile("org.springframework.security:spring-security-test:4.0.1.RELEASE") testCompile("org.yaml:snakeyaml") // spring-boot-gradle-plugin によりバージョン番号が自動で設定されないもの compile("${jdbcDriver}") compile("org.seasar.doma:doma:2.3.1") compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16") compile("org.apache.commons:commons-lang3:3.4") compile("org.projectlombok:lombok:1.16.4") compile("com.google.guava:guava:18.0") compile("org.springframework.session:spring-session:1.0.1.RELEASE") compile("org.simpleframework:simple-xml:2.7.1") testCompile("org.dbunit:dbunit:2.5.1") testCompile("com.icegreen:greenmail:1.4.1") testCompile("org.assertj:assertj-core:3.1.0") testCompile("com.jayway.jsonpath:json-path:2.0.0") // for Doma-Gen domaGenRuntime("org.seasar.doma:doma-gen:2.3.1") domaGenRuntime("${jdbcDriver}") }
testCompile("org.springframework.security:spring-security-core:3.2.7.RELEASE")
、testCompile("org.springframework.security:spring-security-web:3.2.7.RELEASE")
を追加します。
■その3
dependencies { def jdbcDriver = "org.postgresql:postgresql:9.4-1201-jdbc41" // spring-boot-gradle-plugin によりバージョン番号が自動で設定されるもの // Appendix E. Dependency versions ( http://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照 compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity3") compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("org.springframework.boot:spring-boot-starter-velocity") compile("org.springframework.boot:spring-boot-starter-mail") compile("org.springframework.boot:spring-boot-starter-security") compile("org.springframework.boot:spring-boot-starter-redis") compile("org.codehaus.janino:janino") testCompile("org.springframework.boot:spring-boot-starter-test") // (ここから) gradle でテストを実行した場合に spring-security-test-4.0.1.RELEASE.jar しか classpath に指定されず // テストが失敗したため、3.2.7.RELEASE を明記している testCompile("org.springframework.security:spring-security-core:3.2.7.RELEASE") testCompile("org.springframework.security:spring-security-web:3.2.7.RELEASE") // (ここまで) ------------------------------------------------------------------------------------------------------ testCompile("org.springframework.security:spring-security-test:4.0.1.RELEASE") testCompile("org.yaml:snakeyaml") // spring-boot-gradle-plugin によりバージョン番号が自動で設定されないもの compile("${jdbcDriver}") compile("org.seasar.doma:doma:2.3.1") compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16") compile("org.apache.commons:commons-lang3:3.4") compile("org.projectlombok:lombok:1.16.4") compile("com.google.guava:guava:18.0") compile("org.springframework.session:spring-session:1.0.1.RELEASE") compile("org.simpleframework:simple-xml:2.7.1") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.6.1") testCompile("org.dbunit:dbunit:2.5.1") testCompile("com.icegreen:greenmail:1.4.1") testCompile("org.assertj:assertj-core:3.1.0") testCompile("com.jayway.jsonpath:json-path:2.0.0") // for Doma-Gen domaGenRuntime("org.seasar.doma:doma-gen:2.3.1") domaGenRuntime("${jdbcDriver}") }
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.6.1")
を追加します。
LibraryController.java
@RequestMapping("/getLibraryList") public CommonWebApiResponse<List<Library>> getLibraryList(String pref) throws Exception { CommonWebApiResponse<List<Library>> response = new CommonWebApiResponse<>(); response.setContent(Collections.emptyList());
response.setContent(Collections.EMPTY_LIST);
→response.setContent(Collections.emptyList());
に変更します。
FiveDayThreeHourForecastData.java, CityData.java, ForecastData.java
■FiveDayThreeHourForecastData.java
package ksbysample.webapp.lending.service.openweathermapapi; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import lombok.ToString; import java.math.BigDecimal; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) @Data @ToString public class FiveDayThreeHourForecastData { private CityData city; private String cod; private BigDecimal message; private int cnt; private List<ForecastData> list; }
@JsonIgnoreProperties(ignoreUnknown = true)
を付加して、存在しない要素があってもエラーにならないようにします。- データの変換状況を確認できるよう lombok の @ToString アノテーションを付加して、toString メソッドで値が出力できるようにします。
- 大きさの不明な数値が入りそうなところは BigDecimal にしています。
■CityData.java
package ksbysample.webapp.lending.service.openweathermapapi; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) @Data public class CityData { private String id; private String name; // こんなレスポンスであればクラスを作成せずに Map<String, String> で受け取ることも可能 // "coord": { // "lon": 139.691711, // "lat": 35.689499 // }, private Map<String, String> coord; private String country; private int population; private Map<String, String> sys; }
- coord のような場合にはクラスを作成せずに Map<String, String> 型のフィールドにしています。Jackson が適宜変換してくれます。
■ForecastData.java
package ksbysample.webapp.lending.service.openweathermapapi; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.ToString; import org.springframework.format.annotation.DateTimeFormat; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) @Data @ToString public class ForecastData { private BigDecimal dt; private Map<String, String> main; private List<Map<String, String>> weather; private Map<String, String> clouds; private Map<String, String> wind; private Map<String, String> rain; @JsonProperty("dt_txt") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime dtTxt; }
- weather の中はキーと値の組み合わせのみのデータの配列だったので、List<Map<String, String>> で定義しています。
- dt_txt は Java のクラスのフィールド名はアンダーバーを取りたかったので、@JsonProperty("dt_txt") アノテーションを付けています。また日時の文字列でしたので、@JsonFormat で文字列のフォーマットを定義し、LocalDateTime 型のフィールドに変換するようにしています。
OpenWeatherMapApiService.java
package ksbysample.webapp.lending.service.openweathermapapi; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class OpenWeatherMapApiService { private int CONNECT_TIMEOUT = 5000; private int READ_TIMEOUT = 5000; private final String URL_WEATHERAPI_5DAY3HOURFORECAST = "http://api.openweathermap.org/data/2.5/forecast?q={cityname}"; public FiveDayThreeHourForecastData getFiveDayThreeHourForecast(String cityname) { RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory()); ResponseEntity<FiveDayThreeHourForecastData> response = restTemplate.getForEntity(URL_WEATHERAPI_5DAY3HOURFORECAST, FiveDayThreeHourForecastData.class, cityname); return response.getBody(); } private ClientHttpRequestFactory getClientHttpRequestFactory() { // 接続タイムアウト、受信タイムアウトを 5秒に設定する SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(CONNECT_TIMEOUT); factory.setReadTimeout(READ_TIMEOUT); return factory; } }
- RestTemplate は単に変換先のクラスを指定して getForEntity を呼び出すだけです。
OpenWeatherMapApiServiceTest.java
package ksbysample.webapp.lending.service.openweathermapapi; import ksbysample.webapp.lending.Application; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class OpenWeatherMapApiServiceTest { @Autowired private OpenWeatherMapApiService openWeatherMapApiService; @Test public void testGetFiveDayThreeHourForecast() throws Exception { FiveDayThreeHourForecastData fiveDayThreeHourForecastData = openWeatherMapApiService.getFiveDayThreeHourForecast("Tokyo"); assertThat(fiveDayThreeHourForecastData.getCity().getName()).isEqualTo("Tokyo"); assertThat(fiveDayThreeHourForecastData.getCity().getCountry()).isEqualTo("JP"); assertThat(fiveDayThreeHourForecastData.getList().size()).isGreaterThan(0); System.out.println(fiveDayThreeHourForecastData); } }
履歴
2015/08/24
初版発行。