かんがるーさんの日記

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

Gradle で Multi-project を作成する ( 番外編 )( Spring Actuator を利用してアプリ起動時にメールサーバに接続できない場合には起動を中断させる )

概要

記事一覧はこちらです。

本編とは全く関係ありません。。。

何となく思いついたことで Spring Actuator を入れると DB サーバやメールサーバの UP/DOWN を検知できますが、起動時にサーバに接続できなかったら起動を中断させることができたりするのかな?、と思って調べた時の内容です。

結論としては HealthChecker の実装に依存しますが、メールサーバは可能でした。

参照したサイト・書籍

  1. Programmatically shut down Spring Boot application
    https://stackoverflow.com/questions/22944144/programmatically-shut-down-spring-boot-application

目次

  1. 動作確認のための demo プロジェクトを作成する
  2. Spring Actuator でメールサーバをチェックするよう設定する
  3. メールサーバを起動せずに demo アプリを起動してみる
  4. 起動時にメールサーバに接続できなかったら起動を中断するための CustomMailHealthIndicator クラスを作成する
  5. 動作確認

手順

動作確認のための demo プロジェクトを作成する

IntelliJ IDEA から Spring Initializr を利用して demo プロジェクトを作成します。

f:id:ksby:20190426070614p:plainf:id:ksby:20190426070709p:plain
f:id:ksby:20190426070819p:plainf:id:ksby:20190426070854p:plain
f:id:ksby:20190426070931p:plainf:id:ksby:20190426071002p:plain

※DevTools、Web、Mail、Actuator をチェックします。

f:id:ksby:20190426071139p:plainf:id:ksby:20190426071356p:plain

IntelliJ IDEA のメイン画面が表示されたら、lombok@Slf4j アノテーションを使用したいので build.gradle の dependencies に lombok の依存関係を追加します。

plugins {
    id 'org.springframework.boot' version '2.1.4.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    // annotationProcessor と testAnnotationProcessor、compileOnly と testCompileOnly を併記不要にする
    testAnnotationProcessor.extendsFrom annotationProcessor
    testImplementation.extendsFrom compileOnly
}

repositories {
    mavenCentral()
}

dependencies {
    def lombokVersion = "1.18.6"

    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-mail'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // for lombok
    // testAnnotationProcessor、testCompileOnly を併記しなくてよいよう configurations で設定している
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")
}

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

Spring Actuator でメールサーバをチェックするよう設定する

Spring Actuator でメールサーバの health チェックを行うために application.properties に spring.mail.host=localhost の設定を追加します。

spring.mail.host=localhost

メールサーバを起動せずに demo アプリを起動してみる

メールサーバを起動しない状態で demo アプリを起動すると、Spring Actuator の MailHealthIndicator クラスから警告ログが出力されますがアプリは終了せず起動したままです。

f:id:ksby:20190426072646p:plain f:id:ksby:20190426072804p:plain

Health チェックの画面では mail が黄色アイコンで表示されています。

f:id:ksby:20190426072941p:plain

起動時にメールサーバに接続できなかったら起動を中断するための CustomMailHealthIndicator クラスを作成する

警告ログを出力している org.springframework.boot.actuate.mail.MailHealthIndicator クラスを見ると以下のように実装されています。

f:id:ksby:20190426080800p:plain

実際に health チェックしているのが doHealthCheck メソッドで、this.mailSender.testConnection(); で接続してみて例外が throw されなければ builder.up(); を呼び出してステータスを UP に変更する、という実装でした。

MailHealthIndicator クラスには @Component 等のアノテーションが付与されていないので Bean を定義しているクラスを探したところ、org.springframework.boot.actuate.autoconfigure.mail.MailHealthIndicatorAutoConfiguration クラスで定義されていました。

f:id:ksby:20190427015400p:plain

mailHealthIndicator という名前の Bean が存在しない時だけ MailHealthIndicatorAutoConfiguration クラスで Bean を生成しています。

MailHealthIndicator クラスを継承して CustomMailHealthIndicator クラスを作成し、一番最初の health チェックで例外が throw されたら Web アプリを強制終了させるようにしてみます。src/main/java/com/example/demo/CustomMailHealthIndicator.java クラスを新規作成した後、以下の内容を記述します。

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.mail.MailHealthIndicator;
import org.springframework.context.ApplicationContext;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
@Component("mailHealthIndicator")
public class CustomMailHealthIndicator extends MailHealthIndicator {

    private final ApplicationContext context;

    private final AtomicBoolean isFirstTime = new AtomicBoolean(true);

    public CustomMailHealthIndicator(JavaMailSenderImpl mailSender, ApplicationContext context) {
        super(mailSender);
        this.context = context;
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        try {
            super.doHealthCheck(builder);
        } catch (Exception e) {
            // 一番最初の health チェックでメールサーバに接続できなかった時にはアプリを強制終了させる
            if (isFirstTime.get()) {
                log.error("メールサーバが起動していないので強制終了します", e);
                int exitCode = SpringApplication.exit(context, (ExitCodeGenerator) () -> 1);
                System.exit(exitCode);
            }
            throw e;
        } finally {
            isFirstTime.compareAndExchange(true, false);
        }
    }

}

動作確認

先程と同様にメールサーバを起動しない状態で demo アプリを起動すると、最後に Process finished with exit code 1 のメッセージが出力されてアプリが終了しました。

f:id:ksby:20190427080959p:plain f:id:ksby:20190427081114p:plain

メールサーバ(smtp4dev)を起動してから demo アプリを起動すると、アプリは終了せずに起動したままとなり、

f:id:ksby:20190427081344p:plain

メールサーバを終了させると、health チェックのエラーログが出力されますがアプリは終了せずに起動したままでした。

f:id:ksby:20190427081532p:plain

想定通りの動きです。

履歴

2019/04/27
初版発行。