【コラム】

Java API、使ってますか?

8 アノテーションでバリデーション - JSR 303: Bean Validator

8/60

JSR 303: Bean Validator

既報の通り、ついにエンタープライズJavaの次期バージョンであるJava EE 6の仕様策定がJSR 313としてスタートした。本連載でも時期を見てJSR 313やそこに含まれるコンポーネントJSRの詳細を取り上げていく予定だが、エンタープライズ分野に携わる開発者はjcp.orgなどで公表される情報に目を光らせておくといいだろう。

さて、今回もJava SE 7に導入が予定されているAPIを紹介する。Java SE 7への導入が検討されているAPIにはアノテーションを活用したものも少なくない。本来ならば複雑な設定を必要とするような機能も、アノテーションを利用することで容易に利用できるようになるため、この流れは必然と言えるかもしれない。今回はそんなアノテーションを活用したAPIのひとつである「JSR 303: Bean Validator」を取り上げる。

JSR 303はJava Beanオブジェクトのためのバリデーション機構やメタデータモデルを実現する目的で発足した。このAPIを使用することで、Java Beanのプロパティが取り得る値の範囲や条件を設定し、実行時に統一的な方法でそれを検証できるようになる。これによってWebアプリケーションやGUIアプリケーションの入力値検証や、オブジェクトの永続化の際のデータ検証などが容易に行えるようになる。

JSR 303では一般的なバリデーション定義のためのアノテーションを用意し、これをプロパティに指定するだけで条件の指定ができるようにするとのことである。これには数値プロパティの最大値/最小値の指定や、文字列の長さやフォーマットの指定などが含まれるだろう。それに加えて、XMLバリデーションデスクリプタを利用して独自に定義を拡張/オーバーライドできる機構も提供するという。

このような機能を提供してくれるフレームワークはすでにいくつか実在しており、JSR 303の仕様もそれらの影響を受けたものになるだろう。ここではそんなバリデーションフレームワークのうち、代表的なものを2つ取り上げて紹介する。

バリデーションフレームワークの例 その1 - Hibernate Validator

まず最初に紹介するのが、Hibernateプロジェクトによって開発されている「Hibernate Validator」だ。Hibernate Validatorは、HibernateによるJavaオブジェクトの永続化の際にオブジェクトの状態を検証する目的で開発されたが、Hibernate以外の永続化フレームワークや、スタンドアロン・アプリケーションでも利用することができる。Hibernate Validatorはバリデーション定義をアノテーションによって記述することができ、この点がJSR 303の目指すものとよく似ている。

Hibernate Validatorはこのサイトより入手できる。最新バージョンは3.0.0 GAで、使用するにはHibernate Coも必要なので同時にダウンロードしておこう。

本稿のサンプルは、両配布ファイルに含まれる以下の4つのファイルをクラスパスに追加してコンパイル/実行する。

  • hibernate-validator.jar
  • hibernate-commons-annotation.jar
  • hibernate3.jar
  • commons-logging-1.0.4.jar

Hibernate Validatorでは、Beanのプロパティにアノテーションを付加することでバリデーション定義を行う。たとえばリスト1のような感じだ。ここではusernameおよびpasswordに文字数の制限を、birthdayに未来の日付を使用できないようにする制限を、emailにE-mail形式の文字列のみに限定する制限を設けている。

リスト1 UserBean.java

package apisample;

import java.util.Date;
import org.hibernate.validator.Email;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;
import org.hibernate.validator.Past;

public class UserBean {
    @Length(min=4,max=16,message="ユーザ名は4~16文字で入力してください。")
    @NotEmpty
    private String username;
    @Length(min=4,max=8,message="パスワードは4~8文字で入力してください。")
    @NotEmpty
    private String password;
    @Past(message="未来の日付が入力されました。")
    private Date birthday;
    @Email(message="メールアドレスは[xxxx@xxxx.xxx]の形式で入力してください。")
    private String email;

    public UserBean(String username, String password, Date birthday, String email) {
        this.username = username;
        this.password = password;
        this.birthday = birthday;
        this.email = email;
    }

    /** Setter/GetterおよびtoString()は省略 **/
}

実際の検証はリスト2のようにして行う。まずUserBeanクラスを指定してValodatorインスタンスを生成し、getInvalidValuesメソッドに検証したいBeanオブジェクトを渡して検証を実行する。このメソッドは検証結果をInvalidValueの配列で返す。

リスト2 HibernateValidatorSample.java

package apisample;

import java.util.Calendar;
import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;

