Spring FrameworkでValidationを実装する際に泥沼にハマる...

f:id:ts0818:20210609165719j:plain

チェックマークcheckmark, check mark)は、「」(または、「v」や「レ」)のように下か右下に伸びたあと右上にはねる線であらわされる図形・記号である。イギリス英語では tick と呼ばれる。

チェックマーク - Wikipedia

歴史

チェックマークはローマ帝国期に作られていたと考えられている。"V" はラテン語で真実を意味する単語 veritas を縮めるために使われた。これは、肯定の返答、真実、または表中の項目への承認を示すために使われた。長い期間をかけて、このマークのデザインは変化した。

チェックマーク - Wikipedia

⇧ 衝撃...「ローマ帝国期」には「チェックマーク」が爆誕していたんだそうな。

ローマ帝国の起源は、紀元前8世紀中ごろにイタリア半島を南下したラテン人の一派がティベリス川(現:テヴェレ川)のほとりに形成した都市国家ローマである(王政ローマ)。

ローマ帝国 - Wikipedia

⇧ 紀元前8世紀中ごろには、「チェックマーク」が使われていたって考えると、不思議な気持ちになりますね、どうもボクです。

というわけで、今回もJavaフレームワークSpring Framework」についてです。

レッツトライ~。 

 

Validationとは?

Wikipediaさんに聞いてみる。

In software project managementsoftware testing, and software engineeringverification and validation (V&V) is the process of checking that a software system meets specifications and requirements so that it fulfills its intended purpose. It may also be referred to as software quality control. It is normally the responsibility of software testers as part of the software development lifecycle. In simple terms, software verification is: "Assuming we should build X, does our software achieve its goals without any bugs or gaps?" On the other hand, software validation is: "Was X what we should have built? Does X meet the high level requirements?"

https://en.wikipedia.org/wiki/Software_verification_and_validation

⇧ 超ザックリ要約すると「ソフトウェアシステムが仕様や要件を満たしているかをチェックするプロセス」ってことになるみたいね。

で、「Verification」と「Validation」の違いはというと、 

Verification and validation are not the same things, although they are often confused. Boehm succinctly expressed the difference as

  • Verification: Are we building the product right?
  • Validation: Are we building the right product?

https://en.wikipedia.org/wiki/Software_verification_and_validation

⇧ う、う~ん...よう分からん...

このあたりは、Wikipediaの続きを読んでいただくとして、「Validation」ってのは、「あらかじめ決めておいた仕様や要件を満たしているかチェックすること」って意味合いってことになるんですかね。

 

Bean Validationとは?

Wikipediaさんに聞いてみる。

Bean Validation defines a metadata model and API for JavaBean validation. The metadata source is annotations, with the ability to override and extend the meta-data through the use of XML validation descriptors.

Originally defined as part of Java EE, version 2 aims to work in Java SE apps as well.

https://en.wikipedia.org/wiki/Bean_Validation

Java Bean Validation (JSR 303) originated as a framework that was approved by the JCP as of 16 November 2009 and accepted as part of the Java EE 6 specification. The Hibernate team provides with Hibernate Validator the reference implementation of Bean Validation and also created the Bean Validation TCK any implementation of JSR 303 needs to pass.

https://en.wikipedia.org/wiki/Bean_Validation

Java言語独自の「Validation」ってことみたいね。「JSR(Java Specification Request)」っていう、Javaプラットフォームに関する技術や仕様についての公式文書らしい。

⇧ 現在のところ、「Bean Validation」の仕様については「JSR 380」が最終稿なのかね。

ここで気になるのが、Javaで「Validation」と言ったら、「Bean Validation」以外は無いと考えて良いのだろうか... 

「Bean」の必要条件を確認してみた。

Beanの必要条件

など。

https://ja.wikipedia.org/wiki/JavaBeans

⇧ う~ん、上記を満たす「クラス」であれば良い、ってことだとは思うんだけども、「文字列」とか「数値」とか「バリデーション」したいってなった場合は、「Bean」を作らないといけないってことなのかね?

