SpringのMessageSourceをリロードする

SpringのMessageSourceはプロパティファイルを読み取ってメッセージを取得することができます。
プロパティファイルを変更した場合、アプリケーションを再起動すればMessageSourceに反映されるのですが、アプリケーションを起動した状態で読み込むことができるのか調べてみました。

# 結論
* `ResourceBundleMessageSource`、`ReloadableResourceBundleMessageSource`のどちらを利用しても再読込させることが可能
* どちらのクラスもキャッシュの生存期間を指定でき、その期間超えた場合はファイルを再読込する
* ただし、任意のタイミングで再読込したいなら`ReloadableResourceBundleMessageSource`を使うべし

# 各MessageSourceについて

## ResourceBundleMessageSource
JavaのResourceBundleを利用してプロパティファイルからメッセージを取得することができます。
キャッシュの生存期間を指定することができ、その期間を超えた場合はプロパティファイルを再度読み込みます。

ただし、手動でキャッシュを削除するようなメソッドは有していません。

## ReloadableResourceBundleMessageSource
ResourceBundleという名前がついていますが、JavaのResourceBundleは利用しておらず、Spring側でメッセージ取得処理等を作り込んでいます。
こちらもキャッシュの生存期間を指定することができます。
また、キャッシュをクリアするメソッドがあるため、任意のタイミングでキャッシュを破棄できます。

なお、こちらのクラスではプロパティファイルだけでなく、XMLファイルを読み込むこともできます。

## 余談
Java5まではResourceBundleにキャッシュを制御する機能がなかったようです。
そのため、当時は`ReloadableResourceBundleMessageSource`でしか再読込できなかったようです。

# 検証

## 環境
* Spring Boot 2.0.6.RELEASE (SpringFramework 5.0.10.RELEASE)
* Java8

## プロジェクトの準備

```xml:pom.xml(抜粋)
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.6.RELEASE</version>
	<relativePath/>
</parent>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>
```

## メッセージ定義ファイルの作成
プロパティファイルを作成します。

```properties:message.properties
hoge=hogehoge
```

再読込の確認をするために、これらのファイルはSpring Bootプロジェクトの外に配置します。
（私の場合は `E:\test` に格納しました。）

## その他
MessageSourceをBean定義します。

```java
@Bean
MessageSource resourceBundleMessageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.addBasenames("message");
    messageSource.setCacheSeconds(10);
    return messageSource;
}

@Bean
MessageSource reloadableResourceBundleMessageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.addBasenames("classpath:message");
    return messageSource;
}
```

`ResourceBundleMessageSource`のキャッシュ生存期間は10秒に設定しました。
`ReloadableResourceBundleMessageSource`は設定していないのでデフォルト値の-1となっており、常にキャッシュを利用します。
また、クラスパス上から取得する場合はbeasenameに`classpath:`というprefixが必要です。

動作確認用のControllerは以下のような感じで。

```java
@RestController
public class MessageController {

    @Autowired
    @Qualifier("resourceBundleMessageSource")
    MessageSource resourceBundleMessageSource;

    @Autowired
    @Qualifier("reloadableResourceBundleMessageSource")
    MessageSource reloadableResourceBundleMessageSource;

    @GetMapping("/")
    public Map<String, String> index() {
        Map<String, String> map = new HashMap<>();
        map.put("resouceBundleMessageSource:hoge",
                resourceBundleMessageSource.getMessage("hoge", null, Locale.getDefault()));
        map.put("reloadableResourceBundleMessageSource:hoge",
                reloadableResourceBundleMessageSource.getMessage("hoge", null, Locale.getDefault()));

        return map;
    }

    @GetMapping("/clear-cache")
    public Map<String, String> clearCache() {
        ((ReloadableResourceBundleMessageSource) reloadableResourceBundleMessageSource).clearCache();
        return index();
    }

}
```

## 動作確認
起動時にプロパティファイルを配置したディレクトリをクラスパスに追加する必要があります。
実はこのやり方を調べるのに一番時間がかかりました・・・。

Spring Boot Maven Pluginを利用する場合は以下のコマンドで実行すればOKです。

```
mvn spring-boot:run -Dspring-boot.run.folders="E:\test"
```

なお、起動引数ではなくpom.xmlに設定する場合は以下のような感じ。

```xml:pom.xml
<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<configuration>
		<folders>
			<folder>E:\test</folder>
		</folders>
	</configuration>
</plugin>
```

Executable Jarの場合は・・・、話が逸れるのでそのうち別記事にしようと思います。

起動後、http://localhost:8080/ にアクセスすると、以下のように表示されます。

```
curl http://localhost:8080
{"reloadableResourceBundleMessageSource:hoge":"hogehoge","resouceBundleMessageSource:hoge":"hogehoge"}
```

アプリケーションは起動した状態でプロパティファイルを編集します。

```properties:message.properties
hoge=hogehoge!!!!
```

もう一度http://localhost:8080/ にアクセスすると、`ResourceBundleMessageSource`はリロードされていることが確認できます。
（前回プロパティファイルを読み込んでから、cacheSecondsで設定した秒数が経過している必要があります。）

```
curl http://localhost:8080
{"reloadableResourceBundleMessageSource:hoge":"hogehoge","resouceBundleMessageSource:hoge":"hogehoge!!!!"}
```

つぎは、http://localhost:8080/clear-cache にアクセスすると、`ReloadableResourceBundleMessageSource`もリロードされることを確認できます。

```
curl http://localhost:8080/clear-cache
{"reloadableResourceBundleMessageSource:hoge":"hogehoge!!!!","resouceBundleMessageSource:hoge":"hogehoge!!!!"}
```

以上のように、キャッシュの生存期間を指定してファイルを再読込したり、意図的にキャッシュを破棄して再読込させることができます。