public class HibernateValidatorSample {
    public static void main(String[] args) {
        // 正しい値を設定した例
        Calendar birthday1 = Calendar.getInstance();
        birthday1.set(2006,4,1);
        UserBean user1 = new UserBean("mycom","jurnal",birthday1.getTime(),"mycom@example.jp");
        executeValidation(user1);

        // 誤った値を設定した例
        Calendar birthday2 = Calendar.getInstance();
        birthday2.set(2008,4,1);
        UserBean user2 = new UserBean("mj","mycomjurnal",birthday2.getTime(),"mycomexample.jp");
        executeValidation(user2);
    }

    /**
     * Beanのプロパティ値の検証を行う
     */
    private static void executeValidation(UserBean user) {
        ClassValidator userValidator = new ClassValidator(UserBean.class);
        InvalidValue[] validationMessages = userValidator.getInvalidValues(user);
        System.out.println(user + "を検証します。");
        for (InvalidValue message: validationMessages) {
            System.out.println("    ERROR: " + message);
        }
    }
}

このプログラムを実行するとプロンプト1のようになる。user2は各プロパティに誤った値を設定してあるので、検証結果としてエラーメッセージが出力される。

プロンプト1 HibernateValidatorSample.javaの実行例

> java -cp [PATH_TO_JARFILES] apisample/HibernateValidatorSample
UserBean[mycom,jurnal,Mon May 01 21:50:05 JST 2006, mycom@example.jp]を検証します。
UserBean[mj,mycomjurnal,Thu May 01 21:50:05 JST 2008, mycomexample.jp]を検証します。
    ERROR: username ユーザ名は4~16文字で入力してください。
    ERROR: password パスワードは4~8文字で入力してください。
    ERROR: birthday 未来の日付が入力されました。
    ERROR: email メールアドレスは[xxxx@xxxx.xxx]の形式で入力してください。

Hibernate Validatorではここで示した以外にもさまざまなアノテーションが用意されているほか、独自にバリデータを拡張して使用することもできる。

バリデーションフレームワークの例 その2 - Commons Validator

JSR 303ではアノテーションによるバリデーション定義に加えて、XMLバリデーションデスクリプタによる定義の拡張もサポートする予定となっている。そこでこれに似た機能を持つフレームワークとしてCommons Validationを紹介しよう。

Commons ValidatorはApache FoundationのJakarta Commonsプロジェクトにおいて開発されているバリデーションフレームワークであり、XMLによって独自にバリデーション定義を拡張するための機構を有している。

Commons Validatorはこのサイトからダウンロードすることができる。ただし、Commons Validatorを使用するのは以下のツールも必要になるため、合わせて入手しておく必要がある。これら各ツールに含まれるJARファイルをクラスパスに追加して使用する。

今回はリスト3のようなJava Beanを検証するバリデータを定義してみる。

リスト3 UserBean2.java

package apisample;

public class UserBean2 {
    private String username;
    private String email;

    public UserBean2(String username, String email) {
        this.username = username;
        this.email = email;
    }

    /** Setter/GetterおよびtoString()は省略 **/


}

実際に検証を行うプログラムはリスト4のように記述する。validateRequiredメソッドは、文字列プロパティがnullや空文字列でなく、かつ文字数が4 - 16文字であるかどうかを検証する。validateEmailメソッドは文字列がE-mailアドレス形式になっているかどうかを検証する。いずれもJava BeanオブジェクトとフィールドFieldオブジェクトを引数に取る。

リスト4 UserBeanValidator.java

package apisample;

import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.util.ValidatorUtils;

public class UserBeanValidator {
    /**
     * 値がnullや空文字列でなく、かつ4文字以上16文字以内である
     */
    public static boolean validateRequired(Object bean, Field field)
    {
        String value =
            ValidatorUtils.getValueAsString(bean, field.getProperty());
        boolean isNotBlankOrNull = !GenericValidator.isBlankOrNull(value);
        boolean isValidLength = GenericValidator.minLength(value, 4) 
                                && GenericValidator.maxLength(value, 16);
        return isNotBlankOrNull && isValidLength;
    }

    /**
     * 値がE-mailの形式になっている
     */
    public static boolean validateEmail(Object bean, Field field)
    {
        String value =
            ValidatorUtils.getValueAsString(bean, field.getProperty());
        return GenericValidator.isEmail(value);
    }
}

バリデーション定義はXMLでリスト5のように記述する。Commons ValidatorのバリデーションエンジンはこのXMLを参照して、検証に使用するクラス(今回はUserBeanValidatorクラス)を特定する。クラスはタグで宣言し、ここに論理名やクラス名、メソッド名、引数の型、メッセージキーなどを指定する。

<form>タグでは<validation>タグによるバリデータ定義をJava Beanのプロパティに関連付ける。ここではusernameプロパティを論理名"required"のバリデータに、emailプロパティを論理名"email"のバリデータに関連付けている。

