Help us understand the problem. What is going on with this article?

Spring Validator(でラップされたBean Validation)のメッセージをi18nしたときの覚え書き

やりたかったこと

  • 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という仕組みだと思うが、 どうやって実現しているのかは分からんMessageSourcePropertiesConfigurationProperties アノテーションが付いているわけでもなし。

デフォルト設定値を調べる

デバッガで追いかけた。 これ本当ならどうやってリファレンス探せば見つかるんだ?

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;
    }
}

LocalValidatorFactoryBeanjavax.validation.Validatororg.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(を実装した実体 LocaleContextMessageInterpolatorinterpolate メソッド だ。

ServletFilterであるところの RequestContextFilterHttpServletRequest#getLocale() をロケールとして設定している。

したがって、 HTTPリクエストコンテキストでは (補足: なんかQiitaの他の人の記事とかではコンテキストを無視して説明している文章が多いぞ。別にvalidationはRequestScopeだけで行うわけではなかろう)、ロケールは HttpServletRequest#getLocale() の値となるし、それ以外のコンテキストでもそのコンテキストに応じたロケールを設定してくれていると期待できることが分かった。

WebMvcConfigurer

ここにあった!

何がSpring Bootで何がSpringなのか全然分からん

いや、よく考えると auto-configuration はあくまで付加的な仕組みであって、そこから行うべきことを考えるのは筋が違うな。
ということは WebMvcConfigurer で設定するのが本来の姿なのだろうか。
しかし Validationの、国際化の仕組みの設定が WebMvcConfigurer という名前のものに備わっているというのはどうやって思い至れば良いんだろう?

もしかしたらこういう手順か?

  1. Spring Bootリファレンスの該当しそうな章 37.Validation を見る。
  2. @Validated というvalidation専用っぽいアノテーションが使われているぞ。
  3. javadocの記述を読むと Spring MVC という単語が出てるので、validationの仕組みは Spring MVC の機能のひとつなんだ?
  4. Spring Framework ドキュメントのリストを見ると Spring MVC はWeb Servletに含まれているようだ? Servletとvalidationって何か関係があるのか?よく分からんが見てみよう。
  5. 該当する節 1.10.4. Validationが見つかった!なるほど 設定は WebMvcConfigurer で行うのか!
  6. 関係しそうな名前 getValidator()ってメソッドがあるぞ!

マジでこんなこと考えるの?無理ゲーじゃない?
あとこれから更に LocalValidatorFactoryBean インスタンスを生成して返すってのに気付くのにもまた一山超える必要がありそうだし。

まとめ

  • Spring全然わからん。
  • Qiitaのスタイル、バックスラッシュでくくるとハイパーリンク貼ってるのかどうなのかわからんからいまいち。
  • Qiita等でSpring( Boot)解説エントリを書いている人に向けて: 根拠となる公式リファレンス/ソースコードへの参照も含めてほしい。あなたの書いたその実装方法が妥当なのか入門者は確認できない。
yukihane
当社と元祖木根寿司(株)とは一切無関係です。 Qiita上のコンテンツに対する編集リクエストは受け付けていません(見ていません)。 github.com(等)へのリンクがあるコンテンツの場合、そちらへプルリクエスト(等)をお願いします。 投稿時の縛りとして酔っ払った体(からだ)か酔っ払った体(てい)というのを自分に課しています。 CC BY-SA 4.0
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした