[Servlet]バリデーション機能(Bean Validation)の実装

2022/06/15に公開


◆はじめに

本記事は、以下の記事の「InputFunction」プロジェクトをベースに作成しています。
入力して送信した文字列を画面上に返して表示するEcho機能を作成しています。
Echo機能を持った「InputFunction」に、バリデーション機能を追加しました。

https://zenn.dev/nakohama/articles/9f08b12905196e

実際に、実装後に以下のURLにアクセスして、
バリデーション機能を働かせると、以下のようになります。
空欄の入力に対してバリデーション機能が働きます。

http://localhost:8080/InputFunction/echo.jsp

テキストボックスが空欄のまま送信ボタンを押すと、
Validation機能が働きます。
「この項目は必須入力です。」と注意メッセージが表示されます。


◆Bean Validationとは

バリデーション(入力チェック)用のフレームワークです。
アノテーションベースでバリデーション機能を追加できます。
Java EE6 から追加され、Spring などにも導入されています。


分散されやすい入力チェックを、一箇所にまとめまられるますし、
今回は、入力が一つなのであまり有り難みはないかもしれませんが、
大規模開発や、複数入力になるほど、利用価値があると思います。

本記事では、バリデーション機能のうち、
空欄のバリデーション(@NotNull)のみ実装しています。


他にも、上限値(@Max)を設定したり、
クレジットカード番号のみ(@CreditCardNumber)
など様々にあります。


それらに関しては、
本記事内では、触れていませんが、その辺は、
最後に参考として紹介している書籍やその他のサイトを参考にするといいかもしれません。


◆前提/環境など

はじめにお伝えしたように、
本記事は、以下の記事の「InputFunction」プロジェクトをベースに作成しています。
基本的なプロジェクトの構成や、ライブラリについては以下をご覧ください。

https://zenn.dev/nakohama/articles/9f08b12905196e


※本記事では、 eclipseは以下のバージョンでの画像を使用しています。
日本語化はしていません。
Pliadesでも同様の動作をすることは確認済み。


◆手順

① Bean Validation用のライブラリの用意

(1)ライブラリのインストール

本記事では、JavaEEの環境構築は行なっておらず、
JavaSE環境で行っているため、
以下の3つのLibraryをネット上からダウンロードして準備する必要があります。
(1)Bean Validation API ・・・バリデーションAPIのインターフェースをまとめたjar
(2)hibernate-validator ・・・バリデーションAPIの実装クラスをまとめたjar
(3)slf4j-api・・・hibernate-validatorの内部で利用するjar(ロギング用)

Pliadesでの環境で行った際は、
・Bean Validation API (1.0.0 GA)
・hibernate-validator(4.0.2.GA)
・slf4j-api (1.6.1)
のバージョンのライブラリで動作を確認しました。


ただ、本記事では、Eclipse2022-03で進めており、
上記のバージョンでは、エラーが生じたため以下のバージョンを入れています。
こちらでは、合計5つのライブラリをいれます。


(1)Bean Validation API (2.0.1.Final)

https://jar-download.com/artifacts/javax.validation/validation-api/2.0.1.Final/source-code

(2)hibernate-validator(5.3.4.Final)

https://jar-download.com/artifacts/org.hibernate/hibernate-validator/5.3.4.Final/source-code

(3)slf4j-api (1.6.1)

https://jar-download.com/artifacts/org.slf4j/slf4j-api/1.6.1/source-code


自分が詰まった点なのですが、バージョンについては注意が必要かもしれません。
もしかしたら、実行時に、以下のようなエラーが出るかもしれません。
その場合は、ライブラリかEclipseのバージョンを変えてみるといいかもしれません。


以下のページを参考にしました。

https://teratail.com/questions/139339


さらに、未だに理由はよくわかってないのですが、
Eclipse2022-03の環境の際には、実行時に別のエラーが表示されたため
エラーに従ってその他、以下のライブラリも追加しています。
(4) jboss-logging (3.1.4.GA)

https://jar-download.com/artifacts/org.jboss.logging/jboss-logging/3.1.4.GA/source-code

(5)classmate (1.0.9.14-jre14)

https://jar-download.com/maven-repository-class-search.php?search_box=com.fasterxml.classmate.TypeResolver


以下の記事にも似たようなことが書かれていましたので、参考までに。

https://shun1adhocblog.wordpress.com/2013/05/10/gaeでspringのvalidation機能を使おうとしてハマった/


(2)プロジェクトにライブラリを追加