リスト5 validator-definition.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
     "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN"
     "http://jakarta.apache.org/commons/dtds/validator_1_1.dtd">

<form-validation>
   <global>
      <validator name="required"
         classname="apisample.UserBeanValidator" 
         method="validateRequired"
         methodParams=
            "java.lang.Object, org.apache.commons.validator.Field"
         msg="required.field"/>
      <validator name="email"
         classname="apisample.UserBeanValidator" 
         method="validateEmail"
         methodParams=
            "java.lang.Object, org.apache.commons.validator.Field"
         msg="invalid.email"/>
   </global>
   <formset>
      <form name="userInfo">
         <field property="username" depends="required">
            <arg0 key="userInfo.username.displayname"/>
         </field>
         <field property="email" depends="email">
            <arg0 key="userInfo.email.displayname"/>
         </field>
      </form>
   </formset>
</form-validation>

実際の検証はリスト6のようにして行う。まずバリデーション定義ファイル"validator-definition.xml"からValidatorResourcesを生成し、フォーム名を指定してそこからValidatorオブジェクトを生成する。検証はvalidateメソッドで実行し、結果はValidatorResultsオブジェクトとして帰ってくる。

リスト6 CommonsValidatorSample.java

package apisample;

import java.io.*;
import java.util.*;
import org.apache.commons.validator.Validator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorException;
import org.apache.commons.validator.ValidatorResources;
import org.apache.commons.validator.ValidatorResult;
import org.apache.commons.validator.ValidatorResults;
import org.xml.sax.SAXException;

public class CommonsValidatorSample {
    public static void main(String[] args) {
        // 正しい値を設定した例
        UserBean2 user1 = new UserBean2("mycom", "mycom@example.jp");
        executeValidation(user1);

        // 誤った値を設定した例
        UserBean2 user2 = new UserBean2("mj", "mycomexample.jp");
        executeValidation(user2);
    }

