Spring Boot で STOMP Over WebSocket を試してみる

Spring BootSTOMP Over WebSocketを使って、試しにチャットっぽいものを作ってみました。

f:id:onozaty:20170503222047p:plain

同じルームに接続しているクライアントに、同じメッセージを配信するだけならば、Controllerの定義はいりません。

WebSocketConfigとして、エンドポイントとSimpleBrokerでハンドリングする宛先を定義するだけです。

package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@EnableWebSocketMessageBroker
@Configuration
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpoint");
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
    }
}

画面側では、配信と購読で同じ宛先を指定しておきます。自分で送ったものも受け取ることになります。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>チャット</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css" />
</head>
<body>
  <div class="container">
    <h2>チャット</h2>
    <div class="form-horizontal">
      <div class="form-group">
        <label for="roomName" class="col-sm-2 control-label">ルーム</label>
        <div class="col-sm-2">
          <input id="roomName" type="text" class="form-control" value="example" />
        </div>
        <div class="col-sm-3">
          <button id="connectButton" type="button" class="btn btn-default">接続</button>
          <button id="disconnectButton" class="btn btn-default">切断</button>
        </div>
      </div>
      <div class="form-group">
        <label for="message" class="col-sm-2 control-label">メッセージ</label>
        <div class="col-sm-4">
          <input id="message" type="text" class="form-control" />
        </div>
        <div class="col-sm-2">
          <button id="sendButton" type="button" class="btn btn-default">送信</button>
        </div>
      </div>
      <div class="row">
        <div class="col-sm-4 col-sm-offset-2">
          <ul id="messageList" class="list-unstyled">
          </ul>
        </div>
      </div>
    </div>
  </div>
  <script src="/webjars/jquery/1.12.4/jquery.min.js"></script>
  <script src="/webjars/stomp-websocket/2.3.3-1/stomp.js"></script>
  <script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  <script>
    $(function() {
      var endpoint = 'ws://' + location.host + '/endpoint';
      var subscribePrefix = '/topic/';
      var stompClient = null;

      $('#connectButton').click(function() {

        $("#messageList").empty();

        stompClient = Stomp.over(new WebSocket(endpoint));
        stompClient.connect({}, function() {
          stompClient.subscribe(
              subscribePrefix + $('#roomName').val(),
              function(message) {
                $('#messageList').prepend($('<li>').text(message.body));
              });

          $('#roomName').prop('disabled', true);
          $('#connectButton').prop('disabled', true);
          $('#disconnectButton').prop('disabled', false);
        });
      });

      $('#disconnectButton').click(function() {

        stompClient.disconnect();
        stompClient = null;

        $('#roomName').prop('disabled', false);
        $('#connectButton').prop('disabled', false);
        $('#disconnectButton').prop('disabled', true);
      });

      $('#sendButton').click(function() {
        if (!stompClient) {
          alert('未接続です。');
          return;
        }

        stompClient.send(subscribePrefix + $('#roomName').val(), {}, $('#message').val());
      });
    });
  </script>
</body>
</html>

クライアント間でメッセージをやり取りするといったことが、少しのコードで簡単に実現できますね。

POPSPRING 2017 (2017年3月25日@幕張メッセ)

POPSPRING 2017 幕張公演に、嫁さんと2人で参加してきました。フェスは初参加です。

GOLDチケットだったので、とても余裕もって見れました。最初は前の方にいたのですが、逆に人が密集していてみずらかったので、途中からちょっと後ろ目で見ていました。それでもこんな感じで十分近くに見えました。

11時から21時過ぎまでの長丁場だったので、疲れたらちょっと下がって座って見れるのも良かったです。

POPSPRINGでは、アーティストの写真撮影NGとのことだったので、今回は写真がほとんど撮れませんでした。 海外アーティストだと珍しいなぁと思いましたが、いろんなアーティストが出る都合上、NGにせざるを得ないのかもしれませんね。(周りで結構撮っている人がいたけど、特に注意が入ることもなく、、、)

タイムテーブル

Opeging

会場前から並んで参加しました。GOLDだと入場も優先だったので、会場ぎりぎりでもスムーズに入れました。

DJ SHOTA(@djshotamusic)のOpening DJで、かなりテンションあがりました。早く来たかいがありました。