ただ、HTTP通信のGETなんかで、渡ってくるパラメーターが「文字列」1つのみの場合も「Bean」を作成しておかないと「バリデーション」ができないんだとしたら、考えものな気がするんだが...

 

Spring FrameworkにおけるValidationとは?

公式のドキュメントによると、

spring.pleiades.io

f:id:ts0818:20210604134546p:plain

⇧「core.pdf(英語)」を開いて、「validation」で検索してヒット。 

日本語訳のドキュメントだと、

spring.pleiades.io

⇧ 「コア 」を選択して、「validation」で検索すると、

f:id:ts0818:20210604135238p:plain

⇧「検証、データバインディング、型変換」の項目がヒットするので、

 validation = 検証

ということらしい。

ビジネスロジックとして検証を検討することには長所と短所があり、Spring は検証(およびデータバインディング)の設計を提供します。具体的には、検証は Web 層に結び付けられるべきではなく、ローカライズが容易である必要があり、利用可能な検証ツールをプラグインできる必要があります。これらの懸念を考慮して、Spring は Validator 契約を提供します。これは、基本的であり、アプリケーションのすべてのレイヤーで非常に有用です。

https://spring.pleiades.io/spring-framework/docs/current/reference/html/core.html#spring-core

データバインディングは、ユーザー入力をアプリケーションのドメインモデル(またはユーザー入力の処理に使用するオブジェクト)に動的にバインドできます。Spring は、まさにそれを行うために適切な名前の DataBinder を提供します。Validator および DataBinder は validation パッケージを構成し、これは主に Web レイヤーで使用されますが、これに限定されません。

https://spring.pleiades.io/spring-framework/docs/current/reference/html/core.html#spring-core

⇧「検証」自体はどのレイヤー層でも行えるべき、ってことなんですかね。

Spring は、セットアップインフラストラクチャと Spring 独自の Validator 契約へのアダプターを通じて Java Bean 検証をサポートします。アプリケーションは、Java Bean 検証に従って、Bean 検証をグローバルに一度有効にして、すべての検証ニーズに対してのみ使用できます。Web レイヤーでは、DataBinder の構成に従って、アプリケーションは DataBinder ごとにコントローラーローカル Spring Validator インスタンスをさらに登録できます。これは、カスタム検証ロジックのプラグインに役立ちます。

https://spring.pleiades.io/spring-framework/docs/current/reference/html/core.html#spring-core

⇧ 気になるのは、『Spring は、セットアップインフラストラクチャと Spring 独自の Validator 契約へのアダプターを通じて Java Bean 検証をサポートします。』ってあって、英語版だと、

Spring supports Java Bean Validation through setup infrastructure and an adaptor to Spring’s own Validator contract. Applications can enable Bean Validation once globally, as described in Java Bean Validation, and use it exclusively for all validation needs.

In the web layer, applications can further register controller-local Spring Validator instances per DataBinder, as described in Configuring a DataBinder, which can be useful for plugging in custom validation logic.

https://docs.spring.io/spring-framework/docs/current/reference/pdf/core.pdf

⇧『Spring supports Java Bean Validation through setup infrastructure and an adaptor to Spring’s own Validator contract.』ってことなんだけど、「Spring Framework」が「Bean Validation」をサポートしてくれるのは分かったんだけど、「Validation」の全量が不明なんよね...

改めて、Javaで「Validation」って言った場合、「Bean Validation」以外ってのは存在しないと考えて良いのだろうか...

謎は深まるばかり...

 

Spring FrameworkのValidationがBean Validation以外は存在しないとして

Spring Framework」での「validation」っていうものが、「Bean Validation」のみを考えるで良いとした場合、

penguinlabo.hatenablog.com

Hibernate ValidatorはJSR 380の実装なので、基本的にJSR 380の規格に従った実装ですが、一部、Hibernate Validatorの独自拡張の要素があるので、その拡張についても併せて解説していきます。

Spring Frameworkで使用できるBean Validationアノテーション一覧 - ぺんぎんらぼ

