かんがるーさんの日記

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

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その13 )( RestTemplate で WebAPI を呼び出している処理に spring-retry でリトライ処理を入れる )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その12 )( RestTemplateBuilder を使用するように変更したらテストが失敗するようになった理由とは? ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回の検証で WebAPI を呼び出している処理にリトライ処理を入れればテストが成功することが分かったので、spring-retry でリトライ処理を入れます。
    • @Retryable アノテーションではなく RetryTemplate クラスで実装します。
    • リトライ処理は、以下のルールにします。
      • Exception.class ( あるいはその継承クラス ) が throw されたらリトライします。
      • 最大5回リトライします。
      • リトライ間隔は5秒固定にします。
      • リトライが全てエラーになった場合には、発生した例外がそのまま throw されるようにします。
  • spring-retry の処理については以前書いた以下の記事を参考にして実装します。

参照したサイト・書籍

目次

  1. build.gradle を変更する
  2. spring-retry でリトライ処理を入れる
  3. 動作確認
  4. メモ書き

手順

build.gradle を変更する

gradlew dependencies を実行すると org.springframework.boot:spring-boot-starter-amqp の依存関係で spring-retry は入っていることが確認できたのですが、

compile - Dependencies for source set 'main'.
..........
+--- org.springframework.boot:spring-boot-starter-amqp: -> 1.4.4.RELEASE
|    +--- org.springframework.boot:spring-boot-starter:1.4.4.RELEASE (*)
|    +--- org.springframework:spring-messaging:4.3.6.RELEASE
|    |    +--- org.springframework:spring-beans:4.3.6.RELEASE (*)
|    |    +--- org.springframework:spring-context:4.3.6.RELEASE (*)
|    |    \--- org.springframework:spring-core:4.3.6.RELEASE
|    \--- org.springframework.amqp:spring-rabbit:1.6.7.RELEASE
|         +--- org.springframework:spring-web:4.2.9.RELEASE -> 4.3.6.RELEASE (*)
|         +--- org.springframework.retry:spring-retry:1.1.5.RELEASE
|         |    \--- org.springframework:spring-core:4.0.4.RELEASE -> 4.3.6.RELEASE
|         +--- org.springframework:spring-messaging:4.2.9.RELEASE -> 4.3.6.RELEASE (*)
|         +--- org.springframework:spring-context:4.2.9.RELEASE -> 4.3.6.RELEASE (*)
|         +--- org.springframework:spring-tx:4.2.9.RELEASE -> 4.3.6.RELEASE (*)
|         +--- com.rabbitmq:http-client:1.0.0.RELEASE
|         |    +--- org.apache.httpcomponents:httpclient:4.3.6 -> 4.5.2
|         |    |    +--- org.apache.httpcomponents:httpcore:4.4.4 -> 4.4.6
|         |    |    \--- commons-codec:commons-codec:1.9 -> 1.10
|         |    \--- com.fasterxml.jackson.core:jackson-databind:2.5.1 -> 2.8.6 (*)
|         +--- org.springframework.amqp:spring-amqp:1.6.7.RELEASE
|         |    \--- org.springframework:spring-core:4.2.9.RELEASE -> 4.3.6.RELEASE
|         \--- com.rabbitmq:amqp-client:3.6.5 -> 3.6.6
..........

明示的に build.gradle に記述することにします。

  1. build.gradle を リンク先の内容 に変更します。変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

spring-retry でリトライ処理を入れる

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

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

動作確認

  1. clean タスク → Rebuild Project → build タスク を実行して “BUILD SUCCESSFUL” が表示されることを確認します。

    f:id:ksby:20170320190250p:plain

  2. Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」を実行して、テストが全て成功することを確認します。

    f:id:ksby:20170320190701p:plain

メモ書き

メソッドに @Retryable アノテーションを付けるだけでリトライしてくれる訳ではないらしい。@Retryable アノテーションでリトライさせるための条件を忘れているな。。。

ソースコード

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.session:spring-session")
    compile("org.springframework.retry:spring-retry")
    compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    ..........
  • compile("org.springframework.retry:spring-retry") を追加します。

ApplicationConfig.java

@Configuration
public class ApplicationConfig {

    ..........

    @Bean
    public RetryTemplate simpleRetryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setRetryPolicy(
                new SimpleRetryPolicy(5, singletonMap(Exception.class, true)));
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(5000);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        return retryTemplate;
    }

}
  • simpleRetryTemplate メソッドを追加します。

CalilApiService.java

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

    ..........

    private final RestTemplate restTemplateForCalilApi;

    private final RestTemplate restTemplateForCalilApiByXml;

    private final RetryTemplate simpleRetryTemplate;

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

    ..........

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

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

        return libraries;
    }

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

    public List<Book> check(String systemid, List<String> isbnList) {
        ..........

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

            ..........
        }

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

    private <T> ResponseEntity<T> getForEntityWithRetry(RestTemplate restTemplate, String url
            , Class<T> responseType, Object... uriVariables) {
        ResponseEntity<T> response = this.simpleRetryTemplate.execute(context -> {
            if (context.getRetryCount() > 0) {
                logger.info("★★★ リトライ回数 = " + context.getRetryCount());
            }
            ResponseEntity<T> innerResponse = restTemplate.getForEntity(url, responseType, uriVariables);
            return innerResponse;
        });

        return response;
    }
  • private final RetryTemplate simpleRetryTemplate; を追加し、コンストラクタインジェクションの処理も追加します。
  • getForEntityWithRetry メソッドを追加します。
  • 以下のメソッド内で RestTemplate#getForEntity を呼び出しているところを getForEntityWithRetry メソッドを使用するように変更します。
    • getLibraryList メソッド
    • getLibraryListByJackson2Xml メソッド
    • check メソッド

履歴

2017/03/20
初版発行。
2017/03/22
* @Retryable アノテーションで実装したのですが、例外が throw されてもリトライしないことに気付いたので、RetryTemplate クラスを使用する方法に修正しました。