Spring Framework Advent Calendar 2011 part.6 - 同一リクエスト内で同じ値を返す Date の実装

Spring Framework Advent Calendar の 6 日目です。本日は夜に更新時間が取れないため、前倒しでの投稿です。内容も簡易版となっております。

さて、時間もあまりないので単刀直入にいきます。同一リクエスト内で同じ値を返す実装と云うのは、つまりこういうことです。

/**
 * システム日付を返すインターフェースです。
 */
public interface DateContext {

    /**
     * システムの現在日時を返します。
     * @return 現在日時
     */
    public Date now();
}
/**
 * リクエストスコープにバインドされた {@link DateContext} です。
 * 同一のリクエスト内では、常に同じ時刻を指します。
 */
@Service
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
public class RequestScopeDateContext implements DateContext {
    public static final String NAME = "requestScopeDateContext";

    private Date now;

    /**
     * メソッドを呼び出したリクエストスコープ内で同一の現在日時を返します。
     */
    @Override
    public synchronized Date now() {
        if (now == null) {
            now = new Date();
        }
        return now;
    }
}
/**
 * {@link DateContext} のデフォルト実装。
 */
@Service
public class DefaultDateContext implements DateContext {

    /**
     * メソッドを呼び出した時点での現在日時を返します。
     * @return 現在日時
     */
    @Override
    public Date now() {
        return new Date();
    }
}

ScopedProxy を利用して RequestScope と Bean のライフサイクルを結びつけている例です。この方式の良いところは、

  • 「毎回、そのときのシステム時刻を返す DateContext」「同一リクエスト内では同じシステム時刻を返す DateContext」といった、時刻を返す機能は同じでも、異なる動作が求められるようなコンポーネントを Bean 定義ファイルやアノテーションで比較的簡単に交換できること。
  • テスト時にモックとの差し替えが容易なこと。
    • 内部で return new Date(); とかやってるコードをテストするのはなかなかしんどい。*1

といったところでしょうか。

なお、@Scope をすでに利用している方は既知の情報ですが、@Scope("request") や @Scope("session") を利用するには web.xml の listener 定義でorg.springframework.web.context.request.RequestContextListener を指定しておく必要があります。

  <listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>

利用する側は、単に DateContext を利用したいサービスに DI してやるだけです。簡単ですね。


で、こんなものが何の役に立つのかという話なのですが、複数のテーブルを同一リクエスト内で更新するときに、更新日時をすべて同じにしたい、といったケースがあったりします。そんなときに、多少役に立つかもしれません。