Spring Frameworkでは、バリデーションの実装にHibernate Validatorを使用しているので、JSR 380で規格化されているバリデーションだけでなく、Hibernate Validatorの独自拡張のバリデーションも使用することができます。

Spring Frameworkで使用できるBean Validationアノテーション一覧 - ぺんぎんらぼ

⇧ 上記サイト様が「Spring Framework」で利用できる「Bean Validation」の「アノテーション」一覧をまとめてくれていました。

つまり、上記サイト様に記載のないような条件でチェックせざるを得ない場合は、自分で独自にコーディングとかしてあげないといけない模様。

www.tenohira.xyz

⇧ 上記サイト様が参考になるかと。

 

実際に実施してみる

というわけで、

github.com

⇧ 毎度恒例の上記サイト様のお題を試していきますかね。

パラメタageが0以上の整数であり、パラメタnameが3文字以上の半角英数字かつ先頭文字は半角英字であることをバリデーションせよ。」とあるので、

  • パラメタageが0以上の整数
    @Min(value)
  • パラメタnameが3文字以上の半角英数字かつ先頭文字は半角英字
    @Pattern(regex, flags)

で「バリデーション」していく感じになるのかな。

「バリデーション」失敗時のExceptionなんかについては、

qiita.com

⇧ 上記サイト様が詳しいです。 

今回、POSTなので、

qiita.com

⇧ 上記サイト様を参考に、「curl」コマンドで疎通確認します。 

Webの開発現場だと、

qiita.com

⇧ 上記サイト様の説明にあるように、ツールを導入していくのが一般的みたいですね。

 

で、EclipseでGradleプロジェクトを「サーバーで実行」してから、curlでPOSTしてみたところ、なんか、

警告: Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported] [月 6月 07 14:03:38 JST 2021]

⇧ ってなエラーが出たんだけど、どうやら、

m-namiki.hatenablog.jp

kobe-systemdesign.work

bynatures.hatenadiary.jp

Jackson には com.fasterxml.jackson グループのモノと org.codehaus.jackson のモノがあります。Jackson は 2.0.0 以降からネーミングスペースが前者の com.fasterxml.jackson に移ったため、IDE で Jackson をインポートしようとすると、fasterxml, codehaus のどちらを選ぶかを尋ねられることがあります。ここで間違って codehaus を選ぶと古い Jackson が動いてしまい、なんとなく動きはするけれど細かい挙動が違ったりエラーが発生したりします。

Jackson パッケージは fasterxml が新しい - /home/by-natures/dev*

⇧ 上記サイト様によりますと、依存関係に「jackson」パッケージを追加しないといけないらしい...知らんがな 

 

さらなる悲報...

Windowscurlする場合、

teratail.com

⇧ 「シングルコーテーション」使えない問題があるらしい... 

macOS」使ったことないけど、Web開発は断然「macOS」が向いてる状態が続いてるんですかね?

いろいろ、脱線しましたが、以下のようなディレクトリ構成になりました。

f:id:ts0818:20210609142649p:plain

⇧ 例の如く、「Gradleプロジェクト」から作成しているので、

ts0818.hatenablog.com

⇧ 手前味噌で恐縮ですが、上記を参考に、「プロジェクト・ファセット」 への変換や、「デプロイメント・アセンブリー」で『プロジェクトと外部の依存関係』の追加などの対応は忘れずに。(なんか、「build.gradle」に変更を加える度に、「デプロイメント・アセンブリー」で『プロジェクトと外部の依存関係』の追加をしないと駄目っぽい感じなんかな...面倒くさ過ぎる...)

そして、「Spring Boot」を使わずに「Spring Framework」で「バリデーション」する際のハマりどころがまだありました...

shinodogg.com

バリデーションエラーが起こった場合は、BindResultがホゲホゲではなくて、バリデーションエラーExceptionをハンドリングするメソッドを作ってやるのが特徴です。

SpringMVCでJSONを受付けた時のvalidationで困っている件 | shinodogg.com

