jakaruta.doc_025
R e i - D r e a m
for Laravel
TOP > Jakarta_EE10 > カスタムバリデーション
Guest
login

最終投稿日:2026年07月26日

カスタムバリデーション
自作バリデーション学習用ソース作成
アノテーションを使ったバリデーションはお手軽ですが欠点があります。
それは『プログラマに制御が来る前にエラーになる』です。
一言で言えば『痒いところに手が届かない』ですね。
そこで役に立つのが『カスタムバリデーション』です。
要するに自力で項目をチェックします。
その前に学習用にソースを統一しましょう。
dto/SampleDto.java

package dto;
import java.io.Serializable;
import jakarta.enterprise.context.Dependent;
import lombok.Data;

@Dependent
@Data
public class SampleDto implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private String memo;
}

※パッケージ『dto』を作成します
BeanBean.java

package view;
import java.io.Serializable;
public abstract class BaseBean implements Serializable {
    private static final long serialVersionUID = 1L;
    protected boolean isFirstTiem = false;
    protected static final String SELF = null;
    public void init() {
        if (isFirstTiem) {
            return;
        }
        initialize();
        isFirstTiem = true;
    }
    protected abstract void initialize() ;
}

SampleBean.java

package view;
import dto.SampleDto;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.Getter;

@Named
@ViewScoped
public class SampleBean extends BaseBean {
    private static final long serialVersionUID = 1L;
    @Inject
    @Getter
    private SampleDto dto;
    
    @Override
    protected void initialize() {
        dto.setName("");
        dto.setMemo("");
    }
    public String submit() {
        System.out.println("[name]=" + dto.getName() + "、[memo]=" + dto.getMemo() + "です。");
        return null;
    }
}

sample.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="jakarta.faces.html"
    xmlns:f="jakarta.faces.core"
    xmlns:p="jakarta.faces.passthrough">
    <f:metadata>
        <f:event type="preRenderView" listener="#{sampleBean.init}" />
    </f:metadata>
<h:head>
    <title>Hello World</title>
    <h:outputStylesheet library="css" name="style.css" />
    <h:outputScript library="js" name="base.js" />
</h:head>
<h:body>
    <h1>おはよう世界!</h1>
    <h:form id="sample_form" prependId="false">
        <h:messages errorClass="alert-danger" warnClass="alert-warning" infoClass="alert-info" layout="list" />
        
        <h:inputText id="inpText" value="#{sampleBean.dto.name}" />
        <BR />
        <h:inputText id="inpMemo" value="#{sampleBean.dto.memo}"/>
        <BR />
        <h:commandButton value="自画面遷移" action="#{sampleBean.submit()}" />
    </h:form>
</h:body>
</html>

自作バリデーション用雛形ソース作成
学習に必要な基本セットが揃いました。
それではカスタムバリデーション用の環境も作成します。
パッケージ『view.validator』へ以下クラスを作成しましょう。
BaseValidator.java

package view.validator;
import java.io.Serializable;
public class BaseValidator implements Serializable {
    private static final long serialVersionUID = 1L;
    
}

SampleValidator.java

package view.validator;
import jakarta.enterprise.context.Dependent;

@Dependent
public class SampleValidator extends BaseValidator {
    private static final long serialVersionUID = 1L;
    
}

これでカスタムバリデーションの『共通』『SampleBean』用クラスができました。
最後に『SampleBean.java』にバリデーションクラスを注入します。
SampleBean.java

package view;
import dto.SampleDto;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.Getter;

import view.validator.SampleValidator;

@Named
@ViewScoped
public class SampleBean extends BaseBean {
    private static final long serialVersionUID = 1L;
    @Inject
    @Getter
    private SampleDto dto;

    @Inject
    private SampleValidator valid;     
    @Override
    protected void initialize() {
        dto.setName("");
        dto.setMemo("");
    }
    public String submit() {
        System.out.println("[name]=" + dto.getName() + "、[memo]=" + dto.getMemo() + "です。");
        return null;
    }
}

カスタムバリデーション実装
それでは実際にバリデーションをカスタムしてみましょう。
BaseValidator.java

package view.validator;
import java.io.Serializable;
import java.util.List;

import jakarta.faces.context.FacesContext;
import view.validator.SampleValidator.ValidationError;
public class BaseValidator implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // エラーメッセージを「FacesContext」にすべて追加する

    protected void addMessage(List<ValidationError> errors) {
        FacesContext context = FacesContext.getCurrentInstance();
        for (ValidationError error : errors) {
            // 第1引数 = 各項目UIのID属性名
            context.addMessage(error.targetId(), error.facesMessage());
        }
    }
}

