Spring 4.3 キャッシュ関連の主な変更点

今回は、Spring Framework 4.3の変更点紹介シリーズの第3回で、キャッシュ関連の変更点を紹介します。

# シリーズ

* 第１回：[Spring 4.3 DIコンテナ関連の主な変更点](http://qiita.com/kazuki43zoo/items/172d132ff8f4ba098888)
* 第２回：[Spring 4.3 データアクセス関連の主な変更点](http://qiita.com/kazuki43zoo/items/bc036b433444f5c33dc4)
* 第４回：[Spring 4.3 JMS関連の主な変更点](http://qiita.com/kazuki43zoo/items/f0142ebe76dbe873bd5d)
* 第５回：[Spring 4.3 Web関連の主な変更点](http://qiita.com/kazuki43zoo/items/53d2ca25c174ff618331)
* 第６回：[Spring 4.3 WebSocket関連の主な変更点](http://qiita.com/kazuki43zoo/items/f2eac11f94e2afc7c964)
* 第７回(最終回)：[Spring 4.3 テスト関連の主な変更点](http://qiita.com/kazuki43zoo/items/6b6ecafd8e69a54e4803)

# 動作検証環境

* Spring Framework 4.3.0.RELEASE
* Spring Boot 1.4.0.BUILD-SNAPSHOT (2016/6/11時点)

# Caching Improvements

今回は、キャッシュ関連(`@Cacheable`)の主な変更点をみていきます。

| No | キャッシュ関連の主な変更点 |
| --- | --- |
| 1 | マルチスレッド下での同時アクセス時に、キャッシュデータのロード処理を同期化できるようになります。 また、この変更に伴い`Cache`インターフェースに`get(Object key, Callable<T> valueLoader)`メソッドが追加されています。|
| 2 | `@Cacheable`などキャッシュ制御用アノテーションの`condition`属性に指定するSpEL中で、DIコンテナに登録されているBeanを参照できるようになります。|
| 3 | `ConcurrentMapCache`で管理するオブジェクトをシリアライズできるようになります。 |
| 4 | `@Cacheable`などのキャッシュ制御用アノテーションをメタアノテーションとして利用できるようになります。 |

# キャッシュデータのロード処理を同期化できる :+1: 

Spring 4.3から、`@Cacheable`に`sync`属性が追加され、`true`を設定するとキャッシュデータのロード処理が同期化できるようになります。ここで注意しておきたいのは、`sync`属性に指定した値はあくまで「ヒント」であり、実際に同期化するかは`org.springframework.cache.Cache`の実装クラスや利用するライブラリの実装に委ねられているということです。
ちなみに・・・Spring Frameworkが提供する`ConcurrentMap`ベースの`Cache`実装(`ConcurrentMapCache`)は、`Cache`オブジェクト単位で`synchronized`する実装になっています。なお、`sync`属性のデフォルト値は`false`(同期化しない)です。

なお、以降の説明では、キャッシュ実装として`ConcurrentMapCache`を使用する前提で説明をします。ちなみに、`@Cacheable`(アノテーション駆動のキャッシュ制御)を利用する場合は、最低限以下のBean定義が必要になります。

```java
@EnableCaching // アノテーション駆動のキャッシュ制御を有効にする
@Configuration
public class CacheConfig {
    @Bean
    CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("configs");  // CacheManagerをBean定義する
    }
}
```


## データのロード処理を同期化しない(デフォルト)

同期化しない場合は、Spring 4.2と何もかわりません。

```java
@Service
@CacheConfig(cacheNames = "configs")
public class ConfigService {
    @Cacheable // sync属性を省略 or sync = false
    public Config get(String key) {
        // ...
    }
}
```

複数スレッドから同時にアクセスがあった場合は、キャッシュキー(メソッドの引数)が一緒でも同時にメソッドが呼び出される可能性があります。なお、実際にキャッシュされるのは、**メソッドが先に呼びだされたスレッドの返り値**になります。

![spr43-cacheable-sync-false.png](https://qiita-image-store.s3.amazonaws.com/0/117313/0e090c09-2d4a-1ed1-da87-7d5ede68f726.png)

## データのロード処理をを同期化する

Spring 4.3から追加された`@Cacheable`の`sync`属性を`true`にするだけです。

```java
@Cacheable(sync = true) // sync属性をtrueにする
public Config get(String key) {
    // ...
}
```

キーに対応するオブジェクトがキャッシュされていない場合は、`Cache`オブジェクト自体を同期化(`synchronized`)してからデータをロードするメソッド(`@Cacheable`メソッド)を呼び出します。

![spr43-cacheable-sync-true.png](https://qiita-image-store.s3.amazonaws.com/0/117313/abe434c5-6163-d0a1-58ff-45e112c20bd1.png)

同期化する場合は、同期化の範囲を理解しておいた方がよいでしょう。`ConcurrentMapCache`の実装では、同期化の範囲がキーではなく、`Cache`オブジェクト自体になります。これをRDBMSに置き換えると、同期化の範囲が「行」ではなく「テーブル」になるイメージです。

なお、キャッシュデータのロード処理を同期化する場合は、以下がサポートされないので注意してください。

* `@Cacheable`の`unless`属性の利用
* 複数キャッシュの指定
* 他のキャッシュ操作(`@CachePut`, `@CacheEvict`など)との併用


# `@Cacheable`などの`condition`属性でBeanを参照ができる :+1: 

Spring 4.3から、`@Cacheable`, `@CachePut`, `@CacheEvict`のcondition属性に指定するSpEL中で、DIコンテナに登録されているBeanを参照できるようになります。 この改善により、キャッシュ操作の対象データを絞り込む条件をダイナミックに切り替えることができます。なお、SpELからBeanを参照する方法は「[@EventListenerのcondition属性でBeanを参照ができる](http://qiita.com/kazuki43zoo/items/172d132ff8f4ba098888#eventlistener%E3%81%AEcondition%E5%B1%9E%E6%80%A7%E3%81%A7bean%E3%82%92%E5%8F%82%E7%85%A7%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B-1)」をごらんください。


# `ConcurrentMapCache`で管理するオブジェクトをシリアライズできる :+1: 

Spring 4.3から、`ConcurrentMapCache`で管理するオブジェクトをシリアライズすることができるようになります。この改善により、キャッシュ内で管理しているオブジェクトと、キャッシュから返却するオブジェクトを別のインスタンスにすることができます。
たとえば、キャッシュから取得したオブジェクトの状態が変更されたとしても、キャッシュ内で管理しているオブジェクトの状態を変更したくない場合に有効な仕組みです。ただし、キャッシュからオブジェクトを取得する度に、byte配列をオブジェクトにするデシリアライズ処理が実行されるため、同じインスタンスを返却する方法に比べると、処理コストがかかることは意識しておきましょう。

キャッシュするオブジェクトをシリアライズする場合は、`ConcurrentMapCacheManager`の`storeByValue`プロパティに`true`を指定すればよいだけ・・・だと思ったのですが・・・、実際に動かしてみるとシリアライズされませんでした・・・ :cry: 。→ 【2016/5/31】 Spring JIRAのIssue([SPR-14314](https://jira.spring.io/browse/SPR-14314)）にて修正されました :smile: 

```java:シリアライズ機能を有効にするためBean定義例
@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    CacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("configs");
        cacheManager.setStoreByValue(true); // storeByValueプロパティをtrueにする
        return cacheManager;
    }
}
```

なお、この機能を使用する場合は、キャッシュするクラスに`java.io.Serializable`を実装しておく必要があります。

```java
package com.example;

import java.io.Serializable;

public class Config implements Serializable { // Serializableを実装する
    private String key;
    private String value;
    // ...
}
```

# `@Cacheable`などをメタアノテーションとして使用できる :+1: 

Spring 4.3から、`@Cacheable`, `@CacheEvict`, `@CachePut`, `@Caching`をメタアノテーションとして利用できるようになります。
どういう切り口でカスタムアノテーションを作るかは自由ですが、ここでは、同期化が必要になるキャッシュを表現するカスタムアノテーションを使ってみます。同期化が必要になるのは・・・おそらく一貫性の担保が必要なキャッシュだと思うので、「`@ConsistencyCacheable`」という名前にしてみます。

```java
@Cacheable(sync = true) // メタアノテーションとして@Cacheableを指定
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConsistencyCacheable {
}
```

`@Cacheable(sync = true)`ではなく、作成したカスタムアノテーション(`@ConsistencyCacheable`)を使用します。

```java
@ConsistencyCacheable // カスタムアノテーションを指定
public Config get(String key) {
    // ...
}
```

# まとめ

今回は、キャッシュ関連の主な変更点を紹介しました。データのロード処理の同期化やオブジェクトのシリアライズ化のサポートなど、アプリケーションの安全性を高める仕組みが導入されたことで、エンタープライズアプリケーション開発での実用性が増した感じがします。次回は、「JMS関連の主な変更点」を紹介する予定です。


# 参考サイト
* http://docs.spring.io/spring/docs/4.3.0.RELEASE/spring-framework-reference/htmlsingle/

# 備考
## オブジェクトのシリアラズ化の挙動

Spring 4.3から`ConcurrentMapCache`で管理するオブジェクトをシリアライズできるように改善されたが、単純に`sotreByValue`プロパティに`true`を指定しても機能が有効にならなかった・・・。バグの疑いがあるため、[SPR-14314](https://jira.spring.io/browse/SPR-14314)を作成しておいた。
↓
【2016/5/31】
修正されました。