続くOpening Actは FlowBack、RIRI、FAKY と日本人アーティストが続きましたが、知らなかったアーティストと出会えるのはいいですね。とくにRIRI(@riri_tone)が良かったです。

まだ17歳なのにびっくりなのと、歌のときの大人の雰囲気と、MCのときの可愛さのギャップに、すぐにファンというか、応援したくなりました。可愛くて歌はパワフルなところが、アリアナ・グランデに似てるねと、嫁さんと盛り上がりました。

Jordan Fisher

機器のトラブルでかなり遅れて、Jordan Fisherとなりました。POPSPRINGに出演するということで、初めて知ったアーティストでしたが、All About UsをYouTubeで見て、すぐに好きになりました。

Jordan Fisher - All About Us (Official Video)

ちなみにダンスみて、なぜかUsherを思い出しました。似てるかな、、

パフォーマンスを見て、これからさらに売れるんだろうなぁと確信しました。また見たい!

Da-iCE

ちょうどお昼休憩とっていて、ほとんど見逃してしまいました、、ごめんなさい。

Austin Mahone

昨年に続いてのAustin Mahone。ダンサーさんが色っぽい。

Dirty Work でブルゾンちえみが出てこないかなぁと思いましたが、出てきませんでした(笑)。

最後に発表となったアーティストで、まぁ、Austin最後に持ってこられたら、全てOKになりますよね!(日本人アーティストが多いことで、ちょっと話題になっていましたが、最後のAustinの発表で全て吹き飛んだ気が)

Sabrina Carpenter

Sabrina Carpenter もPOPSPRINGに出演するということで初めて知ったアーティストでしたが、ほんと今となってはすごい好きなアーティストになりました。とくに1stアルバムの曲に好きなのが多いです。

Can’t Blame a Girl for Trying や Eyes Wide Open とか。

Sabrina Carpenter - Eyes Wide Open (Official Video)

ステージのSabrinaはすごい輝いてました。

w-inds.

We Don’t Need To Talk Anymore を初めて聞きましたが、これはいい曲だと思いました。

We Don’t Need To Talk Anymore(MUSIC VIDEO Full ver.+15s SPOT) / w-inds.

DNCE

一番盛り上がったと思います。楽しくて、一番疲れました(笑)。ほんと弾けすぎです。

Shown Mendes

いや、ほんとすごいというか、感動しました。

曲は好きで良く聞いてましたが、ギター一本であんな聞かせるパフォーマンスするなんて。すいません、全然Shownのことわかって無かったです。

ずっとハッピ姿のままいくとも思ってませんでした。ちょっとパフォーマンスとアンマッチな気が。

Fifth Harmony

Fifth Harmonyが一番のお目当てだったので、待ちに待った… といった感じでした。

いろいろな形でのパフォーマンスを見せてくれて、疲れがふっとびました。4人みんな特徴があって、いいグループですよね!(Allyのキュートな声が、一番好きです)

終わりに

自分が知らなかったアーティストに出会えたこともあって、ほんと値段以上の価値を感じたフェスでした。 また来年も行きたいです!!(サマソニにも興味がわいてきています…)

参考リンク

Lombokの@Builder(toBuilder = true)で、オブジェクトの複製を作成する

前回の記事の続きで。

@Builder(toBuilder = true)とすると、オブジェクトからBuilderを生成できます。

どういうときに使えるかというと、オブジェクトの複製を作成して、一部のプロパティのみ変えたいといった場合です。

package com.example.lombok;

import lombok.Builder;
import lombok.ToString;

public class BuilderExample {

    public static void main(String[] args) {

        Customer customer1 = new Customer("Taro", "Urashima", "ura@exmple.com", "111-111-1111");

        System.out.println(customer1);

        Customer customer2 = customer1.toBuilder()
                .lastName("Yamada")
                .build();

        System.out.println(customer2);
    }
}

@Builder(toBuilder = true)
@ToString
class Customer {

    private String firstName;

    private String lastName;

    private String mailAddress;

    private String telephone;
}

実行結果は下記の通りです。lastNameだけが異なるオブジェクトが生成されます。

Customer(firstName=Taro, lastName=Urashima, mailAddress=ura@exmple.com, telephone=111-111-1111)
Customer(firstName=Taro, lastName=Yamada, mailAddress=ura@exmple.com, telephone=111-111-1111)