⇧ へぇへぇへぇ~、逆に言うと、「JSON」の「バリデーション」について「BindResult」が使えるのか使えないのかをハッキリさせて欲しい気もしますが...

で、「Spring Boot」が使えないケースだと、

docs.spring.io

5.7.2 Configuring a Bean Validation Implementation

Spring provides full support for the JSR-303 Bean Validation API. This includes convenient support for bootstrapping a JSR-303 implementation as a Spring bean. This allows for a javax.validation.ValidatorFactory or javax.validation.Validator to be injected wherever validation is needed in your application.

Use the LocalValidatorFactoryBean to configure a default JSR-303 Validator as a Spring bean:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
			

The basic configuration above will trigger JSR-303 to initialize using its default bootstrap mechanism. A JSR-303 provider, such as Hibernate Validator, is expected to be present in the classpath and will be detected automatically.

SpringMVCでJSONを受付けた時のvalidationで困っている件 | shinodogg.com

⇧ 「XML(Extensible Markup Language)」に上記の一文を追加して上げないと、「JSR-303」の仕様を実装した「Validation API」が「Spring framework 」で利用できないってことらしい...

というか、どの「XML(Extensible Markup Language)」に追記すれば良いのか、全く記載がないというね...初見殺しのドキュメントだな~...

一応、最新の「Spring Framework」のドキュメントを確認してみると、

docs.spring.io

f:id:ts0818:20210609140057p:plain

f:id:ts0818:20210609140028p:plain

⇧ 上記のような感じで、

  • Java」で設定する場合
  • XML(Extensible Markup Language)」で設定する場合

のどっちかの設定をすることで「Spring Framework」で「バリデーション」が使えるようになるらしい。

初見の人が見たら、どっちも必要なんかな~って思ってしまう紛らわしいドキュメントですな...

今回は、「XML(Extensible Markup Language)」で設定していく感じにします。

というか、2021年6月9日(水)時点で、「Bean Validation」の仕様については「JSR-380」が最新だと思うんだけど、対応してるのかね?

公式のドキュメントを見る限り「JSR-330」は出てくるんだけど、「JSR-380」ってワードが出てこない...

ちなみに、「Hibernate Validator」のAPIがバージョン 7.x からパッケージ名が「javax」から「jakarta」に変わったことにより、

stackoverflow.com

⇧ 上記サイト様のような対応が必要らしい。

つまり、もし、バージョン7.x 以降の「Hibernate Validator」のAPIを「Spring Framework」で使用する場合は、自分で「バリデーション」用のクラスを作成してあげないと駄目みたいね、「カスタムバリデーション」ってやつですかね? 

というわけで、今回は、パッケージ名が変更になる前の「Hibernate Validator」のAPIを依存関係に追加する方針としました。(「カスタムバリデーション」作りたくなかったので...)

というか、「javax.validation.NoProviderFoundException」ってエラーが何で起こってるのか分かり辛くて滅茶苦茶に泥沼にハマったんですが、「org.springframework.validation.beanvalidation.LocalValidatorFactoryBean」を「依存性の注入(DI:Dependency injection)」すると、「javax」パッケージの「Validator」しか探してくれないってことなのかね?

まぁ、何が言いたいかというと、必要な依存関係のライブラリが分かり辛いんですよね...

 

準備が整ったら、ファイルを編集で。

■/Spring_Framework_Validation/build.gradle

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java Library project to get you started.
 * For more details take a look at the Java Libraries chapter in the Gradle
 * User Manual available at https://docs.gradle.org/6.3/userguide/java_library_plugin.html
 */

plugins {
    // Apply the java-library plugin to add support for Java Library
    id 'java-library'
}

