Apache Commons Validator
バリデーションライブラリ
Apache Commons Validator
概要
Apache Commons Validatorは、Javaアプリケーション向けの包括的なバリデーションフレームワークです。クライアントサイドとサーバーサイドの両方でデータ検証を行うための基盤を提供し、XML設定による宣言的なバリデーション定義をサポートします。メールアドレス、URL、クレジットカード番号、ISBNなど、一般的な検証ルーチンを組み込みで提供し、StrutsやSpring MVCなどのフレームワークとも統合可能です。
詳細
Apache Commons Validatorは、再利用可能なバリデーションコンポーネントのセットを提供するフレームワークです。XMLファイルでバリデーションルールを定義でき、JavaBeanのプロパティ検証を抽象化します。組み込みのバリデータルーチンには、メール検証、URL検証、日付検証、数値検証、正規表現検証などが含まれます。
主要な特徴:
- XML設定ベース: バリデーションルールをXMLで定義し、Javaコードから分離
- 豊富な組み込みバリデータ: EmailValidator、UrlValidator、CreditCardValidator、ISBNValidatorなど多数の実用的なバリデータ
- ロケール対応: 言語・地域に応じたバリデーションルールの切り替え
- カスタムバリデータ: 独自のバリデーションロジックの実装と統合
- ネストプロパティ対応: Apache Commons BeanUtilsを使用した複雑なオブジェクト構造の検証
- フレームワーク統合: Struts、Spring MVCなどの主要フレームワークとの連携
バージョン1.9.0では、Java 8以上をサポートし、モダンなJava開発環境に対応しています。
メリット・デメリット
メリット
- 実績と信頼性: Apache Software Foundationによる長年の開発実績
- 包括的なバリデータセット: 実用的な組み込みバリデータが豊富
- 設定の外部化: XMLベースで検証ルールを管理
- 国際化対応: 多言語・多地域対応が容易
- 軽量: 最小限の依存関係で動作
- 拡張性: カスタムバリデータの追加が簡単
デメリット
- XML設定の複雑さ: 大規模プロジェクトでは設定が煩雑になりがち
- アノテーション非対応: Bean Validationと比較して記述が冗長
- 学習曲線: XMLスキーマとフレームワーク概念の理解が必要
- 保守性: XML設定とJavaコードの分離によりリファクタリングが困難
- モダンではない: 最新のJava開発手法と比較して古典的なアプローチ
参考ページ
- Apache Commons Validator 公式サイト
- Apache Commons Validator JavaDoc
- Apache Commons Validator GitHub
- Apache Commons Validator ユーザーガイド
- Maven Repository
書き方の例
インストールと基本セットアップ
<!-- Maven dependency -->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.9.0</version>
</dependency>
// Gradle dependency
dependencies {
implementation 'commons-validator:commons-validator:1.9.0'
}
組み込みバリデータの使用例
import org.apache.commons.validator.routines.*;
import java.util.Locale;
public class BuiltInValidatorExample {
public static void main(String[] args) {
// メールアドレスのバリデーション
EmailValidator emailValidator = EmailValidator.getInstance();
String email = "[email protected]";
if (emailValidator.isValid(email)) {
System.out.println("有効なメールアドレス: " + email);
} else {
System.out.println("無効なメールアドレス: " + email);
}
// URLのバリデーション
UrlValidator urlValidator = new UrlValidator();
String url = "https://www.example.com";
if (urlValidator.isValid(url)) {
System.out.println("有効なURL: " + url);
}
// クレジットカード番号のバリデーション
CreditCardValidator creditCardValidator = new CreditCardValidator();
String creditCard = "4111111111111111"; // テスト用Visa番号
if (creditCardValidator.isValid(creditCard)) {
System.out.println("有効なクレジットカード番号");
}
// ISBNのバリデーション(ISBN-10とISBN-13対応)
ISBNValidator isbnValidator = new ISBNValidator();
String isbn10 = "0-306-40615-2";
String isbn13 = "978-0-306-40615-7";
if (isbnValidator.isValidISBN10(isbn10)) {
System.out.println("有効なISBN-10: " + isbn10);
}
if (isbnValidator.isValidISBN13(isbn13)) {
System.out.println("有効なISBN-13: " + isbn13);
}
// 日付のバリデーション
DateValidator dateValidator = DateValidator.getInstance();
String dateStr = "2024/12/31";
if (dateValidator.isValid(dateStr, "yyyy/MM/dd", Locale.JAPAN)) {
System.out.println("有効な日付: " + dateStr);
}
// 整数のバリデーション
IntegerValidator intValidator = IntegerValidator.getInstance();
String intStr = "12345";
Integer value = intValidator.validate(intStr);
if (value != null) {
System.out.println("有効な整数: " + value);
}
// 正規表現によるバリデーション
RegexValidator regexValidator = new RegexValidator("^[0-9]{3}-[0-9]{4}$");
String postalCode = "123-4567";
if (regexValidator.isValid(postalCode)) {
System.out.println("有効な郵便番号: " + postalCode);
}
}
}
XMLベースのバリデーション設定
<!-- validation.xml -->
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.4.0//EN"
"http://commons.apache.org/dtds/validator_1_4_0.dtd">
<form-validation>
<!-- グローバルバリデータ定義 -->
<global>
<validator name="required"
classname="org.apache.commons.validator.ValidatorAction"
method="validateRequired"
methodParams="java.lang.Object,
org.apache.commons.validator.Field,
org.apache.commons.validator.ValidatorAction"
msg="errors.required"/>
<validator name="email"
classname="org.apache.commons.validator.routines.EmailValidator"
method="isValid"
methodParams="java.lang.String"
msg="errors.email"/>
<validator name="mask"
classname="org.apache.commons.validator.ValidatorAction"
method="validateMask"
methodParams="java.lang.Object,
org.apache.commons.validator.Field,
org.apache.commons.validator.ValidatorAction"
msg="errors.invalid"/>
</global>
<!-- フォーム定義 -->
<formset>
<form name="userForm">
<field property="username" depends="required">
<arg key="label.username"/>
</field>
<field property="email" depends="required,email">
<arg key="label.email"/>
</field>
<field property="phoneNumber" depends="required,mask">
<arg key="label.phoneNumber"/>
<var>
<var-name>mask</var-name>
<var-value>^\d{3}-\d{4}-\d{4}$</var-value>
</var>
</field>
<field property="age" depends="required,integer,intRange">
<arg key="label.age"/>
<var>
<var-name>min</var-name>
<var-value>18</var-value>
</var>
<var>
<var-name>max</var-name>
<var-value>120</var-value>
</var>
</field>
</form>
<!-- 日本語ロケール用の設定 -->
<form name="userForm" locale="ja">
<field property="postalCode" depends="required,mask">
<arg key="label.postalCode"/>
<var>
<var-name>mask</var-name>
<var-value>^\d{3}-\d{4}$</var-value>
</var>
</field>
</form>
</formset>
</form-validation>
Javaでのバリデーション実行
import org.apache.commons.validator.*;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class FormValidationExample {
private ValidatorResources resources;
public FormValidationExample() throws Exception {
// XMLファイルからバリデーション設定を読み込み
InputStream in = getClass().getResourceAsStream("/validation.xml");
resources = new ValidatorResources(in);
}
public void validateUserForm(UserBean user) throws ValidatorException {
// Validatorインスタンスの作成
Validator validator = new Validator(resources, "userForm");
// バリデーション対象のBeanを設定
validator.setParameter(Validator.BEAN_PARAM, user);
// エラーメッセージを格納するマップ
Map<String, String> errors = new HashMap<>();
validator.setParameter("errors", errors);
// バリデーション実行
ValidatorResults results = validator.validate();
// 結果の確認
if (!errors.isEmpty()) {
System.out.println("バリデーションエラー:");
for (Map.Entry<String, String> entry : errors.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
} else {
System.out.println("バリデーション成功");
}
}
}
// バリデーション対象のBean
public class UserBean {
private String username;
private String email;
private String phoneNumber;
private Integer age;
private String postalCode;
// コンストラクタ
public UserBean() {}
// getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getPostalCode() { return postalCode; }
public void setPostalCode(String postalCode) { this.postalCode = postalCode; }
}
カスタムバリデータの作成
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.util.ValidatorUtils;
public class CustomValidators {
/**
* 日本の電話番号形式をバリデート
*/
public static boolean validateJapanesePhoneNumber(
Object bean,
Field field,
ValidatorAction va) {
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
if (GenericValidator.isBlankOrNull(value)) {
return true; // 空の場合はrequiredバリデータに任せる
}
// 日本の電話番号パターン
String phonePattern = "^(0[0-9]{1,4}-[0-9]{1,4}-[0-9]{4}|0[0-9]{9,10})$";
return value.matches(phonePattern);
}
/**
* パスワード強度のバリデート
*/
public static boolean validatePasswordStrength(
Object bean,
Field field,
ValidatorAction va) {
String password = ValidatorUtils.getValueAsString(bean, field.getProperty());
if (GenericValidator.isBlankOrNull(password)) {
return true;
}
// パスワードポリシー:
// - 8文字以上
// - 大文字・小文字・数字・特殊文字を含む
boolean hasLength = password.length() >= 8;
boolean hasUpperCase = password.matches(".*[A-Z].*");
boolean hasLowerCase = password.matches(".*[a-z].*");
boolean hasDigit = password.matches(".*[0-9].*");
boolean hasSpecialChar = password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*");
return hasLength && hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar;
}
/**
* 開始日と終了日の整合性チェック
*/
public static boolean validateDateRange(
Object bean,
Field field,
ValidatorAction va) {
String startDateStr = field.getVarValue("startDateProperty");
String endDateStr = field.getVarValue("endDateProperty");
String datePattern = field.getVarValue("datePattern");
if (startDateStr == null || endDateStr == null) {
return false;
}
String startValue = ValidatorUtils.getValueAsString(bean, startDateStr);
String endValue = ValidatorUtils.getValueAsString(bean, endDateStr);
if (GenericValidator.isBlankOrNull(startValue) ||
GenericValidator.isBlankOrNull(endValue)) {
return true;
}
DateValidator dateValidator = DateValidator.getInstance();
Date startDate = dateValidator.validate(startValue, datePattern);
Date endDate = dateValidator.validate(endValue, datePattern);
if (startDate == null || endDate == null) {
return false;
}
return startDate.before(endDate) || startDate.equals(endDate);
}
}
Spring Framework統合
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.apache.commons.validator.ValidatorResources;
import org.apache.commons.validator.Validator as CommonsValidator;
@Component
public class CommonsValidatorAdapter implements Validator {
private ValidatorResources validatorResources;
@Autowired
public CommonsValidatorAdapter(ValidatorResources validatorResources) {
this.validatorResources = validatorResources;
}
@Override
public boolean supports(Class<?> clazz) {
return UserBean.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
try {
CommonsValidator validator = new CommonsValidator(
validatorResources, "userForm");
validator.setParameter(CommonsValidator.BEAN_PARAM, target);
ValidatorResults results = validator.validate();
// エラーをSpringのErrorsオブジェクトに変換
if (results != null) {
for (Object propertyName : results.getPropertyNames()) {
String property = (String) propertyName;
ValidatorResult result = results.getValidatorResult(property);
for (Object actionName : result.getActions()) {
String action = (String) actionName;
if (!result.isValid(action)) {
errors.rejectValue(property,
"error." + property + "." + action,
"Validation failed for " + property);
}
}
}
}
} catch (ValidatorException e) {
errors.reject("validation.error", "Validation error occurred");
}
}
}
// Controllerでの使用
@Controller
public class UserController {
@Autowired
private CommonsValidatorAdapter validator;
@PostMapping("/users")
public String createUser(@ModelAttribute UserBean user,
BindingResult result) {
validator.validate(user, result);
if (result.hasErrors()) {
return "userForm";
}
// ユーザー作成処理
return "redirect:/users";
}
}
高度なバリデーション設定
<!-- validation-rules.xml - カスタムバリデータの定義 -->
<form-validation>
<global>
<!-- カスタムバリデータの登録 -->
<validator name="japanesePhone"
classname="com.example.validators.CustomValidators"
method="validateJapanesePhoneNumber"
methodParams="java.lang.Object,
org.apache.commons.validator.Field,
org.apache.commons.validator.ValidatorAction"
msg="errors.japanesePhone"/>
<validator name="passwordStrength"
classname="com.example.validators.CustomValidators"
method="validatePasswordStrength"
methodParams="java.lang.Object,
org.apache.commons.validator.Field,
org.apache.commons.validator.ValidatorAction"
msg="errors.weakPassword"/>
<validator name="dateRange"
classname="com.example.validators.CustomValidators"
method="validateDateRange"
methodParams="java.lang.Object,
org.apache.commons.validator.Field,
org.apache.commons.validator.ValidatorAction"
msg="errors.invalidDateRange"/>
</global>
<formset>
<!-- 複雑なフォームのバリデーション設定 -->
<form name="registrationForm">
<field property="username" depends="required,minlength,maxlength">
<arg position="0" key="label.username"/>
<arg position="1" key="${var:minlength}"/>
<arg position="2" key="${var:maxlength}"/>
<var>
<var-name>minlength</var-name>
<var-value>3</var-value>
</var>
<var>
<var-name>maxlength</var-name>
<var-value>20</var-value>
</var>
</field>
<field property="password" depends="required,passwordStrength">
<arg key="label.password"/>
</field>
<field property="phoneNumber" depends="required,japanesePhone">
<arg key="label.phoneNumber"/>
</field>
<field property="dateRange" depends="dateRange">
<arg key="label.dateRange"/>
<var>
<var-name>startDateProperty</var-name>
<var-value>startDate</var-value>
</var>
<var>
<var-name>endDateProperty</var-name>
<var-value>endDate</var-value>
</var>
<var>
<var-name>datePattern</var-name>
<var-value>yyyy/MM/dd</var-value>
</var>
</field>
</form>
</formset>
</form-validation>