読者です 読者をやめる 読者になる 読者になる

かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その9 )( 1.3系 → 1.4系で実装方法が変更された点を修正する )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その8 )( build.gradle への checkstyle, findbugs の導入+CheckStyle-IDEA, FindBugs-IDEA Plugin の導入 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 1.3系 → 1.4系で実装方法が変更された点を修正します。
    • 今回は変更が軽微なものだけで、フィールドへの @Autowired 付加 → コンストラクタインジェクションへの変更、及びテストクラスのアノテーションの変更は次回以降にやります。

参照したサイト・書籍

  1. Spring Boot 1.4 Release Notes
    https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.4-Release-Notes

  2. Spring Boot 1.4+でRestTemplate(HTTPクライアント)を使う
    http://qiita.com/kazuki43zoo/items/7cf3c8ca4f6f2283cefb

目次

  1. spring-boot-starter で名前が変更されたものを反映する
  2. application.properties に spring.session.store-type の設定を追加する
  3. application.properties の spring.datasource の項目名を変更する
  4. @RequestMapping を変更可能なところは @GetMapping, @PostMapping に変更する
  5. RestTemplate オブジェクトを生成する処理を new RestTemplate(...)RestTemplateBuilder#build へ変更する
  6. 次回は。。。

手順

spring-boot-starter で名前が変更されたものを反映する

1.4 から spring-boot-starter-redisspring-boot-starter-data-redis へ変更されたので反映します。

  1. build.gradle を リンク先の内容 に変更します。

  2. Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

  3. Project Tool Window の External Libraries を見ると spring-boot-starter-redis が消えて spring-boot-starter-data-redis が入ったことが確認できます。

    f:id:ksby:20170220230718p:plain

application.properties に spring.session.store-type の設定を追加する

Spring Session のデータ保存先を application.properties で設定できるようになったので、その設定を追加します。

  1. application.properties を リンク先の内容 に変更します。

    設定する時に IntelliJ IDEA で候補を表示させてみたのですが、hash_mapnone といった選択肢もありました。一時的に Spring Session を試したいだけならサーバを立てずに hash_map を選択するのもありなのかもしれません。none は何だろう?と思ったら、39. Spring Session に Spring Session を無効化したい時に設定するよう記述がありました。

    f:id:ksby:20170220231055p:plain

application.properties の spring.datasource の項目名を変更する

使用しているコネクションプーリングのライブラリに応じた namespace が増えました。Spring Boot では特に指定をしていなければ Tomcat JDBC Connection Pool が使用されるので、spring.datasourcespring.datasource.tomcat へ変更します。

Appendix A. Common application properties には「spring.datasource.tomcat.*= # Tomcat datasource specific settings」としか記述がありませんが、IntelliJ IDEA で設定項目を表示させてみたところ、以下の項目が表示されました。

f:id:ksby:20170221001910p:plain f:id:ksby:20170221002008p:plain

知らない設定がいろいろ表示されています。次回以降で設定しておいた方がよいものがあるのか調べてみたいと思います。Tomcat JDBC Connection Pool の URL もメモしておきます。

The Tomcat JDBC Connection Pool
https://tomcat.apache.org/tomcat-8.5-doc/jdbc-pool.html

  1. application-develop.properties, application-product.properties, application-unittest.properties を リンク先の内容 に変更します。

  2. src/main/java/ksbysample/webapp/lending/config の下の ApplicationConfig.javaリンク先のその1の内容 に変更します。

    ※org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration クラスが dataSource Bean を生成して spring.datasource.tomcat の設定を反映してくれると思ったのですが、なぜかうまく行きませんでした。。。 ので、自分で dataSource Bean を定義しています。

@RequestMapping を変更可能なところは @GetMapping, @PostMapping に変更する

ksbysample-webapp-lending のソースを見てみると @RequestMapping(value = "...", method = RequestMethod.POST) のように POST のみに制限しているところはあっても、GET のみで制限しているところはありませんでした。今回は @RequestMapping(value = "...", method = RequestMethod.POST)@PostMapping("...") にのみ変更することにします。

  1. 以下のクラスのメソッドで @RequestMapping(value = "...", method = RequestMethod.POST)@PostMapping("...") へ変更します。

    • ksbysample.webapp.lending.web.confirmresult.ConfirmresultController#filedownloadByResponse
    • ksbysample.webapp.lending.web.confirmresult.ConfirmresultController#filedownloadByView
    • ksbysample.webapp.lending.web.lendingapp.LendingappController#apply
    • ksbysample.webapp.lending.web.lendingapp.LendingappController#temporarySave
    • ksbysample.webapp.lending.web.lendingapproval.LendingapprovalController#complete

RestTemplate オブジェクトを生成する処理を new RestTemplate(...)RestTemplateBuilder#build へ変更する

RestTemplate オブジェクトを生成するための RestTemplateBuilder クラスが提供されています。タイムアウトを設定するのも RestTemplateBuilder クラスを使用した方が簡単で分かりやすいので、RestTemplateBuilder クラスを利用する方法に変更します。

  1. MappingJackson2XmlHttpMessageConverter を生成する部分は Bean にします。src/main/java/ksbysample/webapp/lending/config の下の ApplicationConfig.javaリンク先のその2の内容 に変更します。

  2. src/main/java/ksbysample/webapp/lending/service/calilapi の下の CalilApiService.javaリンク先の内容 に変更します。

※src/main/java/ksbysample/webapp/lending/service/openweathermapapi の下の OpenWeatherMapApiService.java は使用していないのとテストも動かないので、変更しません。

次回は。。。

引き続き 1.3系 → 1.4系で実装方法が変更された点を修正します。

次回はフィールドへの @Autowired 付加 → コンストラクタインジェクションへの変更、その次にテストクラスのアノテーションの変更をやる予定です。

ソースコード

build.gradle

dependencies {
    ..........

    // 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-security")
    compile("org.springframework.boot:spring-boot-starter-data-redis")
    compile("org.springframework.boot:spring-boot-starter-amqp")
    ..........
  • spring-boot-starter-redisspring-boot-starter-data-redis へ変更します。

application.properties

hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
doma.dialect=org.seasar.doma.jdbc.dialect.PostgresDialect

spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy

spring.session.store-type=redis

spring.freemarker.cache=true
spring.freemarker.charset=UTF-8
spring.freemarker.enabled=false
spring.freemarker.prefer-file-system-access=false
  • spring.session.store-type=redis を追加します。

application-develop.properties, application-product.properties, application-unittest.properties

■application-develop.properties

spring.datasource.tomcat.url=jdbc:log4jdbc:postgresql://localhost/ksbylending
spring.datasource.tomcat.username=ksbylending_user
spring.datasource.tomcat.password=xxxxxxxx
spring.datasource.tomcat.driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy

■application-product.properties

spring.datasource.tomcat.url=jdbc:postgresql://localhost/ksbylending
spring.datasource.tomcat.username=ksbylending_user
spring.datasource.tomcat.password=xxxxxxxx
spring.datasource.tomcat.driverClassName=org.postgresql.Driver

■application-unittest.properties

spring.datasource.tomcat.url=jdbc:postgresql://localhost/ksbylending
spring.datasource.tomcat.username=ksbylending_user
spring.datasource.tomcat.password=xxxxxxxx
spring.datasource.tomcat.driverClassName=org.postgresql.Driver
  • 全て spring.datasourcespring.datasource.tomcat へ変更します。尚、上で設定している url, username, password, driverClassName のような全てのコネクションプーリングで共通の設定の場合には spring.datasource のままでも構いません。

ApplicationConfig.java

■その1

..........
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
..........

import javax.sql.DataSource;

@Configuration
public class ApplicationConfig {

    ..........

    /**
     * @return Tomcat JDBC Connection Pool の DataSource オブジェクト
     */
    @Bean
    @ConfigurationProperties("spring.datasource.tomcat")
    public DataSource dataSource() {
        return DataSourceBuilder.create()
                .type(org.apache.tomcat.jdbc.pool.DataSource.class)
                .build();
    }

}
  • dataSource メソッドを追加します。この時 @ConfigurationProperties("spring.datasource.tomcat") アノテーションを付加して spring.datasource.tomcat の設定が DataSource オブジェクトに設定されるようにします。

■その2

@Configuration
public class ApplicationConfig {

    ..........

    /**
     * 外部のWebAPIとXMLフォーマットで通信するために使用する MessageConverter
     * build.gralde に compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:...") を記述して
     * jackson-dataformat-xml が使用できるように設定しないと Bean は生成されない
     *
     * @return MappingJackson2XmlHttpMessageConverter オブジェクト
     */
    @Bean
    @ConditionalOnClass(com.fasterxml.jackson.dataformat.xml.XmlMapper.class)
    public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() {
        // findAndRegisterModules メソッドを呼び出して jackson-dataformat-xml が機能するようにする
        return new MappingJackson2XmlHttpMessageConverter(new XmlMapper().findAndRegisterModules());
    }

}
  • mappingJackson2XmlHttpMessageConverter Bean を追加します。
    • 以前 ksbysample.webapp.lending.service.calilapi.CalilApiService#getMessageConvertersforJackson2Xml に実装していた内容を持ってきて、以下の点を変更しました。
      • assert(...) は削除して @ConditionalOnClass(com.fasterxml.jackson.dataformat.xml.XmlMapper.class) に変更しました。
      • MappingJackson2XmlHttpMessageConverter#setObjectMapper を呼び出すのではなく、new MappingJackson2XmlHttpMessageConverter(...) の引数で渡すように変更しました。
      • spring-framework/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java を見ると、デフォルトで必要な MediaType がセットされていたので、MappingJackson2XmlHttpMessageConverter#setSupportedMediaTypes でセットしていた処理を削除しました。

CalilApiService.java

package ksbysample.webapp.lending.service.calilapi;

import com.google.common.base.Joiner;
import ksbysample.webapp.lending.service.calilapi.response.Book;
import ksbysample.webapp.lending.service.calilapi.response.CheckApiResponse;
import ksbysample.webapp.lending.service.calilapi.response.Libraries;
import ksbysample.webapp.lending.service.calilapi.response.LibrariesForJackson2Xml;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@PropertySource("classpath:calilapi.properties")
public class CalilApiService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final int RETRY_MAX_CNT = 5;
    private static final long RETRY_SLEEP_MILLS = 3000;

    private static final String URL_CALILAPI_ROOT = "http://api.calil.jp";
    private static final String URL_CALILAPI_LIBRALY
            = URL_CALILAPI_ROOT + "/library?appkey={appkey}&pref={pref}";
    private static final String URL_CALILAPI_CHECK
            = URL_CALILAPI_ROOT + "/check?appkey={appkey}&systemid={systemid}&isbn={isbn}&format=xml";
    private static final String URL_CALILAPI_CHECK_FOR_RETRY
            = URL_CALILAPI_ROOT + "/check?session={session}&format=xml";

    @Value("${calil.apikey}")
    private String calilApiKey;

    private final RestTemplate restTemplateForCalilApi;

    private final RestTemplate restTemplateForCalilApiByXml;

    public CalilApiService(@Qualifier("restTemplateForCalilApi") RestTemplate restTemplateForCalilApi
            , @Qualifier("restTemplateForCalilApiByXml") RestTemplate restTemplateForCalilApiByXml) {
        this.restTemplateForCalilApi = restTemplateForCalilApi;
        this.restTemplateForCalilApiByXml = restTemplateForCalilApiByXml;
    }

    /**
     * @param pref ???
     * @return ???
     * @throws Exception
     */
    public Libraries getLibraryList(String pref) throws Exception {
        // 図書館データベースAPIを呼び出して XMLレスポンスを受信する
        ResponseEntity<String> response
                = this.restTemplateForCalilApi.getForEntity(URL_CALILAPI_LIBRALY
                , String.class, this.calilApiKey, pref);

        // 受信した XMLレスポンスを Javaオブジェクトに変換する
        Serializer serializer = new Persister();
        Libraries libraries = serializer.read(Libraries.class, response.getBody());

        return libraries;
    }

    /**
     * @param pref ???
     * @return ???
     * @throws Exception
     */
    public LibrariesForJackson2Xml getLibraryListByJackson2Xml(String pref) throws Exception {
        // 図書館データベースAPIを呼び出して XMLレスポンスを受信する
        ResponseEntity<LibrariesForJackson2Xml> response
                = this.restTemplateForCalilApiByXml.getForEntity(URL_CALILAPI_LIBRALY
                , LibrariesForJackson2Xml.class, this.calilApiKey, pref);
        return response.getBody();
    }

    /**
     * @param systemid ???
     * @param isbnList ???
     * @return ???
     */
    public List<Book> check(String systemid, List<String> isbnList) {
        Map<String, String> vars = new HashMap<>();
        vars.put("appkey", this.calilApiKey);
        vars.put("systemid", systemid);
        vars.put("isbn", Joiner.on(",").join(isbnList));

        ResponseEntity<CheckApiResponse> response = null;
        String url = URL_CALILAPI_CHECK;
        for (int retry = 0; retry < RETRY_MAX_CNT; retry++) {
            // 蔵書検索APIを呼び出して蔵書の有無と貸出状況を取得する
            response = this.restTemplateForCalilApiByXml.getForEntity(url, CheckApiResponse.class, vars);
            logger.info("カーリルの蔵書検索API を呼び出し、レスポンスを取得しました。{}", response.getBody().toString());
            if (response.getBody().getContinueValue() == 0) {
                break;
            }

            // continue の値が 0 でない場合には2秒以上待機した後、URLパラメータを session に変更して再度リクエストを送信する
            try {
                Thread.sleep(RETRY_SLEEP_MILLS);
            } catch (InterruptedException e) {
                logger.warn("カーリルの蔵書検索APIのsleep中にInterruptedExceptionが発生しましたが、処理は継続します。", e);
            }
            url = URL_CALILAPI_CHECK_FOR_RETRY;
            vars.clear();
            vars.put("session", response.getBody().getSession());
        }

        return response.getBody().getBookList();
    }

    @Configuration
    public static class CalilApiConfig {

        private static int CONNECT_TIMEOUT = 5000;
        private static int READ_TIMEOUT = 5000;

        private final RestTemplateBuilder restTemplateBuilder;

        private final MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter;

        /**
         * コンストラクタ
         *
         * @param restTemplateBuilder                    restTemplateBuilder Bean
         * @param mappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter Bean
         */
        public CalilApiConfig(RestTemplateBuilder restTemplateBuilder
                , MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter) {
            this.restTemplateBuilder = restTemplateBuilder;
            this.mappingJackson2XmlHttpMessageConverter = mappingJackson2XmlHttpMessageConverter;
        }

        /**
         * カーリルの図書館API呼び出し用 RestTemplate
         * JSON フォーマットで結果を受信する
         *
         * @return RestTemplate オブジェクト
         */
        @Bean
        public RestTemplate restTemplateForCalilApi() {
            return this.restTemplateBuilder
                    .setConnectTimeout(CONNECT_TIMEOUT)
                    .setReadTimeout(READ_TIMEOUT)
                    .rootUri(URL_CALILAPI_ROOT)
                    .build();
        }

        /**
         * カーリルの図書館API呼び出し用 RestTemplate
         * XML フォーマットで結果を受信する
         *
         * @return RestTemplate オブジェクト
         */
        @Bean
        public RestTemplate restTemplateForCalilApiByXml() {
            return this.restTemplateBuilder
                    .setConnectTimeout(CONNECT_TIMEOUT)
                    .setReadTimeout(READ_TIMEOUT)
                    .rootUri(URL_CALILAPI_ROOT)
                    .messageConverters(this.mappingJackson2XmlHttpMessageConverter)
                    .build();
        }

    }

}
  • @Configuration public static class CalilApiConfig { ... } を追加して、その中で restTemplateForCalilApi Bean, restTemplateForCalilApiByXml Bean を定義するようにします。
  • RETRY_MAX_CNT, RETRY_SLEEP_MILLS の定義は CalilApiConfig クラス内へ移動します。
  • URL_CALILAPI_ROOT の定義を追加し、URL_CALILAPI_LIBRALY, URL_CALILAPI_CHECK, URL_CALILAPI_CHECK_FOR_RETRY の URL の定義を URL_CALILAPI_ROOT を使うように変更しました。URL_CALILAPI_ROOT は restTemplateForCalilApi Bean, restTemplateForCalilApiByXml Bean の生成時に RestTemplateBuilder#rootUri を呼び出す時に使用します。
  • DI 用の以下のフィールドと、インジェクションするためのコンストラクタを追加します。
    • private final RestTemplate restTemplateForCalilApi;
    • private final RestTemplate restTemplateForCalilApiByXml;
  • 各メソッド内で new RestTemplate(...) を呼び出して RestTemplate オブジェクトを生成している処理を削除しました。

履歴

2017/02/22
初版発行。