Lombokで生成されるのは、下記のようなコードになります。(delombokで確認)

class Customer {
    private String firstName;
    private String lastName;
    private String mailAddress;
    private String telephone;

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    Customer(final String firstName, final String lastName, final String mailAddress, final String telephone) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.mailAddress = mailAddress;
        this.telephone = telephone;
    }


    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static class CustomerBuilder {
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String firstName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String lastName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String mailAddress;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String telephone;

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        CustomerBuilder() {
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder firstName(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder lastName(final String lastName) {
            this.lastName = lastName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder mailAddress(final String mailAddress) {
            this.mailAddress = mailAddress;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder telephone(final String telephone) {
            this.telephone = telephone;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public Customer build() {
            return new Customer(firstName, lastName, mailAddress, telephone);
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public java.lang.String toString() {
            return "Customer.CustomerBuilder(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
        }
    }

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static CustomerBuilder builder() {
        return new CustomerBuilder();
    }

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public CustomerBuilder toBuilder() {
        return new CustomerBuilder().firstName(this.firstName).lastName(this.lastName).mailAddress(this.mailAddress).telephone(this.telephone);
    }

    @java.lang.Override
    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public java.lang.String toString() {
        return "Customer(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
    }
}

Lombokの@BuilderでBuilderを簡単に作成する

多数のプロパティを持つクラスを生成するときに、Builderが有用だと思っているのですが、普通に書くとそれなりにコード書く必要があります。そこでLombokの@Builderです。

package com.example.lombok;

import lombok.Builder;
import lombok.ToString;

public class BuilderExample {

    public static void main(String[] args) {

        // 引数が多くなると、どのプロパティに対するものかわかりずらい
        Customer customer1 = new Customer("Taro", "Urashima", "ura@exmple.com", "111-111-1111");

        System.out.println(customer1);

        // Builder使うことによって、どのプロパティに対しての値なのかわかりやすくなる
        Customer customer2 = Customer.builder()
                .firstName("Taro")
                .lastName("Urashima")
                .mailAddress("ura@exmple.com")
                .telephone("111-111-1111")
                .build();

        System.out.println(customer2);
    }
}

@Builder
@ToString
class Customer {

    private String firstName;

    private String lastName;

    private String mailAddress;

    private String telephone;
}

上記によってLombokで生成されるのは、下記のようなコードになります。(delombokで確認)

class Customer {
    private String firstName;
    private String lastName;
    private String mailAddress;
    private String telephone;

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    Customer(final String firstName, final String lastName, final String mailAddress, final String telephone) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.mailAddress = mailAddress;
        this.telephone = telephone;
    }


    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static class CustomerBuilder {
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String firstName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String lastName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String mailAddress;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String telephone;

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        CustomerBuilder() {
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder firstName(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder lastName(final String lastName) {
            this.lastName = lastName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder mailAddress(final String mailAddress) {
            this.mailAddress = mailAddress;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder telephone(final String telephone) {
            this.telephone = telephone;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public Customer build() {
            return new Customer(firstName, lastName, mailAddress, telephone);
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public java.lang.String toString() {
            return "Customer.CustomerBuilder(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
        }
    }

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static CustomerBuilder builder() {
        return new CustomerBuilder();
    }

    @java.lang.Override
    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public java.lang.String toString() {
        return "Customer(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
    }
}

Lombokで生成されたメソッドに対してアノテーションを設定する

毎回調べているような気がするのでメモ。

Lombok+Jackson(Spring MVCやJerseyなどで使ってたり)を使っている場合に、@JsonIgnoreで一部のフィールドを対象外にしたい場合に、privateなフィールドに設定してもうまく動きません。

Lombokで生成したsetterまたはgetterに設定してあげる必要があり、下記のような書き方をします。(java - @JsonIgnore with @Getter Annotation - Stack Overflowより引用)

@Getter
@Setter
public class User {

    private userName;

    @Getter(onMethod = @__(@JsonIgnore))
    @Setter
    private password;
}

ちょっと特殊な書き方なので、いつも忘れます…

なお、Lombokで生成されたメソッドに対してアノテーションを指定する方法は、下記の3パターンになります。

  1. 生成されるメソッドに対して: @Setter(onMethod = @__(@ExampleAnnotation))
  2. 生成されるメソッドの引数に対して: @Setter(onParam = @__(@ExampleAnnotation))
  3. 生成されるコンストラクタに対して: @AllArgsConstructor(onConstructor = @__(@ExampleAnnotation))

Lombokのドキュメントだと、下記に記載があります。

Greasemonkeyでテキストエリアに入力補完を追加する

Redmineのtextile記法で、コードハイライトは<pre><code class="java"></code></pre>のような書き方をするのですが、これを入力するのが面倒になってきたので、Greasemonkeyを使ってテキストエリアで入力補完を行ってみました。

なお、Redmine 3.3 からは、ツールバーにコードハイライト用のボタンが追加されていますので、それを使うことによっても手間は軽減できるかと思います。

実装方法

テキストエリアでの入力補完は、カーソル位置を取るのが面倒なため、他のライブラリを利用します。

いくつかよさそうなものがありましたが、手軽そうなものということで、At.jsを今回は利用しました。

Greasemonkeyの@requireで外部JavaScriptを読み込めるので、cdnjsにあるAt.jsと、At.jsが依存しているCaret.jsというライブラリを読み込むようにしました。At.jsはjQueryに依存していますが、Redmineの各画面でも読み込んでいるので、指定しなくて大丈夫です。

At.js用のスタイルは、直接styleタグとしてヘッダに追加しました。

後は対象のテキストエリアを指定して、入力補完の設定をしていくだけです。 At.jsでは、atwhoという関数に対して、必要な情報を指定することよって、補完が行われるようになります。 とてもシンプルなので、迷うことはありませんでした。

$('.atwho-inputor').atwho({
  at: "@",
  data: ["one", "two", "three"],
}).atwho({
  at: ":",
  data: ["+1", "-1", "smile"]
});

動作イメージ

f:id:onozaty:20170228025858g:plain

スクリプト

ということで出来上がったスクリプトは、下記のようになります。

// ==UserScript==
// @name        Redmine wiki textcomplete
// @namespace   com.enjoyxstudy.redmine.wiki.textcomplete
// @version     1
// @grant       none
// @require     https://cdnjs.cloudflare.com/ajax/libs/at.js/1.5.2/js/jquery.atwho.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/Caret.js/0.3.1/jquery.caret.min.js
// ==/UserScript==
(function($) {
  $('textarea.wiki-edit').atwho({
    at: '<',
    data: [
      {name: 'java', content: '<pre><code class="java">\n</code></pre>'},
      {name: 'sql', content: '<pre><code class="sql">\n</code></pre>'}],
    insertTpl: '${content}',
    suffix: ''
  }).atwho({
    at: '{',
    data: [
      {name: 'collapse', content: '{{collapse(詳細を表示...)\n}}'}],
    insertTpl: '${content}',
    suffix: ''
  }).atwho({
    at: 'a',
    data: [
      {name: 'attachment', content: 'attachment:'}],
    insertTpl: '${content}',
    suffix: ''
  }).atwho({
    at: 'c',
    data: [
      {name: 'commit', content: 'commit:'}],
    insertTpl: '${content}',
    suffix: ''
  });

  $('head').append(
    `<style>
.atwho-view {
    position:absolute;
    top: 0;
    left: 0;
    display: none;
    margin-top: 18px;
    background: white;
    color: black;
    border: 1px solid #DDD;
    border-radius: 3px;
    box-shadow: 0 0 5px rgba(0,0,0,0.1);
    min-width: 120px;
    z-index: 11110 !important;
}

.atwho-view .atwho-header {
    padding: 5px;
    margin: 5px;
    cursor: pointer;
    border-bottom: solid 1px #eaeff1;
    color: #6f8092;
    font-size: 11px;
    font-weight: bold;
}

.atwho-view .atwho-header .small {
    color: #6f8092;
    float: right;
    padding-top: 2px;
    margin-right: -5px;
    font-size: 12px;
    font-weight: normal;
}

.atwho-view .atwho-header:hover {
    cursor: default;
}

.atwho-view .cur {
    background: #3366FF;
    color: white;
}
.atwho-view .cur small {
    color: white;
}
.atwho-view strong {
    color: #3366FF;
}
.atwho-view .cur strong {
    color: white;
    font:bold;
}
.atwho-view ul {
    /* width: 100px; */
    list-style:none;
    padding:0;
    margin:auto;
    max-height: 200px;
    overflow-y: auto;
}
.atwho-view ul li {
    display: block;
    padding: 5px 10px;
    border-bottom: 1px solid #DDD;
    cursor: pointer;
    /* border-top: 1px solid #C8C8C8; */
}
.atwho-view small {
    font-size: smaller;
    color: #777;
    font-weight: normal;
}
    </style>`
  );
})(jQuery);

おわりに

いろいろ候補を追加して、自分好みのものに変えていこうと思っています。

Spring Boot: Scheduledアノテーションを使用して、スケジュールされたタイミングでメソッドを実行する

Scheduledアノテーションを使うと、スケジュールされたタイミングでメソッドを実行することができます。 これで周期実行的なものは、簡単に実装できます。

実装方法

実行したいメソッドにScheduledアノテーションを付けます。

@Component
public class Scheduler {

    @Scheduled(fixedRate = 5000)
    public void doSomething() {
        // 5秒周期で行いたい処理
    }
}

Scheduledアノテーションによる実行を有効とするためには、EableSchedulingアノテーションを付けます。

@SpringBootApplication
@EnableScheduling
public class SchedulerApplication {

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

これだけで周期実行が実装できます。簡単ですね!

Scheduledで指定できるパターン

指定方法は大きく分けて3パターン用意されています。

  1. fixedDelay : 前回タスクの実行完了時点から指定時間後にタスクを実行する。単位はms。
  2. fixedRate : 前回タスクの実行開始時点から指定時間後にタスクを実行する。単位はms。
  3. cron : 指定した周期でタスクを実行する。(Linuxのcronと似た書式)

また、fixedDelayfixedRateでは、初回のタスクの実行開始時間を指定するものとして、initialDelayがあります。

下記のようにすると、初回はアプリケーション起動から30秒後に実行され、その次からは、前回タスクの実行完了から60秒後に実行されることになります。

@Scheduled(fixedDelay = 60000, initialDelay = 30000)
public void doSomething() {
}

cronでは、zoneというフィールドで、cronの起動時間のタイムゾーンを指定できます。(未指定の場合は、デフォルトのタイムゾーン)

下記のようにすると、東京のタイムゾーンで、8時と9時と10時に実行されます。(8時から10時の間で、0秒、0分のタイミングで実行といった指定になっている)

@Scheduled(cron = "0 0 8-10 * * *", zone = "Asia/Tokyo")
public void doSomething() {
}

設定ファイルで指定する

ソース上に固定値で設定するのではなく、設定ファイルに記載することができます。

${設定名}で指定しておいて、

@Scheduled(cron = "${scheduler.cron}")
public void doSomething() {
}

application.properties で設定値を書きます。

scheduler.cron=*/5 * * * * *

fixedDelayfixedRateinitialDelayも、設定値とできるように、fixedDelayStringfixedRateStringinitialDelayStringというものがフィールドとしてあるので、そちらを使えばOKです。

@Scheduled(fixedRateString = "${scheduler.fixed-rate}")
public void doFixedRateString() throws InterruptedException {
}

ユニットテストの際に、周期実行を抑止する

テストの時には、勝手に周期実行が動いて欲しく無い場合もあるかと思います。

fixedDelayStringfixedRateString ならば、テスト時の設定値を変えてとても大きな数字にしておけば、抑止と同等のことができそうですが、cronだと、ぜったいに実行されないタイミングを指定するのは難しそうです。

こういった場合は、EnableSchedulingが指定されないように設定してあげれば回避できます。

下記のようなクラスでSpring Bootを起動しているならば、

@SpringBootApplication
@EnableScheduling
public class SchedulerApplication {

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

テスト用に別の起動クラスを作成し、そちらでは@EnableSchedulingを指定しないようにします。 また、コンポーネントスキャンで、@EnableSchedulingを指定しているものを拾ってしまうと有効になってしまうので、該当のクラスを除外するように指定しておきます。

@SpringBootApplication
@ComponentScan(excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = SchedulerApplication.class))
public class TestApplication {

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

このクラスを@SpringBootTestに指定してあげれば、@EnableSchedulingが指定されずに、周期実行を抑止することが出来ます。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SchedulerTest.TestApplication.class)
public class SchedulerTest {