共通のバリデーションクラスです。
個別バリデーション処理でセットされた『エラー情報』を画面へ出力するための処理です。
SampleValidator.java

package view.validator;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import dto.SampleDto;
import jakarta.enterprise.context.Dependent;
import jakarta.faces.application.FacesMessage;

@Dependent
public class SampleValidator extends BaseValidator {
    private static final long serialVersionUID = 1L;
    // IDとメッセージをセットにするための内部クラス(Record)

    public record ValidationError(String targetId, FacesMessage facesMessage) implements Serializable {}
    
    // バリデーション関数
    public boolean validate(SampleDto dto) {
        // プロパティファイルの読み込み
        ResourceBundle bundle = ResourceBundle.getBundle("ValidationMessages");
        // バリデーションメッセージインスタンス生成
        List<ValidationError> errors = new ArrayList<>();
        
        // [name]必須チェック
        if (dto.getName() == null || dto.getName().isBlank()) {
            String pattern = bundle.getString("error.required.name");
            String formattedMsg = pattern.replace("${validatedValue}", dto.getName());
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, formattedMsg, null);
            errors.add(new ValidationError("inpText", msg));
        }
        // [memo]必須チェック
        if (dto.getMemo() == null || dto.getMemo().isBlank()) {
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "メモは必須入力です。", null);
            errors.add(new ValidationError("inpMemo", msg));
        }
        // エラーがある場合は画面にエラーメッセージを出力する
        if (errors.size() > 0) {
            this.addMessage(errors);
        }
        
        return errors.size() == 0;
    }
}

① 各項目に対応する『ID属性名』と『エラーオブジェクト』を格納する《ValidationError》を定義します。
② 『List<ValidationError>』型を返却値とし、『SampleDto』を引数とした関数を作成します。
③『resources/ValidationMessages.properties』を使用し『KEY名』でエラーメッセージを指定する場合は読み込みます。
④ バリデーション情報格納用インスタンス『errors』を初期化。
⑤『FacesMessage』型「msg」インスタンスにエラーメッセージを格納します。
・第1引数 = FacesMessage.SEVERITY_ERROR(エラー情報固定値)
・第2引数 = 実際にエラー文言(直接記述しても「ValidationMessages.properties」を利用しても良い)
・第3引数 = null(詳細エラー文言。画面上段にエラー文言一覧として出力する場合は不要です)
ポイント①

[name]必須チェックに注目して下さい。
以下処理で、エラーメッセージの内容を置換しています。
    String formattedMsg = pattern.replace("${validatedValue}", dto.getName());

これはビーンバリデーションの様に「{}」内をデコードしてくれないからです。
自作バリデーションの少し不便な所が垣間見えますね。

ポイント②

各項目がエラー判定となった場合は『errors.add』関数にエラー情報を追加します。
その際『ValidationError』を初期化した内容を追加しますが、第1引数に注目して下さい。
ただの文字列ですが、これは何でしょう?

答えは、対象項目UIの『ID属性名』となります。
従って「.xhtml」側の《form》タグの『prependId』属性によっては値が変わるのでご注意を!
    ※ 画面間での値の受取方法 参照

次にバッキングビーン側に作成したバリデーションを呼び出す処理を追加します。
SampleBean.java

package view;
import dto.SampleDto;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.Getter;
import view.validator.SampleValidator;

@Named
@ViewScoped
public class SampleBean extends BaseBean {
    private static final long serialVersionUID = 1L;
    @Inject
    @Getter
    private SampleDto dto;
    @Inject
    private SampleValidator valid;
    
    @Override
    protected void initialize() {
        dto.setName("");
        dto.setMemo("");
    }
    public String submit() {
        System.out.println("[name]=" + dto.getName() + "、[memo]=" + dto.getMemo() + "です。");
        // カスタムバリデーション

        if (!this.valid.validate(dto)) {
            // エラーがある場合は処理終了
            return null;
        }
        // エラーがない場合は処理続行
        System.out.println("処理続行!");
        return null;
    }
}