「lib」のフォルダに、ライブラリをコピーします。
Echo機能用のライブラリも入っているため、
Eclipse2022-03の環境では、以下のような構成になりました。


(3)ClassPathを通す

libフォルダにライブラリを置くだけでは、
ライブラリをインポートした際に使えません。
ClassPathを通す必要があります。

事前に設定をしておきます。
プロジェクトのプロパティから、「Java Build Path」を開き、
「librariles」のタブから以下の画面を表示させます。

「Add JARs」ボタンを押します。
WebContentのlibフォルダに入れたライブラリーを選択します。
追加されたら、「Apply」で適用してプロパティ画面を閉じます。


プロジェクトのディレクトリに「Referenced Libraries」が追加され、
その中に、追加されたライブラリ名が並んでいます。

importのエラーも解消されます。

② バリデーション機能のコードの記述

以下の3つのファイルを用意します。
(1)echo.jsp
(2)EchoServlet.java
(3)TextForm.java

(1)(2)は、既に作っているはずなので、
新たにファイルを作成するのは、(3)だけかと思います。
ただ、コードの中身については、
どのファイルも、Echo機能のコードに追加する形で、
新たに、Validation機能のコードを記述しています。


(1)echo.jspのコード

echo.jsp
<%@page language="java" contentType = "text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@page isELIgnored= "false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<title>Echo機能+Validation機能</title>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>

<body>
	
 <h3>Echo機能+Validation機能</h3>

  <%-- 入力フォーム --%>
 <form action="echo" method="post">
  <br />入力してください : 
  
  <%-- テキストボックス(m) --%>
  <input type="text" name="m" />
  
   <%-- Validationメッセージの出力  --%>
   <%-- var="vm" → c:out で valueにセットする際に使う名称   --%>
   <%-- [' ']には、 MessageForm.javaのテキストボックスのメッセージ保持用のパラメータ名を入れる   --%>
 <c:forEach items="${validationMessage['textMessage']}" var="vm">
   <c:out value="${vm}"/>
 </c:forEach>
 
<br />
 
 <%-- 送信ボタン --%>
  <input type="submit" /> <br />

 </form>

 <br />
 
  <%-- Echoメッセージの出力--%>
 <c:if test="${ not empty requestScope.message }">
  入力された文字は、「${ requestScope.message }」です。
 </c:if>
  
</body>


(2)EchoServlet.javaのコード

EchoServlet.java

package input;

import java.io.IOException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

  //URLを定義するアノテーション
    @WebServlet(urlPatterns = {"/echo"})  
    
  //Validation機能を持たせたEchoサーブレットクラス
public class EchoServlet extends HttpServlet{
	private static final long serialVersionUID = 1L;
	
	
	
  //画面からPostされた場合に起動するメインとなるメソッド
	@Override
	protected void doPost(HttpServletRequest request , HttpServletResponse response)throws ServletException,
IOException {
		
		//取得文字のエンコード設定(文字化け防止)
		request.setCharacterEncoding("UTF-8");

	//Validation機能
        //テキストボックスのtextを保持したFormの作成
		TextForm SetForm = toSetForm(request);
		//toValidationFormをバリデーションに通す。
		//引っかかった場合はrequestにvalidationMessageをセットする
		setValidationAttribute(request,SetForm);
		
	//Echo機能
	    //Echoメッセージのセット
		if(request.getAttribute("validationMessage")  == null) {			
	   //バリデーションメッセージがrequestに付与されていない場合、
	   //Formに保持していたtextをrequestにEcho用に付与する
			String EchoMessage = SetForm.getTextMessage();
			request.setAttribute("message",EchoMessage);
		}
		
		//コンソールに出力
		System.out.println("validationMessage:"+request.getAttribute("validationMessage"));
		System.out.println("message:"+request.getAttribute("message"));	
		
		//RequestDispatcher
		RequestDispatcher dispatcher = request.getRequestDispatcher("echo.jsp");
		dispatcher.forward(request, response);	
	}
	
	
	