repositories {
    // Use jcenter for resolving dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:28.2-jre'

	// https://mvnrepository.com/artifact/org.springframework/spring-core
	implementation group: 'org.springframework', name: 'spring-core', version: '5.3.7'

	// https://mvnrepository.com/artifact/org.springframework/spring-context
	implementation group: 'org.springframework', name: 'spring-context', version: '5.3.7'

	// https://mvnrepository.com/artifact/org.springframework/spring-webmvc
	implementation group: 'org.springframework', name: 'spring-webmvc', version: '5.3.7'

//	// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
//	runtimeOnly group: 'org.hibernate.validator', name: 'hibernate-validator', version: '7.0.1.Final'

	// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
	implementation group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.2.0.Final'

	// https://mvnrepository.com/artifact/org.glassfish/javax.el
	implementation group: 'org.glassfish', name: 'javax.el', version: '3.0.1-b12'

//    // https://mvnrepository.com/artifact/org.glassfish/jakarta.el
//    implementation group: 'org.glassfish', name: 'jakarta.el', version: '4.0.1'

//	// https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api
//	implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: '3.0.0'

	// https://mvnrepository.com/artifact/javax.validation/validation-api
	implementation group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'

//	// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor
//    implementation group: 'org.hibernate.validator', name: 'hibernate-validator-annotation-processor', version: '7.0.1.Final'

	// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
	implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.3'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}

⇧ 依存関係としては、「spring-core」「spring-context」「spring-webmvc」「hibernate-validator」「javax.el」「validation-api」「jackson-databind」あたりがあれば良さ気かと。

ただ、

stackoverflow.com

⇧ 上記サイト様の説明を読んだ感じでは、「spring-context」あれば「spring-core」は不要かも。

というか「spring-context-support」 ってライブラリもあるみたいなんで、どれを追加すれば良いのか、正直よく分かりません...

■/Spring_Framework_Validation/src/main/resources/config/spring-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- Spring MVC の機能を使うことを宣言。 -->
	<!-- この宣言をすることで、 @Component などのアノテーションが使えるようになる。 -->
	<mvc:annotation-driven  validator="validator"/>
	<!-- <context:annotation-config />-->
	<!-- AspectJスタイルのSpring AOPを有効化 -->
	<!-- <aop:aspectj-autoproxy/>-->
	<!-- Bean となるクラスファイルが格納されているパッケージを宣言。 -->
	<!-- Spring はこのパッケージ配下を自動でスキャンし、Bean として登録する。 -->
	<context:component-scan base-package="Spring_Framework_Validation.*" />
	<!-- Springでhibernate validator使う場合に必要 -->
	<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />

</beans>

■/Spring_Framework_Validation/WebContent/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

  <!-- 文字のエンコーディングを指定し  -->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>
      <!-- org.Apache.catalina.filters.SetCharacterEncodingFilter -->
      org.springframework.web.filter.CharacterEncodingFilter
    </filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <!-- 上記フィルターをすべての URL で適用する。 -->
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- リスナーを登録 -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>

  <!-- spring.xml -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:/config/spring-web.xml</param-value>
  </context-param>

  <!-- Spring MVC アプリの場合、だいたいは唯一のサーブレットを登録する。 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- すべての URL リクエストについて、上記で登録したサーブレットで処理する。 -->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

■/Spring_Framework_Validation/src/main/java/Spring_Framework_Validation/handler/AbstractRestControllerExceptionHandler.java

package Spring_Framework_Validation.handler;

import javax.validation.ConstraintViolationException;

import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
@Order(1)
public abstract class AbstractRestControllerExceptionHandler extends ResponseEntityExceptionHandler  {

@Override
  protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
    if (body == null) {
      body = ex.getClass().toString();
    }
    return super.handleExceptionInternal(ex, body, headers, status, request);
  }

  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    return handleExceptionInternal(ex, "MethodArgumentNotValid",headers, status, request);
  }

  @Override
  protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    return handleExceptionInternal(ex, "BindException",headers, status, request);
  }

  @ExceptionHandler(ConstraintViolationException.class)
  public ResponseEntity<Object> handleConstraintViolation(
          ConstraintViolationException ex, WebRequest request) {
    return handleExceptionInternal(ex, "Constraint", null, HttpStatus.BAD_REQUEST, request);
  }

  // すべての例外をキャッチする
  // どこにもキャッチされなかったらこれが呼ばれる
  @ExceptionHandler(Exception.class)
  public ResponseEntity<Object> handleAllException(Exception ex, WebRequest request) {
    return super.handleExceptionInternal(ex, "handleAllException", null, HttpStatus.INTERNAL_SERVER_ERROR, request);
  }
}