    /**
     * Beanのプロパティ値の検証を行う
     */
    private static void executeValidation(UserBean2 user) {
        System.out.println(user + "を検証します。");
        try {
            InputStream in = user.getClass().getResourceAsStream("validator-definition.xml");
            ValidatorResources resources = new ValidatorResources(in);
            Validator validator = new Validator(resources, "userInfo");
            validator.setParameter(Validator.BEAN_PARAM, user);
            ValidatorResults results = validator.validate();

            // 検証結果を表示
            Set propertyNames = results.getPropertyNames();
            for (Object propertyName: propertyNames) {
                ValidatorResult result = results.getValidatorResult((String)propertyName);
                Iterator actions = result.getActions();
                while (actions.hasNext()) {
                    String actionName = (String) actions.next();
                    if (!result.isValid(actionName)) {
                        ValidatorAction action = resources.getValidatorAction(actionName);
                        System.out.println("    ERROR: " + action.getMsg());
                    }
                }
            }
        } catch (SAXException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (ValidatorException ex) {
            ex.printStackTrace();
        };
    }
}

このプログラムを実行するとプロンプト2のようになる。

プロンプト2 CommonsValidatorSample.javaの実行例

> java -cp [PATH_TO_JARFILES] apisample/CommonsValidatorSample
UserBean2[mycom,mycom@example.jp]を検証します。
UserBean2[mj,mycomexample.jp]を検証します。
    ERROR: required.field
    ERROR: invalid.email

まとめ

JSR 303は、JSF(JavaServer Faces)やJPA(Java Persistence API)、Bean Bindingなどといった、Java Beansを利用するさまざまなAPIのコアコンポーネントとなることを目標として開発されている。そういう意味では、このJSRが他のAPIに与える影響は非常に大きい。当初の予定では2006年中にEarly Draft Reviewが行われる予定となっていたのでどうやら標準化作業は遅れ気味のようだが、近いうちに仕様の詳細が公開されることだろう。

提供:毎日就職ナビ

会員登録はこちら

学生のための就職情報サイト「毎日就職ナビ」。6,000社以上の新卒採用情報が常時掲載され、社内の雰囲気が伝わる情報画面、さまざまな項目での会社検索、エントリーや説明会検索など、機能も充実。無料適職診断、就活Q & A、エントリーシート添削講座など、就職活動に役立つ記事も満載です。研究者、エンジニアを目指す学生の方々も是非エントリーしてください。お待ちしています!

毎日コミュニケーションズはプライバシーマークを取得しています。

8/60

インデックス

連載目次
第60回 どうなる? 今後のJavaプラットフォーム(Java SE編)
第59回 どうなる? 今後のJavaプラットフォーム(Java EE編)
第58回 Java SE 7の要注目機能"クロージャ"はどうなるのか その6
第57回 Java SE 7の要注目機能"クロージャ"はどうなるのか その5
第56回 Java SE 7の要注目機能"クロージャ"はどうなるのか その4
第55回 Java SE 7の要注目機能"クロージャ"はどうなるのか その3
第54回 Java SE 7の要注目機能"クロージャ"はどうなるのか その2
第53回 Java SE 7の要注目機能"クロージャ"はどうなるのか
第52回 Early Draftが公開されたJSF 2.0
第51回 EJBから独立したJava Persistence 2.0
第50回 モバイルJavaの新しい潮流となるか - MSA 2.0のドラフト公開
第49回 やっぱり基本はServlet - Servlet 3.0のEarly Draftを読む
第48回 JOGLで3Dプログラミング その4
第47回 JOGLで3Dプログラミング その3
第46回 JOGLで3Dプログラミング その2
第45回 JOGLで3Dプログラミング
第44回 JARファイルを効率的にネットワーク転送するためのPack200形式
第43回 Early Draftで把握するEJB 3.1の新機能
第42回 次世代の携帯端末向けJava仕様"MIDP 3.0"はどうなるか その2
第41回 次世代の携帯端末向けJava仕様"MIDP 3.0"はどうなるか その1
第40回 リソースアダプタによる接続の仕組み
第39回 JCAを利用したシステム間接続
第38回 Java EEと外部システムの接続性を支えるJCAがバージョンアップ
第37回 Javaのモジュラリティ強化を担う"スーパーパッケージ"とは
第36回 JSR 308対応のコンパイラを試す
第35回 公開されたJSR 308のEarly Draftを検証する
第34回 スクリプト言語とJavaを結びつけるJSR 223
第33回 Java EE環境に統一されたコンポーネントモデルを提供するJSR 299 その2
第32回 Java EE環境に統一されたコンポーネントモデルを提供するJSR 299 その1
第31回 Javaの文法がそのまま使えるスクリプト言語"BeanShell"
第30回 Javaアプリケーションにオブジェクトのキャッシュ機構を提供するJCache API
第29回 Javaアプリケーションからのリソース管理を可能にするJSR 284
第28回 XMLデータソースへの問い合わせはJSR 225で
第27回 Portlet Specification 2.0をもっと手軽に利用する
第26回 次期Javaポートレット仕様となるJSR 286
第25回 JSFとポートレットをつなげるJSR 301
第24回 Webサービス向けのポートレット仕様「WSRP」
第23回 高い相互運用性を実現するポートレットAPI - JSR 168
第22回 Java EE環境でタスクのスケジューリングを可能にするJSR 236
第21回 Java EE環境でのスレッドプログラミングを可能にするJSR 237
第20回 音声認識/合成のためのAPI - Java Speech APIとJSR 113
第19回 JSR 291でJavaプラットフォームにダイナミックコンポーネントモデルを導入
第18回 JAX-RSで簡単RESTful - JSR 311
第17回 待望のServlet 3.0がJSRに登場 - JSR 315
第16回 アノテーションを使ってバグ退治 - JSR 305
第15回 アノテーションをさらに広い範囲で利用可能にするJSR 308
第14回 Webアプリケーション開発の要となるか - JSF 2.0がJSRに登場
第13回 Webサービス経由でのJMX Agentへの接続を可能にするJSR 262
第12回 Javaアプリケーションのモジュール化をサポートするJava Module System
第11回 "NIO.2"がやってきた - JSR 203: More New I/O APIs for the Java Platform
第10回 JSR 295: Beans Bindingの参照実装を試す
第9回 けっこう便利! 単位を扱うAPI -- JSR 275: Units Specification
第8回 アノテーションでバリデーション - JSR 303: Bean Validator
第7回 Swing開発の救世主となるか - Swing Application Framework
第6回 JavaBeansのプロパティを同期させるバインディングAPI
第5回 誰よりも早く"Java SE 7"を睨む
第4回 日時情報の取り扱いを改善する JSR 310: Date and Time API
第3回 古いAPIも進化している!? - JSR 919: JavaMail 1.4
第2回 JSR 1 リアルタイムJava仕様
第1回 JCPによって進められるJava関連技術の標準化

もっと見る

提供:マイナビ

会員登録はこちら

大学・大学院・短大・専門学生向けの就職情報サイト「マイナビ2010」「マイナビ2009」に今すぐ登録しよう!  大手企業からベンチャー企業までの約13,000社の企業情報を公開、エントリーが可能です。2010年卒予定の方は「マイナビ2010」に、2009年卒予定の方は「マイナビ2009」に登録してください。

毎日コミュニケーションズはプライバシーマークを取得しています。



人気記事

一覧

イチオシ記事

新着記事