	//テキストを保持したFormを作成するメソッド
	private TextForm toSetForm(HttpServletRequest request) {
		//バリデーションに通すためのフォームのインスタンス作成
		TextForm SetForm = new TextForm();
		
		//テキストボックスの文字列を取得
		String text = request.getParameter("m");
		
		//テキストボックスが空欄ではない場合
		if(text.isEmpty() == false) {
			//テキストを保持したFormを返す(今回は実装してないがNull以外のバリデーション機能もあるため)
			SetForm.setTextMessage(text);
		}
		return SetForm;
	}
	

	
	//バリデーション用のメソッド
	private void setValidationAttribute(HttpServletRequest request , TextForm toValidationForm) {
			
		//toValidationFormに対してバリデーションを実行
		Map<String,List<String>> validationMessage = validate(request,toValidationForm);
		
		//バリデーションメッセージがセットされている場合、requestにバリデーションメッセージを付与
		if(validationMessage.isEmpty() == false) {
			request.setAttribute("validationMessage", validationMessage);
			return;
		}    
	}
	
	
	
	//validate
	private Map<String , List<String>> validate(HttpServletRequest request,TextForm toSetForm){
		
		ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
		Validator validator = validatorFactory.getValidator();
		Set<ConstraintViolation<TextForm>> validationResult = validator.validate(toSetForm);
		
		Map<String , List<String>> ret = new HashMap<String , List<String>>();
		
		for(ConstraintViolation<TextForm> violation : validationResult) {	
			String propertyPath = violation.getPropertyPath().toString();
			List<String> messages = ret.get(propertyPath);
			if(messages == null) {
				messages = new ArrayList<String>();
				ret.put(propertyPath, messages);
			}
			messages.add(violation.getMessage());
		}
		return ret;	
	}
}


(3)TextForm.javaのコード

TextForm.java
package input;

import java.io.Serializable;
import javax.validation.constraints.NotNull;



//テキストボックスのメッセージを保持するForm(バリデーション機能を追加)
public class TextForm implements Serializable{
	
	private static final long serialVersionUID = 1L;
	
	//バリデーション用(空欄の場合のエラーメッセージ設定)
    @NotNull ( message ="この項目は必須入力です。")
    
    //テキストボックスのメッセージ保持用
    private String textMessage;

    //テキストボックスのメッセージのセット
    public void setTextMessage(String textMessage) {
    	this.textMessage = textMessage;
    }	
    
    //テキストボックスのメッセージのゲット
    public String getTextMessage() {
    	String textMessage = this.textMessage;
    	return textMessage;
    }	
}

③ 動作確認

それでは、サーバーを起動して、
以下のURLにアクセスしましょう。

http://localhost:8080/InputFunction/echo.jsp

空欄のまま、送信ボタンを押して、
バリデーション機能を働かせると、以下のようになります。
空欄の入力に対してバリデーション機能が働きます。

もちろん、文字列を入力して、
送信ボタンを押せば、Echo機能が働きます。

以上のようになっていれば、成功です。
ちなみに、Eclipse上のコンソールには、
以上の動作を行うと、以下のような出力がされているはずです。

・Validation機能(空欄を送信)時のパラメータ内の文字列について

validationMessage:{textMessage=[この項目は必須入力です。]}
message:null
Validation機能(空欄を送信)時ですが、
validate関数にSetFormを通した後、
requestのvalidationMessageに、
{textMessage=[この項目は必須入力です。]}が格納されています。
あー、でもなんで、textMessageにバリデーションのメッセージが格納されてるんだろうか。
また、requestのmessageは、nullになるので、
その場合は、if文でEcho機能の処理の箇所は飛ばしています。


・Echo機能(文字列を送信)時のパラメータ内の文字列について

validationMessage:null
message:1・2・3!文字列
Echo機能(文字列を送信)時ですが、
SetFormの作成時に、textMessageに文字列が格納され、
その後、requestのmessageに渡されます。
また、validate関数にSetFormを通した後、
validationMessageは、nullになっています。
そして、requestのmessageは、nullではないので、
Echo機能の箇所が処理されて、表示されます。



◆さいごに

以上です。いかがだったでしょうか。
とりあえず、動くものは作れたでしょうか。
今回のおさらいです。


①Bean Validationを利用した入力画面を作成した

②値を保持するForm.javaのようなValueオブジェクトが必要

③Valueオブジェクトに、@NotNullなどアノテーションベースで実装できる


コードに関しての詳しい説明に関しては、
あまり書いていません。一応、コード内にコメントをつけてます。
あとは、他の参考に載せている書籍やサイトなど調べてみてください。


最後まで、ご覧いただきありがとうございました。


◆参考

本記事は、以下の書籍を大いに参考にしています。
より詳しく知りたい方は、どうぞ。

https://www.amazon.co.jp/dp/4797362596

その他、ValidationBeanに関する記事

https://qiita.com/opengl-8080/items/3926fbde5469c0b330c2

https://www.tuyano.com/index3?id=6209570301018112&page=5

Discussion