Spring Framework Advent Calendar 2011 part.1 - Spring MVC で JSR-303 Validator を使う

前書き

どうも、Java 界のぼっちこと max747 です。
さて 12 月といえば Advent Calendar の時期でございます。各種コミュニティで Advent Calendar の企画が立ち上がっておりますが、私はそれらを遠巻きに眺めつつ Spring Framework の Advent Calendar をやります。Spring Framework およびその周辺プロダクトを中心に、ネタの続く限り続けようかなと。
Java 言語を対象とした Advent Calendar については Java Advent Calendar 2011 : ATND をご覧くださいませ。

JSR-303 Bean Validation

アノテーションを使用して Bean のバリデーションを記述する方法を定めた仕様です。2年ほど前に Final Release が出ています。JSR の名が付いていることからもわかるとおり、Java の標準化プロセスを経て定められている仕様です。リファレンス実装は Hibanate Validator です。ご存知の方も多いでしょう。

基本的な使い方

JSR-303 を使ったバリデーションのやり方については、id:yamkazu さんの記事 JSR 303 Bean Validationで遊んでみるよ! - Yamkazu's Blog に詳しく書かれています。ぜひお読みください。

Spring MVC に組み込むには

Spring MVC 用の Bean 定義ファイルに、以下を記述します。Spring 3.0.x で MVC を使っている方は、きっとすでに設定してありますよね。

<mvc:annotation-driven />

あとは、ライブラリ (jar ファイル) を放り込むだけです。超簡単ですね! なお JSR-303 がサポートされたのは Spring 3.0 からですので、2.x 系をお使いの方は速やかにアップデートされることをおすすめいたします。

バリデーションを動かす

Spring MVC では Controller のメソッドに HTTP の URL をマッピングします。このとき、メソッドの引数に受け取る form を書きますが、この form をバリデーションしたい場合に @Valid アノテーション (javax.validation.Valid) を与えます。すると、このコントローラーメソッドに到達する直前に Bean Validaiton が行われ、その結果が BindingResult オブジェクトに格納されます。

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(@Valid RegisterForm form, BindingResult result) {
        if (result.hasErrors()) {
            return "register/input";
        }
        return "register/end";
    }

ここで注意が必要なのは、

  • form と BindingResult はセットで引数にする必要があること
  • form, BindingResult の順で、隣り合うように引数を並べること

この 2 つです。

エラーメッセージのローカライズ

Hibanate validator の流儀に従うなら、classpath のルートに ValidationMessages.properties ファイルを用意して対応することになります。ただし、そこは Spring ということで、Spring 流の messageSource を使う方法も提供されています*1。方法は、Bean 定義ファイルに messageSource を定義して、

  <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="classpath:messages" />
  </bean>

メッセージ用のプロパティファイルを用意するだけです。

NotEmpty=\u5fc5\u9808\u3067\u3059   ## NotEmpty=必須です

通常、メッセージ用のプロパティファイルはすでにあるでしょうから、そこに追記すればよいでしょう。なお、バリデーションのメッセージを特定のフォームのときだけ別のメッセージに変更する、といったことも可能です。

NotEmpty=\u5fc5\u9808\u3067\u3059                 # すべての NotEmpty アノテーションに影響します
registerForm.NotEmpty=\u5fc5\u9808\u3067\u3059    # registerForm の NotEmpty アノテーションに影響します


以上、駆け足でしたが JSR-303 Validator を Spring に組み込む方法の紹介でした。実際に動作するサンプルも用意しています。適当に clone して遊んでください。
https://github.com/max747/spring-examples/

*1:詳細は確認していませんが、バージョン 3.0.4 以降でないとダメかもしれません

yum や rpm でインストールした Jenkins の利用ポートを変更する

CentOS 上で Jenkins 環境を構築する際、yumrpm で Jenkins をインストールすると、起動スクリプトもあわせてインストールされるため

# service jenkins start
# /etc/init.d/jenkins start

などのコマンドで起動できるようになり便利です。
では、これらのコマンドで起動する際の Jenkins の利用ポートを変更するにはどうしたらよいでしょうか。起動スクリプトを読むと、環境変数 $JENKINS_PORT を設定すればよいことが分かりますが、たとえば

# export JENKINS_PORT=10080
# service jenkins start

としてもうまくいきません。
実は、起動スクリプトの L54 で読み込んでいる /etc/sysconfig/jenkins に JENKINS_PORT が定義されており、そちらの値で環境変数が上書きされるためです。ということで、yumrpm でインストールした Jenkins の利用ポート番号を変更したい場合は /etc/sysconfig/jenkins の内容を変更すればOKです。

Spring Security でアカウントロック機構を実現する

