やりたかったこと
- BeanValidation のプロパティファイル
ValidationMessages.properties
でなく、 Spring のメッセージプロパティに統合したい。- Spring のメッセージプロパティとは?
Accept-Language
ベースでメッセージを国際化したい。
調べた
Spring のメッセージプロパティファイルはどこ?
これはキーワード “site:spring.io i18n message” でググるとすぐ見つかった。
messages.properties
だ。
また、メッセージに関するプロパティは、このページで説明されている通り MessageSourceProperties で確認できる。
例えば cacheDuration
というフィールドがあるので application.properties
ファイルには
spring.messages.cache-duration
という名前で該当値を設定できる(フィールド名は camelCase だが、これに bind するプロパティ名は いわゆるkebab-case が推奨されている)。
Externalized Configurationという仕組みだと思うが、 どうやって実現しているのかは分からん 。 MessageSourceProperties
に ConfigurationProperties
アノテーションが付いているわけでもなし。
デフォルト設定値を調べる
デバッガで追いかけた。 これ本当ならどうやってリファレンス探せば見つかるんだ?
ValidationAutoConfiguration#defaultValidator()
メソッドだ。
プロジェクト名(ディレクトリ名)から想像がつく通り、 Auto-configurationという仕組みだ。
明示的な設定を行っていない場合、フレームワーク側でよしなに設定を行ってくれる。
…が、その設定が気に入らない、というのが今回の問題のひとつの側面だ。
ここで登場するメソッド MessageInterpolatorFactory#getObject()
で、お節介にも BeanValidation 側の MessageInterpolator
を取ってきている。
このため冒頭で記載したとおりデフォルトで ValidationMessages.properties
が使われるようだ。
マジかよ? なんでそんなとこで日和ってんねん!
なおした
今回こうやった
まずはじめに、今回の問題の本質とは無関係だが、fallback 先ロケール設定を変更しておく。デフォルトだとシステムロケール、つまり日本語環境なら messages.properties
でなく messages_ja.properties
にフォールバックしてしまうので直感に反する。
そこで application.properties
ファイルに次を設定。
spring.messages.fallback-to-system-locale=false
Spring Tools 4 for Eclipse なら補完も効くし説明もポップアップ表示される。嬉しい。なお、もしかしたら他の IDE でも同様の機能はあるのかもしらんが、使ったこと無いのでわからない。
さて本題。
上記で登場した auto-configuration であるところの ValidationAutoConfiguration#defaultValidator()
を上書きして自分好みにしてしまえばいい。
@ConditionalOnMissingBean(Validator.class)
が付与されているので、自前で Validator
を提供するメソッドを作ってしまえば良いということだ。
というわけでこんなクラスを作るぞ。
import javax.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class MyConfig {
@Autowired
private MessageSource messageSource;
@Bean
public Validator localValidatorFactoryBean() {
final LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource);
return factoryBean;
}
}
LocalValidatorFactoryBean
は javax.validation.Validator
も org.springframework.validation.Validator
も実装しているが、戻り値の型として使うべきは(@ConditionalOnMissingBean
で指定している型である)前者だ。間違えて後者を使うと想定どおり動作しないやっかいなバグになるぞ。
ちなみに戻り値の型指定、 javax.validation.Validator
の代わりに LocalValidatorFactoryBean
でも動作した。
instanceof
で評価しているのだと思うが確証はない。
さて MessageSource
が登場しているがこれはなにか? わからん 。
ググってたときにたまたま見つけた。
いやもちろん名前通りメッセージリソースなのだが、ここまで見てきたリファレンスでは一切登場していない。
27. InternationalizationとかMessageSourceProperties
の javadoc とかで触れられているべきでは!?
こいつはロケール考慮されているようで、デフォルトだと AcceptHeaderLocaleResolver
を使って 冒頭に記載した希望通りAccept-Language
を考慮してくれるようだがいい加減調べるのに疲れたので裏はとっていない。
多分このへん読めば良いのだろう。
ところで この部分の調査の過程で LocalValidatorFactoryBean #setValidationMessageSourceに解答っぽい記述があることに気づいた。
が、 この文章見て具体的に何やればいいかわからんのだが? そもそも このメソッドの存在をどうやったら見つけられるんだ?
やっかいなことに
Spring(Boot)のやりかたは上に書いた方法だけではないようだ。次のエントリで同じ目的を達成するための方法が説明されている。
WebMvcConfigurerAdapter
(注: 現行バージョンでは代わりに WebMvcConfigurer
)って何やねんどこから出てきた?
どっちで設定すべきやねーん!
追記
ロケールについて
上で端折った i18n について調べ直した。デバッガのステップ実行で。
LocaleContextHolder#getLocale()
でロケールを取得している。
こいつを使っているのが MessageInterpolator
(を実装した実体 LocaleContextMessageInterpolator
の interpolate
メソッド だ。
ServletFilter であるところの RequestContextFilter
で HttpServletRequest#getLocale()
をロケールとして設定している。
したがって、 HTTP リクエストコンテキストでは (補足: なんか Qiita の他の人の記事とかではコンテキストを無視して説明している文章が多いぞ。別に validation は RequestScope だけで行うわけではなかろう)、ロケールは HttpServletRequest#getLocale()
の値となるし、それ以外のコンテキストでもそのコンテキストに応じたロケールを設定してくれていると期待できることが分かった。
WebMvcConfigurer
ここにあった!
何が Spring Boot で何が Spring なのか全然分からん。
いや、よく考えると auto-configuration はあくまで付加的な仕組みであって、そこから行うべきことを考えるのは筋が違うな。
ということは WebMvcConfigurer
で設定するのが本来の姿なのだろうか。
しかし Validation の、国際化の仕組みの設定が WebMvcConfigurer
という名前のものに備わっているというのはどうやって思い至れば良いんだろう?
もしかしたらこういう手順か?
- Spring Boot リファレンスの該当しそうな章 37.Validation を見る。
@Validated
という validation 専用っぽいアノテーションが使われているぞ。- javadoc の記述を読むと Spring MVC という単語が出てるので、validation の仕組みは Spring MVC の機能のひとつなんだ?
- Spring Framework ドキュメントのリストを見ると Spring MVC はWeb Servletに含まれているようだ? Servlet と validation って何か関係があるのか?よく分からんが見てみよう。
- 該当する節 1.10.4. Validationが見つかった!なるほど 設定は
WebMvcConfigurer
で行うのか! - 関係しそうな名前
getValidator()
ってメソッドがあるぞ!
マジでこんなこと考えるの?無理ゲーじゃない?
あとこれから更に LocalValidatorFactoryBean
インスタンスを生成して返すってのに気付くのにもまた一山超える必要がありそうだし。
まとめ
- Spring 全然わからん。
- Qiita のスタイル、バックスラッシュでくくるとハイパーリンク貼ってるのかどうなのかわからんからいまいち。
- Qiita 等で Spring( Boot)解説エントリを書いている人に向けて: 根拠となる公式リファレンス/ソースコードへの参照も含めてほしい。あなたの書いたその実装方法が妥当なのか入門者は確認できない。