かんがるーさんの日記

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

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その2 )( Gradle を 3.5 → 4.10 にバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その1 )( 概要 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot 2 では Gradle の 4.x 以降しかサポートされないので、Spring Boot のバージョンを上げる前に Gradle を 3.5 → 4.10 にバージョンアップします。

参照したサイト・書籍

目次

  1. 2.0.x ブランチの作成
  2. gradlew wrapper --gradle-version=4.10 コマンドを実行する
  3. build.gradle を修正する
  4. 動作確認

手順

2.0.x ブランチの作成

master から 2.0.x ブランチを、2.0.x から feature/132-issue ブランチを作成します。

gradlew wrapper --gradle-version=4.10 コマンドを実行する

build.gradle の wrapper タスクの記述を以下のように変更します。

wrapper {
    gradleVersion = "4.10"
    distributionType = Wrapper.DistributionType.ALL
}
  • task wrapper(type: Wrapper) { gradleVersion = '3.5' }wrapper { gradleVersion = "4.10" ... } に変更します。

gradle/wrapper/gradle-wrapper.properties を以下のように変更します。

#Tue May 30 00:33:27 JST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip
  • gradle-3.5-bin.zipgradle-4.10-bin.zip に変更します。

コマンドプロンプトから gradlew wrapper --gradle-version=4.10 コマンドを実行します。最後に「'_CONSOLE' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。」というメッセージが出ますが、無視して構いません。

f:id:ksby:20180829212513p:plain

gradlew --version コマンドを実行して 4.10 にバージョンアップしていることを確認します。

f:id:ksby:20180829212914p:plain

build.gradle を修正する

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その53 )( Gradle を 3.5 → 4.6 へバージョンアップする ) の時の内容を参考に、tasks.withType(FindBugs) { ... } の中に記述していた doFirst { ... } の記述を削除し、以下のように変更します。

tasks.withType(FindBugs) {
    reports {
        xml.enabled = false
        html.enabled = true
    }
}

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

動作確認

clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

f:id:ksby:20180829230306p:plain

履歴

2018/08/29
初版発行。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その1 )( 概要 )

概要

記事一覧はこちらです。

  • 「Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る」で作成した Web アプリケーション ( ksbysample-webapp-lending ) の Spring Boot のバージョンを 1.5.4 → 2.0.8 へバージョンアップします。
  • 進め方は以下の方針とします。
    • Git のブランチは 2.0.x を作成して、そちらで作業します。Spring Boot のバージョンと合わせます。
    • 最初に Gradle のバージョンを 3.5 → 4.x へバージョンアップします。
    • 次に Spring Boot のバージョン番号を 2.0.8 にします。
      • Spring Initializr で 2.0.x のプロジェクトを作成して、修正した方がよさそうな点があれば反映します。
      • Spring IO Platform は 9 April 2019 に EOL を迎えるので、Spring Boot の BOM を利用するように変更します。
      • ライブラリは最新バージョンにアップデートします。
      • build.gradle の記述についても、プラグインの書き方を apply plugin: ... から plugins block に変更したり、それ意外にも build.gradle の書き方で変更した方が良い点を変更します。
    • プロジェクトを build し直してエラーが出る点があれば修正し、まずはここまでで動くようにします。
    • その後で 2.0 系ではこう書くべきという点があるか確認し、変更した方がよいところを修正します。
    • 以下の対応もする予定です。
      • Tomcat connection Pool → HikariCP に変更します。
      • FindBugs → SpotBugs に変更します。
      • Spring Boot Actuator を導入します。
      • Redis, RabbitMQ を Docker に切り替えます。

 
2.0 の Release Notes、Migration Guide はこちらです。

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

Spring Boot 2.0 Migration Guide
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide

履歴