アカウントロック機構は、パスワード認証をともなうWebアプリケーションの開発ではしばしばセキュリティ要件として盛り込まれることがあります。また、Webサービスのユーザーとしても馴染みのある機能のひとつでしょう。今日は、この機能を Spring Security の認証の枠組みの中で実現する方法について紹介します。なお、本記事は Spring Security 3.0.4 をベースに書かれています。
いきなりですが、 Spring Security では、アカウントロック機構は提供されていません。アカウントをロックするのは認証サービスが行うべきことであり、認証サービスとアプリケーションをつなぐためのフレームワークである Spring Security には、そのような義務はありません。また、アカウントロックの実現方式についても、認証サービスに強く依存します。ですので、アプリケーションへの要求としてアカウントロック機構が求められており、認証サービスがアカウントロック機構を提供していない場合は、自作する必要が出てきます*1
といっても、実装のための手がかりがまったくないというわけではありません。Spring Security では、認証に失敗した場合に AuthenticationFailureBadCredentialsEvent というイベントが発生します。このイベントを受け取る Listener を実装し Spring Bean として登録しておけば、認証失敗時に対応する Listener が呼び出されます。したがって、この Listener にアカウントロックの機能を実装すれば事足りる、というわけです。一般的には、認証失敗の情報をデータベース等に記録し、規定回数を超えた場合はユーザーアカウントをロックする、といった内容になるでしょう。
以下は、Spring Security が標準で送出するイベントの一覧になります。表の右列にある「対応する例外」が発生したときに、イベントがトリガーされます。認証に成功した場合は、AuthenticationSuccessEvent が送出されます。つまり、認証成功時に何か特別な処理をしたい場合*2にも、今回紹介する手法と同様のやり方で実現できます。

発生するイベント 意味 対応する例外
AuthenticationFailureBadCredentialsEvent 認証情報が正しくない BadCredentialsException, UsernameNotFoundException
AuthenticationFailureCredentialsExpiredEvent 認証情報が失効している CredentialsExpiredException
AuthenticationFailureDisabledEvent アカウントが無効になっている DisabledException
AuthenticationFailureExpiredEvent アカウントが失効している AccountExpiredException
AuthenticationFailureLockedEvent アカウントがロックされている LockedException
AuthenticationFailureProviderNotFoundEvent 認証プロバイダーが見つからない ProviderNotFoundException
AuthenticationFailureProxyUntrustedEvent (CAS認証において)プロキシーが信頼できない ProxyUntrustedException
AuthenticationFailureServiceExceptionEvent AuthenticationManager 内で問題が発生した AuthenticationServiceException
AuthenticationSuccessEvent 認証に成功した なし


イベントの伝播は Spring Framework の標準的な機構を用いて行われます。すなわち、イベントオブジェクトは ApplicationEvent を基底としたクラスとなり、通知は ApplicationListener で受け取ることができます。Spring Framework のイベント伝播の仕組みについては、すでに blog や書籍等で数多くの解説記事がありますので、ここでは触れません。不明な点があれば、それらの解説記事も参照してください。

最後に、実装サンプルを載せておきます。
注意点としては、AuthenticationFailureBadCredentialsEvent に対応する例外は BadCredentialsException と UsernameNotFoundException のふたつがあります。そのため、単純に AuthenticationFailureBadCredentialsEvent を捕捉するだけでは、上記の2つの Exception を区別できないため、存在しないユーザーもDBに記録してしまう可能性があります。これを嫌うのであれば、捕捉した Event から例外情報を取り出して、その型をチェックすることが必要になります。


なお、Spring 3.0 以降では ApplicationListener が Generics 対応しており、サンプルコードのように受け取るイベントの型を明示できるようになっています。キャストの手間がひとつ減りますので、利用側としてはうれしいですね。

*1:LDAPのように、認証サービス側がアカウントロック機構をすでに持っている場合は、その枠組みを利用すればよく、その場合は自作する必要はありません。

*2:たとえば、ログインの証跡を取りたいなど。

is 演算子は同一性、== 演算子は等価性

「初めてのPython」で読んだような気がするのだけど、すっかり忘れてしまっていたので。ちょっと検証してみます。

>>> class Foo(object):
...     def __init__(self, x):
...         self.x = x
...     def __eq__(self, arg):
...         return self.x == arg
...
>>>
>>> f = Foo(1)
>>> g = Foo(1)
>>> h = Foo(2)
>>> f is g
False
>>> g is h
False
>>> h is f
False
>>> f == g
True
>>> f == h
False
>>> h == f
False

ふむ、確かに。

初めてのPython 第3版

初めてのPython 第3版

アプリケーションウィンドウのスクリーンショットを撮る

Command+Shift+4, Space でマウスポインタがカメラアイコンになる。その状態でスクリーンショットを撮りたいウィンドウを選択する。スクリーンショットはデスクトップに保存される。
Command+Shift+4 の後、Space キーを押さずにマウスで矩形選択して、その範囲をスクリーンショットにすることもできるようだ。

ターミナルで exit したときにウィンドウを閉じる

Mac のターミナルで exit したときに、デフォルトの動作では [プロセスが完了しました] と表示され、ウィンドウは自動的に閉じてくれない。そのため、明示的に x ボタンもしくは Command+w で閉じる必要がある。
この挙動は、環境設定で変更できるようだ。

Version: Mac OS X 10.6.8 / Terminal 2.1.2(273.1)