■/Spring_Framework_Validation/src/main/java/Spring_Framework_Validation/entity/Person.java

package Spring_Framework_Validation.entity;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

public class Person {

  @NotNull
  @Pattern(regexp="^[a-zA-Z][a-zA-Z0-9 -/:-@\\[-\\`\\{-\\~]{3,}")
  private String name;

  @NotNull
  @Min(value=0)
  private int age;

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
}

■/Spring_Framework_Validation/src/main/java/Spring_Framework_Validation/controller/PersonController.java

package Spring_Framework_Validation.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import Spring_Framework_Validation.entity.Person;
import jakarta.validation.Valid;

@RestController
//@Validated
public class PersonController {

  @RequestMapping(value="/", method=RequestMethod.GET)
  public String index() {
    return "Welcome!";
  }

  @RequestMapping(value="/person", method=RequestMethod.POST)
  public String validPerson(@Valid @RequestBody Person person) {
    return HttpStatus.OK.getReasonPhrase();
  }
}

⇧ ってな感じで保存したらば、「アプリケーションサーバー(ここでは、Eclipse内蔵のTomcat)」を起動します。

f:id:ts0818:20210607150834p:plain

起動されたら、

f:id:ts0818:20210607150942p:plain

コマンドプロンプトから「curl」コマンドで、「アプリケーションサーバー(ここでは、Eclipse内蔵のTomcat)」に対してHTTP通信してみます。

■GETでHTTP通信(バリデーションOKな値)

curl http://localhost:8080/Spring_Framework_Validation/ -X GET -H "Content-Type: application/json" -d "{\"name\":\"Mr.pineapple\", \"age\":\"150\"}" 

■POSTでHTTP通信(バリデーションOKな値)

curl http://localhost:8080/Spring_Framework_Validation/ -X GET -H "Content-Type: application/json" -d "{\"name\":\"Mr.pineapple\", \"age\":\"150\"}" 

f:id:ts0818:20210607151207p:plain

今度は、バリデーションエラーが出るような値で試してみます。

■POSTでHTTP通信(バリデーションNGな値)

curl http://localhost:8080/Spring_Framework_Validation/ -X GET -H "Content-Type: application/json" -d "{\"name\":\"33Mr.pineapple\", \"age\":\"150\"}" 

f:id:ts0818:20210609145341p:plain

⇧ ってな感じで、正常に「バリデーション」されてることが確認できました。 

Eclipseの「コンソール」にも

警告: Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String Spring_Framework_Validation.controller.PersonController.validPerson(Spring_Framework_Validation.entity.Person): [Field error in object 'person' on field 'name': rejected value [33Mr.pineapple]; codes [Pattern.person.name,Pattern.name,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.name,name]; arguments []; default message [name],[Ljavax.validation.constraints.Pattern$Flag;@494ea20,^[a-zA-Z][a-zA-Z0-9 -/:-@\[-\`\{-\~]{3,}]; default message [正規表現 "^[a-zA-Z][a-zA-Z0-9 -/:-@\[-\`\{-\~]{3,}" にマッチさせてください]] ] [水 6月 09 13:54:22 JST 2021]

f:id:ts0818:20210609145517p:plain

⇧ バリデーションが機能したことによる例外が投げられてることが確認できました。

それにしても、「Spring Framework」は、いろいろな決まり事が分かり辛いですね...

ネット上の情報も錯綜してますし、もうちょっと公式のドキュメントをどうにかして欲しい気がしますかね...

秘伝のタレみたいな感じで一見さんお断りみたいな書き方にするのは止めて欲しい...

あと、ドキュメントは実現したい機能ごとに完結してるようにして欲しいですね、情報が集約されてないとドキュメントを読む側は辛すぎる...

毎度、モヤモヤ感が半端ないですが、今回はこのへんで。