カスタムバリデーション
自作バリデーション学習用ソース作成
それは『プログラマに制御が来る前にエラーになる』です。
一言で言えば『痒いところに手が届かない』ですね。 そこで役に立つのが『カスタムバリデーション』です。
要するに自力で項目をチェックします。 その前に学習用にソースを統一しましょう。
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;
}
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() ;
}
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;
}
}
自作バリデーション用雛形ソース作成
それではカスタムバリデーション用の環境も作成します。
パッケージ『view.validator』へ以下クラスを作成しましょう。
package view.validator;
import java.io.Serializable;
public class BaseValidator implements Serializable {
private static final long serialVersionUID = 1L;
}
package view.validator;
import jakarta.enterprise.context.Dependent;
@Dependent
public class SampleValidator extends BaseValidator {
private static final long serialVersionUID = 1L;
}
最後に『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;
}
}
カスタムバリデーション実装
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());
}
}
}
個別バリデーション処理でセットされた『エラー情報』を画面へ出力するための処理です。
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;
}
}
② 『List<ValidationError>』型を返却値とし、『SampleDto』を引数とした関数を作成します。
③『resources/ValidationMessages.properties』を使用し『KEY名』でエラーメッセージを指定する場合は読み込みます。
④ バリデーション情報格納用インスタンス『errors』を初期化。
⑤『FacesMessage』型「msg」インスタンスにエラーメッセージを格納します。
・第2引数 = 実際にエラー文言(直接記述しても「ValidationMessages.properties」を利用しても良い)
・第3引数 = null(詳細エラー文言。画面上段にエラー文言一覧として出力する場合は不要です)
[name]必須チェックに注目して下さい。
以下処理で、エラーメッセージの内容を置換しています。
String formattedMsg = pattern.replace("${validatedValue}", dto.getName());
自作バリデーションの少し不便な所が垣間見えますね。
各項目がエラー判定となった場合は『errors.add』関数にエラー情報を追加します。
その際『ValidationError』を初期化した内容を追加しますが、第1引数に注目して下さい。
ただの文字列ですが、これは何でしょう?
従って「.xhtml」側の《form》タグの『prependId』属性によっては値が変わるのでご注意を!
※ 画面間での値の受取方法 参照
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;
}
}
可読性を上げるため、バッキングビーンには極力『イベント』のみ記述しましょう。 最後にビュー側を少し修正します。
<!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>
#{not empty facesContext.getMessageList('inpText') つまり『ID属性名にエラーがある場合』には、描画されたHTMLは『..class="parts_text_err"..』となります。
これでエラー用のスタイルを適用する事が可能となります。
ボタン部分のコードを以下の様に修正します。
<h:commandButton value="自画面遷移" actionListener="#{sampleBean.submit()}">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
色々なFacesMessage
FacesMessage(FacesMessage.SEVERITY_ERROR... 実はこれ以下4種類あります。
② 情報が取得できなかったため前回の情報を表示しています。
③ 項目『お名前』は必須項目です。
早速実装してみましょう。
まずはメッセージ管理用ファイルを作成します。
I001=正常に登録が完了しました。
W001=情報が取得できなかったため前回の情報を表示しています。
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』その他にしてボタン押下すると確認できます。




※CSSの内容は、


<!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>