S2Cachingを使ってみよう

Seasar2徹底入門で紹介しようと思い、途中まで原稿も書いていたS2Cachingについてですが、せっかくなのでこのブログで紹介しておこうと思います。
S2CachingはAOPで任意のメソッドの呼び出しをインターセプトし、戻り値をキャッシュするための機能を提供します。バックエンドのキャッシュライブラリとしてEhcacheを使用しています。
S2Cachingを使うには以下のURLからs2caching-0.1-SNAPSHOT.jarをダウンロードし、クラスパスに追加します。

また、その他にEhcacheの動作に必要なJARファイルと、S2Cachingが内部的に使用しているCommons-Langもクラスパスに追加する必要があります。Ehcacheは以下のURLからダウンロードすることができます。

diconファイルに以下のようにインターセプターを登録します。

<component name="cacheManager" class="net.sf.ehcache.CacheManager">
  @net.sf.ehcache.CacheManager@getInstance()
  <destroyMethod name="shutdown" />
</component>
<component class="org.seasar.caching.interceptor.CallCacheInterceptor" 
           name="cacheInterceptor">
  <arg>cacheManager</arg>
  <arg>"cache-01"</arg>
</component>

また、Ehcacheの設定を行うためにehcache.xmlという設定ファイルをクラスパスルートに作成する必要があります。ehcache.xmlの例を以下に示します。defaultCacheだけ定義していますが、キャッシュごとに設定を変えたい場合は別々に定義することもできるみたいです。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="true" monitoring="autodetect"
         dynamicConfig="true">

    <diskStore path="java.io.tmpdir"/>
    
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            diskSpoolBufferSizeMB="30"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            statistics="false" />

</ehcache>

一応必要最低限の設定にしたつもりですが、Ehcacheは使ったことがないのでこの設定で正しいのかどうかはわかりません。ehcache.xmlの詳細な設定については以下のURLを参照してください(機会があれば翻訳してみたいです)。

長くなりましたが以上で準備は完了です。cacheInterceptorを任意のメソッドに適用することでメソッドの戻り値をキャッシュすることができます。ただし、戻り値をキャッシュ可能なメソッドには以下の制約があります。

  • 適用先メソッドの戻り値の型が Serializable であること
  • 適用先メソッドの引数の型すべてが Serializable であること

S2Cachingはキャッシュされた値を返却する場合、シリアライズによって複製したオブジェクトを返却します。このため、返却されたオブジェクトに対して破壊的な操作を行ってもキャッシュされたエントリには影響ありません(逆にキャッシュされたオブジェクトを変更することはできません)。
また、S2Cachingはキャッシュのキーとして以下を用います。つまり、同一のメソッドに対し、引数の値が一致した場合にキャッシュされたエントリが返却されるということになります。このため、引数のパターンが多いメソッドに対して適用してもキャッシュ効率は非常に悪いので注意してください。

  • 適用先オブジェクトのSystem.identityHashCode値
  • 適用先オブジェクトのClassオブジェクト
  • 適用先メソッドの名前
  • 適用先メソッドの引数列の型
  • 呼び出しに用いられた引数の値

S2Cachingではキャッシュを削除するための機能を提供していません。キャッシュエントリを削除するにはEhcacheのAPIを直接呼び出す必要があります。

CacheManager cm = SingletonS2Container.getComponent(CacheManager.class);
// 全部削除
cm.removeAll();
// キャッシュを指定して削除
cm.getCache("cache-01").removeAll()

全部もしくはキャッシュ単位でしか削除できないため(キーを指定して1エントリずつ削除することももちろん可能ですが、キーはS2caching側で自動生成しているのであまり現実的ではないかと…)、キャッシュの削除タイミングが異なる場合はあらかじめ削除タイミングにあわせてキャッシュと、それに対応するインターセプターを複数定義しておく必要があります。それでもそれほど細かい制御はできないですね。
個人的な感覚としては、特に削除に関して大雑把な制御しかできないため、実際のプロジェクトで利用するには用途が限られるのではないかと思います。アプリケーションスコープに格納するような、変更のないマスタデータのキャッシュなどであれば使えるのではないでしょうか。
2007年で開発が止まっているのも気になりますが、Seasar 2.3時代に作られたプロダクトであるためか、アノテーションでのインターセプターの適用などに対応していないなど、使いにくい部分も見受けられます。使う場合は自分で使いやすいよう手を入れて使うのがよいかもしれません。
あと、S2Cachingというよりも、むしろEhcacheの設定や挙動をしっかりと把握しておくことが大切です。通常のライブラリと違ってキャッシュライブラリはメモリとディスクの使い分け、最大キャッシュ数やアルゴリズムクラスタリングなどかなり細かく設定を行うことができます。ehcache.xmlで設定できる内容をきちんと理解して適切に設定を行う必要があります。