バッキングビーンの追加はたったこれだけです。
可読性を上げるため、バッキングビーンには極力『イベント』のみ記述しましょう。
最後にビュー側を少し修正します。
sample.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="jakarta.faces.html"
    xmlns:f="jakarta.faces.core"
    xmlns:p="jakarta.faces.passthrough">
    <f:metadata>
        <f:event type="preRenderView" listener="#{sampleBean.init}" />
    </f:metadata>
<h:head>
    <title>Hello World</title>
    <h:outputStylesheet library="css" name="style.css" />
    <h:outputScript library="js" name="base.js" />
</h:head>
<h:body>
    <h1>おはよう世界!</h1>
    <h:form id="sample_form" prependId="false">
        <h:messages errorClass="alert-danger" warnClass="alert-warning" infoClass="alert-info" layout="list" />

        
        <h:inputText id="inpText" value="#{sampleBean.dto.name}" style="width:300px;height:25px;"
                styleClass="#{not empty facesContext.getMessageList('inpText') ? 'parts_text_err' : 'parts_text'}" />
        <BR />
        <h:inputText id="inpMemo" value="#{sampleBean.dto.memo}"/>
        <BR />
        <h:commandButton value="自画面遷移" action="#{sampleBean.submit()}" />
    </h:form>
</h:body>
</html>

『styleClass』属性に注目して下さい。
    #{not empty facesContext.getMessageList('inpText')
つまり『ID属性名にエラーがある場合』には、描画されたHTMLは『..class="parts_text_err"..』となります。
これでエラー用のスタイルを適用する事が可能となります。
○ AJAXでも問題なく動きます
念のためAJAXでも実装してみます。
ボタン部分のコードを以下の様に修正します。
sample.xhtml

<h:commandButton value="自画面遷移" actionListener="#{sampleBean.submit()}">
    <f:ajax execute="@form" render="@form" />
</h:commandButton>

    ※より厳密なAJAXにする場合は、バッキングビーン側のメソッドを『void』にしてね
色々なFacesMessage
カスタムバリデーションでエラーメッセージとしてい定義するため以下の指定をしました。
    FacesMessage(FacesMessage.SEVERITY_ERROR...
実はこれ以下4種類あります。
○ SEVERITY_INFO            情報として
○ SEVERITY_WARN          警告として
○ SEVERITY_ERROR        エラーとして
○ SEVERITY_FATAL          致命的なエラーとして
よく情報をシステムに登録した際に以下の様なメッセージが出たりします。
① 正常に登録が完了しました。
② 情報が取得できなかったため前回の情報を表示しています。
③ 項目『お名前』は必須項目です。
これみんな赤い表示だと変ですよね?
早速実装してみましょう。
まずはメッセージ管理用ファイルを作成します。
InfomationMessages.properties

I001=正常に登録が完了しました。
W001=情報が取得できなかったため前回の情報を表示しています。

SampleBean.java

public String submit() {
    System.out.println("[name]=" + dto.getName() + "、[memo]=" + dto.getMemo() + "です。");

    ResourceBundle InfoBundle = ResourceBundle.getBundle("InfomationMessages");
    Severity severityLevel;
    String msg;
    switch (dto.getName()) {
    case "hoge":
        severityLevel = FacesMessage.SEVERITY_INFO;
        msg = InfoBundle.getString("I001");
        break;
    case "foo":
        severityLevel = FacesMessage.SEVERITY_WARN;
        msg = InfoBundle.getString("W001");
        break;
    default:
        severityLevel = FacesMessage.SEVERITY_ERROR;
        msg = "エラーだよ。";
    }
    FacesMessage resultMsg = new FacesMessage(sseverityLevel, msg, null);
    FacesContext context = FacesContext.getCurrentInstance();
    context.addMessage(null, resultMsg);
    
    return null;
}

全体的なクラスの内容は「カスタムバリデーション実装」を参照してください。
今回は新たに『InfomationMessages.properties』を作成してメッセージを参照しています。
実際にDBに登録して《成功|警告|エラー》を制御する訳にもいかないので、簡易的に入力の内容で振り分けています。
やっている事は単純で『FacesMessage』クラスから《成功|警告|エラー》情報と、メッセージを使って初期化します。
その内容をコンテキストインスタンスに初期化した内容を突っ込んでます。
テキストボックスの内容を『hoge』『foo』その他にしてボタン押下すると確認できます。
infoメッセージ
警告メッセージ
エラーメッセージ
    ※CSSの内容は、ビーンバリデーションの書式とサンプル 参照
きっぷる
ログインしてコメントを残そう!!


きっぷる