2018/08/29
初版発行。
2018/10/16
* Spring Boot 2.0 Migration Guide のリンクを追加しました。
2019/02/06
* Spring Boot のバージョンアップ後のバージョンを 2.0.4 → 2.0.8 に修正しました。

Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( 大目次 )

  1. その1 ( 概要 )
  2. その2 ( Gradle を 3.5 → 4.10 にバージョンアップする )
  3. その3 ( build.gradle を変更する )
  4. その4 ( AbstractJsonpResponseBodyAdvice を削除し、失敗しているテストを成功させる )
  5. その5 ( checkstyle を 7.8.1 → 8.12 に、PMD を 5.8.1 → 6.7.0 にバージョンアップする )
  6. その6 ( FindBugs 3.0.1 → SpotBugs 3.1.7 に切り替える )
  7. その7 ( src/main/resources/static の下の css や js ファイルにアクセスできない原因とは? )
  8. その8 ( 一旦動作確認し、動作しない点があれば修正する )
  9. その9 ( gradle-errorprone-plugin を 0.0.16 → 0.6 にバージョンアップ。。。しようと思いましたが止めました )
  10. その10 ( Tomcat connection Pool → HikariCP に変更する )
  11. その11 ( HikariCP のコネクションプーリングの情報を JMX で取得できるようにする )
  12. その12 ( Spring Boot Actuator を導入する )
  13. その13 ( Remember Me 認証が使えなくなっていたので調査・修正する )
  14. その14 ( Docker で Prometheus+Grafana の環境を構築して Spring Actuator で収集したメトリックスを表示する )
  15. その15 ( Prometheus+Grafana メモ書き )
  16. その16 ( Gradle を 4.10 → 4.10.2 へ、Spring Boot を 2.0.4 → 2.0.6 へバージョンアップする )
  17. その17 ( Spock を 1.1-groovy-2.4 → 1.2-groovy-2.5 へバージョンアップする )
  18. その18 ( Docker で Redis の環境を構築する(単体サーバ構成)+Spring Actuator の Endpoint の Basic 認証ではセッション情報を生成しないようにする )
  19. その19 ( Docker で Redis の環境を構築する2(Redis Cluster 構成))
  20. その20 ( Docker で Redis の環境を構築する3(Redis を 5.0.1 → 5.0.2 にバージョンアップする+.env の環境変数を使用するよう変更する))
  21. その21 ( Docker で RabbitMQ の環境を構築する )
  22. その22 ( Docker で RabbitMQ の環境を構築する2(RabbitMQ の Clustering 構成) )
  23. その23 ( Docker Network メモ書き+Prometheus の HTTP API でデータを削除する )
  24. 番外編 ( gradle-docker-compose-plugin で test の前に自動で Docker コンテナを起動してみる )
  25. 番外編 ( docker volume メモ書き )
  26. その24 ( Docker で PostreSQL+pgAdmin4+ Flyway の環境を構築する )
  27. その25 ( Docker で PostreSQL+pgAdmin4+ Flyway の環境を構築する2 )
  28. その26 ( Gradle を 4.10.2 → 4.10.3 へ、Spring Boot を 2.0.6 → 2.0.7 へバージョンアップする。。。が Spring Security の bug のため Spring Boot は 2.0.6 へ戻す )
  29. その27 ( ProviderManager#getProviders が DaoAuthenticationProvider を3つ返す原因を調査する )
  30. その28 ( Docker で SMTPサーバ+Webmailクライアント環境を構築する )
  31. その29 ( build.gradle の dependencies から不要な記述を削除する )
  32. その30 ( Redis のクライアントライブラリを Jedis → Lettuce に変更する )
  33. その31 ( Spring Actuator の Basic 認証用ユーザの認証成功時には AuthenticationSuccessEvent イベントが発生しないようにする+いろいろ調整する )
  34. その32 ( Spring Boot を 2.0.6 → 2.0.8 へバージョンアップする )
  35. その33 ( PC の IP アドレスが変更された時に修正するファイルを最小限にする )
  36. その34 ( Docker で複数の Tomcat を起動して動作確認する )
  37. その35 ( Docker で起動しているサーバの TimeZone を Asia/Tokyo に変更する )
  38. 番外編 ( docker logs メモ書き )
  39. その36 ( Windows のサービスから起動して動作確認する )
  40. 感想

Spring Boot + Spring Integration でいろいろ試してみる ( その25 )( Docker Compose でサーバを構築する、SMTP+POP3サーバ編 )

概要

記事一覧はこちらです。

Spring Integration のアプリケーションで使用するサーバを Docker Compose で構築します。

  • SMTPサーバ+POP3サーバを構築します。
  • Dockerイメージは tvial/docker-mailserver を使用します。選定理由は以下の通りです。
    • STARS と PULLS の数が多い。
    • SMTP, POP3, IMAP が使用可能で、各SSL版もサポートされている模様。
    • 中身が Postfix+Dovect でオーソドックスな構成である。
  • SMTPサーバは認証なし、SSLなし、ポート番号は25番を使用。
  • POP3サーバは認証あり、SSLなし、ポート番号は110番を使用。
  • 認証に使用するパスワードは、サーバ側で PLAIN TEXT で保存します。

参照したサイト・書籍

目次

  1. ksbysample-eipapp-dockerserver プロジェクトを作成する
  2. SMTP+POP3 サーバを構築する
    1. docker-mailserver をカスタマイズする設定ファイルを配置するディレクトリを作成する
    2. docker-compose.yml を作成する
    3. Postfix をカスタマイズするための postfix-main.cf を作成する
    4. Dovecot をカスタマイズするための dovecot.cf を作成する
    5. ユーザを登録するための postfix-accounts.cf を作成する
    6. サーバを起動する
    7. 動作確認
  3. SMTP でメールを送信するサンプルを作成する
  4. POP3 でメールを受信するサンプルを作成する

手順

ksbysample-eipapp-dockerserver プロジェクトを作成する

Spring Initializr で作成します。

f:id:ksby:20180826201831p:plain f:id:ksby:20180826202733p:plain f:id:ksby:20180826202823p:plain f:id:ksby:20180826202923p:plain f:id:ksby:20180826203301p:plain

生成された build.gradle は以下のものですが、

buildscript {
    ext {
        springBootVersion = '2.0.4.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'ksbysample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-integration')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencies block を以下のように変更します。

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-integration')
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation('org.springframework.integration:spring-integration-mail')
    implementation("org.apache.commons:commons-lang3")
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}
  • compileimplementationtestCompiletestImplementation に変更します。
  • Spring Integration の Mail Support の機能を使用するので、以下の行を追加します。
    • implementation('org.springframework.integration:spring-integration-mail')
  • メールを送信するのに JavaMailSender Bean を使用したいので、以下の行を追加します。
    • implementation("org.springframework.boot:spring-boot-starter-mail")
  • StringUtils を使用したいので、以下の行を追加します。
    • implementation("org.apache.commons:commons-lang3")

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

また @SpringBootApplication アノテーションが記述されているファイル名が長いので、KsbysampleEipappDockerserverApplication.java → Application.java に変更します。

SMTPPOP3 サーバを構築する

docker-mailserver をカスタマイズする設定ファイルを配置するディレクトリを作成する

プロジェクトの直下に docker/mail-server/config ディレクトリを作成します。

f:id:ksby:20180826210331p:plain

docker-compose.yml を作成する

プロジェクトのルートディレクトリ直下に docker-compose.yml を新規作成し、以下の内容を記述します。

version: '3'

services:
  # docker-mailserver
  # https://hub.docker.com/r/tvial/docker-mailserver/
  # https://github.com/tomav/docker-mailserver
  #
  # 起動した docker-mailserver のコンテナ(mail-server) にアクセスする場合には以下のコマンドを実行する
  # docker exec -it mail-server /bin/sh
  #
  # アカウントのパスワードを SHA512 で作成する場合には、コンテナにアクセスして以下のようにコマンドを実行して生成する
  # doveadm pw -s SHA512-CRYPT -u [メールアドレス(例:tanaka@mail.example.com)] -p [パスワード]
  #
  mail-server:
    image: tvial/docker-mailserver:latest
    container_name: mail-server
    hostname: mail
    domainname: example.com
    ports:
      - "25:25"
      - "110:110"
    volumes:
      - ./docker/mail-server/config/:/tmp/docker-mailserver/
    environment:
      # debug したい場合には以下の行のコメントアウトを解除する
      # - DMS_DEBUG=1
      - ENABLE_SPAMASSASSIN=0
      - ENABLE_CLAMAV=0
      - ENABLE_FETCHMAIL=0
      - ENABLE_FAIL2BAN=0
      - ENABLE_POSTGREY=0
      - ENABLE_POP3=1
    cap_add:
      - NET_ADMIN
      - SYS_PTRACE
    restart: always
  • コンテナ名は mail-server
  • メールのドメイン名は mail.example.com
  • https://github.com/tomav/docker-mailserver の docker-compose.yml のサンプルから主に以下の点を変更しています。
    • PORT は 25番(SMTP)、110番POP3)のみ記述しています。Note: Port 25 is only for receiving email from other mailservers and not for submitting email. You need to use port 465 or 587 for this. と記述されていますが、465番、587番は SSL, TLS 用のポートなので、今回は SSL なしのサンプルのため 25番を使用します。
    • サンプルでは volumes に maildata:/var/mail, mailstate:/var/mail-state が記載されていますが、Windows PC 上のディレクトリにマウントしないことにしたので記述していません。マウントする場合には docker volume create コマンドで maildata, mailstate という名前のボリュームを作成して、サンプルのように記述すればよいはず。
    • 開発環境用としてシンプルに SMTP, POP3 を使用したいだけなので、spamassasin, clamav, fetchmail, fail2ban, postgrey は OFF にします。
    • POP3 を使用するので ENABLE_POP3=1 を追加します。

Postfix をカスタマイズするための postfix-main.cf を作成する

docker/mail-server/config の下に postfix-main.cf を新規作成し、以下の内容を記述します。

mydestination =
smtpd_recipient_restrictions =
  • mydestination と virtual_mailbox_domains に同じドメインが設定されていると warning: do not list domain mail.example.com in BOTH mydestination and virtual_mailbox_domains というログが出るので、mydestination を空にします。
  • 既存の smtpd_recipient_restrictions の設定のままではメール送信時に Helo command rejected: need fully-qualified hostname というエラーが出るため、空にします。

Dovecot をカスタマイズするための dovecot.cf を作成する

docker/mail-server/config の下に dovecot.cf を新規作成し、以下の内容を記述します。

disable_plaintext_auth = no
ssl = no
# debug したい場合には以下の行のコメントアウトを解除する
# auth_verbose = yes
# auth_debug = yes
  • SSL をオフにして、かつ PLAIN TEXT のパスワード送信を受け付けるようにするために disable_plaintext_auth = nossl = no の設定を入れます。この2つの設定がないと POP3 の認証時に Plaintext authentication disallowed on non-secure (SSL/TLS) connections. というエラーが発生します。

ユーザを登録するための postfix-accounts.cf を作成する

tanaka@mail.example.com、suzuki@mail.example.com の2ユーザを作成します。docker/mail-server/config の下に postfix-accounts.cf を新規作成し、以下の内容を記述します。

tanaka@mail.example.com|{PLAIN}xxxxxxxx
suzuki@mail.example.com|{PLAIN}yyyyyyyy

ここまでの作業で docker ディレクトリは以下の構成になります。

f:id:ksby:20180828020508p:plain

サーバを起動する

コマンドプロンプトを起動し docker-compose.yml のあるディレクトリへ移動して docker-compose up -d コマンドを実行して起動します。

f:id:ksby:20180827010033p:plain

IntelliJ IDEA の docker plugin を見ると mail-server コンテナが起動していることが確認できます。

f:id:ksby:20180827010307p:plain

動作確認

telnet で接続して SMTP, POP3 コマンドを実行すれば動作確認できるのですが、Windows には標準では telnet コマンドがありません。「Windows の機能の有効化または無効化」で「Telnet クライアント」がインストール可能ですが、インストールしてもなぜかうまく接続できませんでした。Git for Windows の中を見ても telnet コマンドはありませんでした。

Windows 10 では WSL(Windows Subsystem for Linux)という機能で Ubuntu 18.04 がインストールできますので、Ubuntu から telnet で接続して動作確認します(おそらく Windowstelnet を使うのはこれが一番使いやすいです)。WSL や Ubuntu 18.04 のインストール方法はここでは書きません。

まずは telnet localhost 25 で接続してメールを送信します。

f:id:ksby:20180827012105p:plain

コマンドプロンプトdocker exec -it mail-server /bin/sh コマンドを実行して mail-server コンテナに接続し、/var/mail の下の tanaka@mail.example.com にメールが届いていることを確認します。

f:id:ksby:20180827012323p:plain

最後に telnet localhost 110 で接続して POP3 でメールを受信できることを確認します。

f:id:ksby:20180827012707p:plain

SMTP でメールを送信するサンプルを作成する

Spring Framework のスケジューリング機能を利用して一定時間毎にメールを送信したいので、src/main/java/ksbysample/eipapp/dockerserver/Application.java@EnableScheduling アノテーションを付与します。

package ksbysample.eipapp.dockerserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

src/main/resources/application.properties に以下の内容を記述します。

spring.mail.host=localhost
spring.mail.port=25
# メール送信処理を debug したい場合には以下の行のコメントアウトを解除する
# spring.mail.properties.mail.debug=true

src/main/java/ksbysample/eipapp/dockerserver の下に flow パッケージを新規作成し、その下に MailFlowConfig.java を新規作成して、以下の内容を記述します。

package ksbysample.eipapp.dockerserver.flow;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.mail.MailHeaders;
import org.springframework.integration.mail.MailSendingMessageHandler;
import org.springframework.integration.support.StringObjectMapBuilder;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

@Configuration
public class MailFlowConfig {

    /****************************************
     * メール送信処理のサンプル                 *
     ****************************************/

    private final JavaMailSender mailSender;

    private final AtomicInteger count;

    public MailFlowConfig(JavaMailSender mailSender) {
        this.mailSender = mailSender;
        this.count = new AtomicInteger(0);
    }

    @Bean
    public MailSendingMessageHandler mailSendingMessageHandler() {
        return new MailSendingMessageHandler(this.mailSender);
    }

    @Autowired
    private MailSendingMessageHandler mailSendingMessageHandler;

    /**
     * 受信したメッセージを元にメールを送信する
     *
     * @return {@IntegrationFlow} オブジェクト
     */
    @Bean
    public IntegrationFlow sendMailFlow() {
        return f -> f
                // メッセージ送信に時間がかかるので(1件あたり約10秒)、スレッドを生成してメール送信は別スレッドに処理させる
                .channel(c -> c.executor(Executors.newCachedThreadPool()))
                .filter(Message.class, m ->
                        m.getHeaders().containsKey(MailHeaders.FROM)
                                && m.getHeaders().containsKey(MailHeaders.TO)
                                && m.getHeaders().containsKey(MailHeaders.SUBJECT))
                .log(LoggingHandler.Level.WARN, m -> String.format("★★★ メール送信: %s", m.getPayload()))
                .wireTap(sf -> sf.handle(mailSendingMessageHandler))
                .log(LoggingHandler.Level.WARN, m -> String.format("◇◇◇ メール送信: %s", m.getPayload()));
    }

    /**
     * sendMailFlow へ5秒おきに MailHeaders.* のヘッダーをセットした Message を送信する
     */
    @Scheduled(initialDelay = 5000, fixedDelay = 5000)
    public void sendMessageTask() {
        Map<String, Object> headers = new StringObjectMapBuilder()
                .put(MailHeaders.FROM, "sample@test.co.jp")
                .put(MailHeaders.TO, "tanaka@mail.example.com,suzuki@mail.example.com")
                .put(MailHeaders.SUBJECT, "これはテストです")
                .get();
        MessageHeaders messageHeaders = new MessageHeaders(headers);
        Message<String> message
                = MessageBuilder.createMessage(String.format("count = %d", this.count.incrementAndGet())
                , messageHeaders);
        sendMailFlow().getInputChannel().send(message);
    }

}

動作確認してみます。最初に docker-compose up -d でメールサーバを起動し、メールが何も届いていないことを確認します。

f:id:ksby:20180828010712p:plain

次に作成したアプリケーションを実行してメールを送信します。

f:id:ksby:20180828010940p:plain

メールサーバを見るとメールが届いていることが確認できます。

f:id:ksby:20180828011054p:plain

POP3 でメールを受信するサンプルを作成する

src/main/java/ksbysample/eipapp/dockerserver/flow/MailFlowConfig.javaPOP3 のメール受信の処理を追加します。

    /****************************************
     * メール受信処理のサンプル                 *
     ****************************************/

    /**
     * Pop3MailReceiver {@link MailFlowConfig#pop3MessageSource()} に記述せず Bean で定義する必要がある
     * Bean にしないと受信したメッセージをサーバから削除してくれない
     *
     * @return {@Pop3MailReceiver} オブジェクト
     */
    @Bean
    public Pop3MailReceiver pop3MailReceiver() {
        Pop3MailReceiver pop3MailReceiver
                = new Pop3MailReceiver("localhost", "tanaka@mail.example.com", "xxxxxxxx");
        pop3MailReceiver.setShouldDeleteMessages(true);
        Properties javaMailProperties = new Properties();
        // debug したい場合には以下のコメントアウトを解除する
        // javaMailProperties.put("mail.debug", "true");
        pop3MailReceiver.setJavaMailProperties(javaMailProperties);
        return pop3MailReceiver;
    }

    @Bean
    public MailReceivingMessageSource pop3MessageSource() {
        return new MailReceivingMessageSource(pop3MailReceiver());
    }

    /**
     * 5秒おきにメールを受信してみる
     *
     * @return
     */
    @Bean
    public IntegrationFlow mailRecvByPop3Flow() {
        return IntegrationFlows.from(pop3MessageSource()
                // 5秒おきに最大100件受信する
                , c -> c.poller(Pollers.fixedDelay(5000).maxMessagesPerPoll(100)))
                .<MimeMessage>log(LoggingHandler.Level.ERROR, m -> {
                    try {
                        return String.format("◎◎◎ メール受信: %s"
                                , StringUtils.chomp((String) m.getPayload().getContent()));
                    } catch (IOException | MessagingException e) {
                        throw new RuntimeException(e);
                    }
                })
                .get();
    }

メールサーバを再起動してから(送信されたメールがクリアされます)、アプリケーションを実行するとメールの送信と受信が行われます。

f:id:ksby:20180828015033p:plain

履歴

2018/08/28
初版発行。

IntelliJ IDEA を 2018.2.1 → 2018.2.2 へバージョンアップ

IntelliJ IDEA を 2018.2.1 → 2018.2.2 へバージョンアップする

IntelliJ IDEA の 2018.2.2 がリリースされているのでバージョンアップします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20180826082733p:plain

  3. Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。

    f:id:ksby:20180826082917p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20180826083300p:plain

  6. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2018.2.2 へバージョンアップされていることを確認します。

  7. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    f:id:ksby:20180826083458p:plain

  8. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20180826084044p:plain

  9. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run with Coverage」-「All Tests」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20180826085431p:plain

Spring Boot + Spring Integration でいろいろ試してみる ( その24 )( MessageSource からの Message 送信有無を制御する )

概要

記事一覧はこちらです。

org.springframework.integration.core.MessageSource からデータ取得して処理する処理を任意に stop したり start したりする方法を調べました。そのメモ書きです。

参照したサイト・書籍

目次

  1. ksbysample-eipapp-messagesource-controll プロジェクトを作成する
  2. まずは 1秒毎に hello を出力する処理を書いてみる
  3. MessageSource から null を返せば何も処理されないのか?
  4. MessageSource から Message を返すか否かを 5秒毎に切り替えてみる
  5. poller を stop、start して制御してみる
    1. Control Bus で制御する
    2. org.springframework.integration.endpoint.AbstractEndpoint#stop, start で制御する

手順

ksbysample-eipapp-messagesource-controll プロジェクトを作成する

今回は Spring Initializr で作成します。

f:id:ksby:20180821051057p:plain f:id:ksby:20180821051412p:plain f:id:ksby:20180821051457p:plain f:id:ksby:20180821051601p:plain

生成された build.gradle は以下のものですが、

buildscript {
    ext {
        springBootVersion = '2.0.4.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'ksbysample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-integration')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

lombok の @Slf4j を使いたいので dependencies block を以下のように変更します。Spring Initializr で生成された Gradle プロジェクトの Gradle は 4.8.1 だったので、記述も少し変更しました。

dependencies {
    def lombokVersion = "1.18.2"

    implementation('org.springframework.boot:spring-boot-starter-integration')
    testImplementation('org.springframework.boot:spring-boot-starter-test')

    // for lombok
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")
}

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

@SpringBootApplication が付与されているクラス名が長かったので、短く Application クラスに変更します。

まずは 1秒毎に hello を出力する処理を書いてみる

以下の仕様で実装します。

  • MessageSource は "hello" の文字列が payload にセットされたメッセージを返す。
  • IntegrationFlow を返す Bean の処理は、1秒毎に MessageSource からメッセージを取得してログに出力する。

src/main/java/ksbysample/eipapp/mscontroll の下に FlowConfig.java を新規作成し、以下の内容を記述します。

package ksbysample.eipapp.mscontroll;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.support.MessageBuilder;

@Slf4j
@Configuration
public class FlowConfig {

    @Bean
    public MessageSource<String> helloMessageSource() {
        return () -> MessageBuilder.withPayload("hello").build();
    }

    @Bean
    public IntegrationFlow helloFlow() {
        return IntegrationFlows.from(helloMessageSource()
                , e -> e.poller(Pollers.fixedDelay(1000)))
                .handle((p, h) -> {
                    log.info("★★★ " + p);
                    return null;
                })
                .get();
    }

}

実行すると以下のようにログが出力されます。

f:id:ksby:20180821053654p:plain

MessageSource から null を返せば何も処理されないのか?

MessageSource から Message を返さずに null を返すと処理は何も起きない気がするので、試してみます。helloMessageSource メソッドを以下のように変更します。

    @Bean
    public MessageSource<String> helloMessageSource() {
//        return () -> MessageBuilder.withPayload("hello").build();
        return () -> null;
    }

実行してみると Started Application in 1.981 seconds が出力された後は何も出力されませんでした。

f:id:ksby:20180821055312p:plain

MessageSource から Message を返すか否かを 5秒毎に切り替えてみる

以下の仕様で実装し直してみます。

  • MessageSource は boolean 型のフィールド変数を見て Message を返すか否かを切り替えられるようにする。
  • フィールド変数の値は @Scheduled アノテーションを付与したメソッドで 5秒毎に変更する。

まずは @Scheduled アノテーションが使用できるよう src/main/java/ksbysample/eipapp/mscontroll/Application.java を以下のように変更します。

package ksbysample.eipapp.mscontroll;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
  • @EnableScheduling を追加します。

src/main/java/ksbysample/eipapp/mscontroll/FlowConfig.java を以下のように変更します。

package ksbysample.eipapp.mscontroll;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;

@Slf4j
@Configuration
public class FlowConfig {

    private boolean messageSourceControlFlg = true;

    @Bean
    public MessageSource<String> helloMessageSource() {
        return () -> messageSourceControlFlg ? MessageBuilder.withPayload("hello").build() : null;
    }

    @Bean
    public IntegrationFlow helloFlow() {
        return IntegrationFlows.from(helloMessageSource()
                , e -> e.poller(Pollers.fixedDelay(1000)))
                .handle((p, h) -> {
                    log.info("★★★ " + p);
                    return null;
                })
                .get();
    }

    @Scheduled(initialDelay = 5000, fixedDelay = 5000)
    public void messageSourceControlFlgChanger() {
        messageSourceControlFlg = !messageSourceControlFlg;
        log.warn(String.valueOf(messageSourceControlFlg));
    }

}
  • private boolean messageSourceControlFlg = true; を追加します。
  • helloMessageSource Bean の処理を return () -> MessageBuilder.withPayload("hello").build();return () -> messageSourceControlFlg ? MessageBuilder.withPayload("hello").build() : null; に変更します。
  • messageSourceControlFlgChanger メソッドを追加します。@Scheduled アノテーションを付与して 5秒毎に実行されるようにします。

実行すると 5秒間 hello のログが出力されて、true → false に変わると5秒間何も起きず、false → true に変わると再び hello が出力されるようになりました。やりたかったことは実現できました。

f:id:ksby:20180821060823p:plain

poller を stop、start して制御してみる

MessageSource ではなく、poller の方の処理を stop, start する方法もあります。

Control Bus で制御する

Spring Integration には Control Bus という Component の動作を制御するための機能が用意されており、これを利用することで poller の動作を制御できます。

src/main/java/ksbysample/eipapp/mscontroll/FlowConfig.java を以下のように変更します。

package ksbysample.eipapp.mscontroll;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowDefinition;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.scheduling.annotation.Scheduled;

@Slf4j
@Configuration
public class FlowConfig {

    private boolean messageSourceControlFlg = false;

    @Bean
    public MessageSource<String> helloMessageSource() {
        return () -> MessageBuilder.withPayload("hello").build();
    }

    @Bean
    public IntegrationFlow helloFlow() {
        return IntegrationFlows.from(helloMessageSource()
                // poller に .id("xxx") を付与すると、Control Bus に "@xxx.stop()", "@xxx.start()" の SpEL のメッセージ
                // を送信することで poller の処理を停止・開始することができるようになる
                , e -> e.poller(Pollers.fixedDelay(1000))
                        .autoStartup(messageSourceControlFlg)
                        .id("helloPollingAdapter"))
                .handle((p, h) -> {
                    log.info("★★★ " + p);
                    return null;
                })
                .get();
    }

    @Bean
    public IntegrationFlow controlBus() {
        return IntegrationFlowDefinition::controlBus;
    }

    @Scheduled(initialDelay = 5000, fixedDelay = 5000)
    public void messageSourceControlFlgChanger() {
        messageSourceControlFlg = !messageSourceControlFlg;
        String payload = messageSourceControlFlg ? "@helloPollingAdapter.start()" : "@helloPollingAdapter.stop()";
        Message operation = MessageBuilder.withPayload(payload).build();
        controlBus().getInputChannel().send(operation);
        log.warn(payload);
    }

}
  • messageSourceControlFlg の初期値を true → false に変更します。
  • helloMessageSource メソッド内の処理を return () -> messageSourceControlFlg ? MessageBuilder.withPayload("hello").build() : null;return () -> MessageBuilder.withPayload("hello").build(); に変更します。
  • helloFlow メソッド内の処理で、e -> e.poller(...) の後に .autoStartup(messageSourceControlFlg).id("helloPollingAdapter") を追加します。
  • controlBus Bean を追加します。
  • messageSourceControlFlgChanger メソッド内の処理を Control Bus にメッセージを送信して停止・再開する処理に変更します。

実際に動作させてみると、アプリが起動して 5秒経過した後に started helloPollingAdapter のログが出力されて hello の文字が出力されはじめ、5秒後に stopped helloPollingAdapter のログが出て hello の文字が出なくなります。

f:id:ksby:20180822222408p:plain

org.springframework.integration.endpoint.AbstractEndpoint#stop, start で制御する

実は e -> e.poller(...) の後に追加した .id("helloPollingAdapter") の名前で Bean が生成されているので、その Bean を取得して AbstractEndpoint#stop, AbstractEndpoint#start メソッドを呼び出すことで制御することもできます。

src/main/java/ksbysample/eipapp/mscontroll/FlowConfig.java を以下のように変更します。

package ksbysample.eipapp.mscontroll;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;

@Slf4j
@Configuration
public class FlowConfig {

    private final ApplicationContext context;

    public FlowConfig(ApplicationContext context) {
        this.context = context;
    }

    @Bean
    public MessageSource<String> helloMessageSource() {
        return () -> MessageBuilder.withPayload("hello").build();
    }

    @Bean
    public IntegrationFlow helloFlow() {
        return IntegrationFlows.from(helloMessageSource()
                , e -> e.poller(Pollers.fixedDelay(1000))
                        .autoStartup(false)
                        .id("helloPollingAdapter"))
                .handle((p, h) -> {
                    log.info("★★★ " + p);
                    return null;
                })
                .get();
    }

    @Scheduled(initialDelay = 5000, fixedDelay = 5000)
    public void messageSourceControlFlgChanger() {
        SourcePollingChannelAdapter adapter
                = (SourcePollingChannelAdapter) context.getBean("helloPollingAdapter");
        if (adapter.isRunning()) {
            adapter.stop();
        } else {
            adapter.start();
        }
    }

}
  • private boolean messageSourceControlFlg = false; を削除します。
  • private final ApplicationContext context; を追加し、コンストランクタで DI するようにします。
  • helloFlow メソッド内の処理で、.autoStartup(messageSourceControlFlg).autoStartup(false) に変更します。
  • controlBus Bean を削除します。
  • messageSourceControlFlgChanger メソッド内の処理を helloPollingAdapter Bean を取得して AbstractEndpoint#stop, AbstractEndpoint#start メソッドを呼び出して停止・再開する処理に変更します。

実行すると Control Bus の時と同じ動作になります。

f:id:ksby:20180822224856p:plain

履歴

2018/08/21
初版発行。
2018/08/22
* poller を stop、start して制御してみる を追加した。

Windows 7 PC → Windows 10 PC へ移行する ( 最後にメモ書き )

記事一覧はこちらです。

  • 使用している Windows 7 PC と Windows 10 PC は物理的なディスプレイのサイズがほとんど同じで、ディスプレイ上に表示されている内容も同じだったのですが、解像度を見ると前者は 1280 x 800 で、後者は 1920 x 1200 となっており、Windows 10 PC では 150% に拡大されていました。「高DPIデバイス」というものらしいです。

    解像度が高いならもっと表示できるようになっても構わないなと思い、150% → 100% にしてみたらさすがに文字が小さすぎて見えず。125% だとちょうど良さそうだったのでこれにしたのですが、今度は Q-Dir 等のソフトで文字がにじむ現象が見られました。調べたら古いソフトで高DPIに対応していないとこうなるらしい。ソフトの exe のプロパティで設定をするときれいに表示させるようにできるので、いくつか設定しました。画面が広く表示されるようになったのは嬉しいのですが、少し面倒そうです。

  • これまで Google IME を利用していたのですが、拡大率を 125% に変更したら変換候補のウィンドウが入力中の文字を隠すように表示されるようになって Microsoft IME に切り替えました。Microsoft IME にすることはもうないだろうな、と思っていたので意外です。まさかこんな理由で切り替えることになるとは。。。 Microsoft IME を使ってみると、変換候補も結構賢くなっていて Google IME とそう変わらない感じです。

  • 画像の編集にペイントを使用していましたが、高DPIに対応していなくて使い物になりません(画像を貼り付けても小さくなります)。代替ソフトをいろいろ調べてみましたが paint.net にしました。

  • IntelliJ IDEA の Grep Console Plugin が便利です。特に WARN のログが気づきにくいのですが、色が変わって表示されるので簡単に分かるようになりました。

  • IntelliJ IDEA にインストール済の Plugin を見ていたら Struts 1.x を見つけました。こんなプラグインもあったんだとか、使っていない Plugin(見てると結構ありそう)を整理したら IntelliJ IDEA が軽快に動作するようになるのかなとか思いましたが、他にもやりたいことが山積みなので今のままですね。

  • Windows 10 になって、やっと Docker for Windows を入れることが出来ました。IntelliJ IDEA でも Docker サポートされているし、今後はサーバは Docker で環境作って楽ができるはず! でも最近の流れだと Docker なんて使えて当たり前で、Kubernetes とかを覚えていきたいところです。まあ、マイペースで行きましょう。

次は ksbysample-webapp-lending の Spring Boot 1.5 → 2.0 バージョンアップを行います(たぶん)。