Springboot はエレガントなパラメーター検証 (Spring Validation) を実装し、if else に別れを告げます

I. 概要

信頼性の高い API インターフェイスを提供し、パラメータを検証して最終的なデータ ストレージの正確性を確保したい場合、これは必不可少必須。たとえば、下の図は、私たちのプロジェクトの 1 つの新しいメニュー検証パラメーター関数を示しています.私は検証のために多くの if else を書きました.これは非常に洗練されていません.退屈な CRUD と比較すると、パラメーター検証はさらに退屈です. これはあくまでメニュー作成の検証であり、メニュー、メニューURL、メニューの親IDが空かどうか、上位メニューが正しく実装されているかどうかを判断するだけで、30~40行を消費しています。コードの管理はもちろん、バックグラウンドで商品などの多くのパラメータを持つインターフェイスを作成します。数百行の検証コードが書き込まれると推定されます。

/**
	 * 验证参数是否正确
	 */
	private void verifyForm(SysMenuEntity menu){
    
    
		if(StringUtils.isBlank(menu.getName())){
    
    
			throw new RRException("菜单名称不能为空");
		}
		
		if(menu.getParentId() == null){
    
    
			throw new RRException("上级菜单不能为空");
		}
		
		//菜单
		if(menu.getType() == Constant.MenuType.MENU.getValue()){
    
    
			if(StringUtils.isBlank(menu.getUrl())){
    
    
				throw new RRException("菜单URL不能为空");
			}
		}
		
		//上级菜单类型
		int parentType = Constant.MenuType.CATALOG.getValue();
		if(menu.getParentId() != 0){
    
    
			SysMenuEntity parentMenu = sysMenuService.getById(menu.getParentId());
			parentType = parentMenu.getType();
		}
		
		//目录、菜单
		if(menu.getType() == Constant.MenuType.CATALOG.getValue() ||
				menu.getType() == Constant.MenuType.MENU.getValue()){
    
    
			if(parentType != Constant.MenuType.CATALOG.getValue()){
    
    
				throw new RRException("上级菜单只能为目录类型");
			}
			return ;
		}
		
		//按钮
		if(menu.getType() == Constant.MenuType.BUTTON.getValue()){
    
    
			if(parentType != Constant.MenuType.MENU.getValue()){
    
    
				throw new RRException("上级菜单只能为菜单类型");
			}
			return ;
		}
	}

小さな友達は、パラメーターの検証を追加しなくても大丈夫だと言うでしょうか? それともパラメータ検証をフロントエンドに置く?それではあなたの考えは単純すぎます.世界は私たちが想像するよりも安全ではありません.ブラウザをバイパスし、HTTPツールを直接使用してリクエストをシミュレートし、不正なパラメータをバックエンドAPIインターフェースに渡してアクセスする「ハッカー」がいる可能性があります. “不可告人”目的たとえば、SQLインジェクション攻撃は、多くの場合、追加したくないということではなく、パラメーター検証を実現する機能を迅速に追加するための統一された便利な方法がないことが多いと思います。

世界で遭遇する困難のほとんどは、特に開発エコロジーが非常に完全な Java の場合、すでに解決策を持っています. Java は 2009 年に Bean Validation 仕様を提案し、JSR303、JSR349、および JSR380 の 3 つの標準を通過まし.トップに固執し、2.0 に発展します。慎重な友人は、 Jakarta Bean Validation 3.0では3.0ここ に大きな変更はなく、パッケージ名と名前空間だけが変更されていること
ここに画像の説明を挿入に気付くかもしれません。実際、これはまだBean Validation 2.0の実装です。Bean Validationは、私たちがずっと前に学んだ JPA と同じです. 仕様を提供するだけで、特定の実装を提供するものではありません. 現在、Bean Validation 仕様を実装するデータ検証フレームワークには、主に次のものがあります。
ここに画像の説明を挿入

  • Hibernate バリデーター
  • Apache BVal には、
    Hibernate は時代遅れの ORM フレームワークにすぎないと言う友人がいるかもしれません。今は誰も使わないの?実際、Hibernate はEverything dataのスローガンの下にあり、Hibernate Search、Hibernate OGM およびその他のソリューションも提供しています。Hibernate は中国ではあまり使用されておらず、mybatis のようなセミオーム フレームワークは主に中国で使用されています。Google のトレンドを見てみましょう。

mybatis、jpa、Hibernate の中国での人気を検索:
ここに画像の説明を挿入
mybatis、jpa、Hibernate の世界での人気を検索:
写真を入れられない理由がわからないので、小さな写真を撮りましょう

国内の開発環境から、開発者の99.99%がSpringを使用しているに違いないとも言え、Spring ValidationにはBean Validationのカプセル化サポートが組み込まれている. Bean Validation の呼び出し、API メソッドの提供。実装原理に関しても、Spring AOP インターセプトに基づいており、最終的に Bean Validation のさまざまな実装フレームワークを呼び出します。たとえば、Hibernate Validator です。検証関連の操作の実現は Spring トランザクション トランザクションに似ており、宣言型トランザクションは@Transactionalアノテーションによって実現されます。次に、Spring Boot でパラメーター検証を実装する方法を学びましょう。

2. 注意事項

始める前に、この記事に含まれる可能性のある注釈について理解しましょう。javax.validation.constraintsパッケージでは、一連の制約 ( constraint ) アノテーションが定義されています。以下のように、合計22:
ここに画像の説明を挿入
大まかに次のカテゴリに分類できます。

2.1 null チェックと非 null チェック

  • @NotBlank: 文字列が null でない場合にのみ使用でき、#trim() の後の文字列の長さは 0 より大きくなければなりません。
  • @NotEmpty: コレクション オブジェクトの要素が 0 ではない、つまり、コレクションが空でなく、文字列が null でない場合にも使用できます。
  • @NotNull: null にすることはできません。
  • @Null : null でなければなりません。

2.2 数値チェック

  • @DecimalMax(value) : 注釈付き要素は、値が指定された最大値以下である数値でなければなりません。
  • @DecimalMin(value) : 注釈付き要素は、指定された最小値以上の値を持つ数値でなければなりません。
  • @Digits(integer, fraction) : 注釈付き要素は、値が許容範囲内にある数値でなければなりません。
  • @Positive : 正の数を決定します。
  • @PositiveOrZero: 正または 0 を判断します。
  • @Max(value) : フィールドの値は、この値以下にする必要があります。
  • @Min(value) : フィールドの値は、この値以上である必要があります。
  • @Negative : 負数を判定します。
  • @NegativeOrZero: 負または 0 を判断します。

2.3 ブール値チェック

  • @AssertFalse : 注釈付き要素は true でなければなりません。
  • @AssertTrue : 注釈付き要素は false でなければなりません。

2.4 長さチェック

  • @Size(max, min): フィールドのサイズが最小値と最大値の間であるかどうかを確認します。これは、文字列、配列、コレクション、マップなどにすることができます。

2.5 日付チェック

  • @Future : 注釈付き要素は未来の日付でなければなりません。
  • @FutureOrPresent : 日付が将来の日付か現在の日付かを判断します。
  • @Past : フィールドの日付が過去であることを確認します。
  • @PastOrPresent : 日付が過去の日付か現在の日付かを判断します。

2.6 その他のチェック

  • @Email : 注釈付き要素は電子メール アドレスである必要があります。
  • @Pattern(value) : 注釈付き要素は、指定された正規表現に準拠する必要があります。

2.7 Hibernate Validator の追加の制約アノテーション

org.hibernate.validator.constraintsパッケージでは、一連の制約 ( constraint ) アノテーションが定義されています。次のように:

  • @Range(min=, max=) : 注釈付き要素は適切な範囲内にある必要があります。
  • @Length(min=, max=) : 注釈付き文字列のサイズは、指定された範囲内でなければなりません。
  • @URL(protocol=,host=,port=,regexp=,flags=) : 注釈付き文字列は有効な URL でなければなりません。
  • @SafeHtml : 送信された HTML が安全かどうかを判断します。たとえば、javascript スクリプトなどを含めることはできません。

ここに画像の説明を挿入

2.8 @Valid 和 @Validated

@Valid アノテーションは Bean Validation によって定義され、通常のメソッド、コンストラクター、メソッド パラメーター、メソッドの戻り値、およびメンバー変数に追加して、制約をチェックする必要があることを示すことができます。

@Validated アノテーションは、クラス、メソッド パラメーター、および通常のメソッドに追加して、制約をチェックする必要があることを示すことができる Spring Validation ロック定義です。同時に、@Validated にはグループ検証をサポートする value 属性があります。プロパティは次のとおりです。

@Target({
    
    ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
    
    

	/**
	 * Specify one or more validation groups to apply to the validation step
	 * kicked off by this annotation.
	 * <p>JSR-303 defines validation groups as custom annotations which an application declares
	 * for the sole purpose of using them as type-safe group arguments, as implemented in
	 * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
	 * <p>Other {@link org.springframework.validation.SmartValidator} implementations may
	 * support class arguments in other ways as well.
	 */
	Class<?>[] value() default {
    
    };

}

初心者は、@Valid (javax.validation パッケージの下) と @Validated (org.springframework.validation.annotation パッケージの下) アノテーションを混同しがちです。この 2 つは一般的に次のような違いがあります。

名前 spring Validation は宣言型検証を実装しますか ネストされた検証をサポートするかどうか グループ検証をサポートするかどうか
@検証済み はい いいえ はい
@有効 いいえ はい いいえ

@Valid にはネストされたオブジェクトの検証機能があります. たとえば、 @Valid アノテーションが User.profile プロパティに追加されていない場合、 UserProfile.nickname プロパティが検証されません.

// User.java
public class User {
    
    
    
    private String id;

    @Valid
    private UserProfile profile;

}

// UserProfile.java
public class UserProfile {
    
    

    @NotBlank
    private String nickname;

}

一般に、ほとんどのシナリオで @Validated アノテーションを使用できます。ネストされた検証のシナリオでは、@Valid アノテーションを使用してメンバー プロパティに追加します。

3.クイックスタート

3.1 依存関係の紹介

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>com.ratel</groupId>
    <artifactId>java-validation</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>java-validation</name>
    <description>java validation action</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!--在一些高版本springboot中默认并不会引入这个依赖,需要手动引入-->
<!--        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <scope>compile</scope>
        </dependency>-->

        <!-- 保证 Spring AOP 相关的依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>


        <!--lombok相关 方便开发-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--knife4j接口文档 方便待会进行接口测试-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Boot システムでは、spring-boot-starter-validation 依存関係も提供されます。ここでは、紹介しませんでした。なぜ?spring-boot-starter-web は spring-boot-starter-validation を導入し、spring-boot-starter-validation は hibernate-validator の依存関係も導入したため、導入を繰り返す必要はありません。3 つの依存インポート関係は、下の図で確認できます。
ここに画像の説明を挿入

3.2 基本クラスの作成

UserAddDTO エンティティ クラス:

package com.ratel.validation.entity;

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
 * @Description
 * @Author ratelfu
 * @Date 2023/04/07
 * @Version 1.0
 */
@Data
public class UserAddDTO {
    
    

    /**
     * 账号
     */
    @NotEmpty(message = "登录账号不能为空")
    @Length(min = 5, max = 16, message = "账号长度为 5-16 位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
    private String username;
    /**
     * 密码
     */
    @NotEmpty(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
    private String password;
}

UserController is used to write interfaces. クラスに @Validated アノテーションを追加して、UserController のすべてのインターフェイスでパラメーターの検証が必要であることを示します。

package com.ratel.validation.cotroller;

import com.ratel.validation.entity.UserAddDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Min;

@RestController
@RequestMapping("/users")
@Validated
public class UserController {
    
    

    private Logger logger = LoggerFactory.getLogger(getClass());

    @GetMapping("/get")
    public UserAddDTO get(@RequestParam("id") @Min(value = 1L, message = "编号必须大于 0") Integer id) {
    
    
        logger.info("[get][id: {}]", id);
        UserAddDTO userAddDTO = new UserAddDTO();
        userAddDTO.setUsername("张三");
        userAddDTO.setPassword("123456");
        return userAddDTO;
    }

    @PostMapping("/add")
    public void add(@Valid @RequestBody UserAddDTO addDTO) {
    
    
        logger.info("[add][addDTO: {}]", addDTO);
    }

}

3.3 プログラムを起動してテストする

プログラムを開始すると、ブラウザーに次のように入力できます。swagger アクセス アドレス: http://localhost:8080/doc.html#/home テストする swagger ドキュメントを開きます。

ここに画像の説明を挿入
まず、http://localhost:8080/users/get?id=-1 にアクセスしてテストし、返された結果を確認して、ID を確認します。
ここに画像の説明を挿入
次に、http://localhost:8080/users/add にアクセスして、新しいユーザーを確認します。リクエストの本文を次のように記述します。

{
    
    
  "password": "233",
  "username": "33"
}

次に、次のように結果を返します。

{
    
    
  "timestamp": "2023-04-09T13:33:58.864+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
    
    
      "codes": [
        "Length.userAddDTO.password",
        "Length.password",
        "Length.java.lang.String",
        "Length"
      ],
      "arguments": [
        {
    
    
          "codes": [
            "userAddDTO.password",
            "password"
          ],
          "arguments": null,
          "defaultMessage": "password",
          "code": "password"
        },
        16,
        4
      ],
      "defaultMessage": "密码长度为 4-16 位",
      "objectName": "userAddDTO",
      "field": "password",
      "rejectedValue": "233",
      "bindingFailure": false,
      "code": "Length"
    },
    {
    
    
      "codes": [
        "Length.userAddDTO.username",
        "Length.username",
        "Length.java.lang.String",
        "Length"
      ],
      "arguments": [
        {
    
    
          "codes": [
            "userAddDTO.username",
            "username"
          ],
          "arguments": null,
          "defaultMessage": "username",
          "code": "username"
        },
        16,
        5
      ],
      "defaultMessage": "账号长度为 5-16 位",
      "objectName": "userAddDTO",
      "field": "username",
      "rejectedValue": "33",
      "bindingFailure": false,
      "code": "Length"
    }
  ],
  "message": "Validation failed for object='userAddDTO'. Error count: 2",
  "path": "/users/add"
}

結果の json 文字列のエラー フィールドと、パラメーター エラー詳細配列を返します。各配列要素は、パラメーター エラーの詳細に対応します。ここでは、ユーザー名がアカウントの長さ 5 ~ 16 文字に違反しています。password が 4 ~ 16 文字のパスワード長に違反しています。
返された結果の概略図:
ここに画像の説明を挿入

3.3 いくつかの質問

ここにいる慎重な友人は、いくつかの質問をするかもしれません:

3.3.1 質問 1

#get(id) メソッドでは、id に @Valid アノテーションを追加しませんでした。#add(addDTO) メソッドでは、@Valid アノテーションを addDTO に追加しました。この違いはなぜですか?

UserController は @Validated アノテーションを使用するため、Spring Validation はアスペクトとパラメーターの検証に AOP を使用します。このアスペクトのインターセプターはMethodValidationInterceptorを使用します。

  • #get(id) メソッドは、検証が必要なパラメーター id が分散しているため、 @Valid アノテーションを追加する必要はありません。
  • #add(addDTO) メソッドの場合、検証が必要なパラメーター addDTO は、実際にはネストされた検証と同等です. 検証されるパラメーターはすべて addDTO にあるため、 @Valid を追加する必要があります (実際には @Validated を追加することも今のところではなく、実際の測定に問題ありません. 理由がわかっている場合は、 @Valid を使用してより適切に区別してください) 注釈。

3.3.2 質問 2

#get(id) メソッドが返す結果は status = 500、#add(addDTO) メソッドが返す結果は status = 400 です。

  • #get(id) メソッドの場合、MethodValidationInterceptor インターセプターで、パラメーターが正しくないことが確認されると、ConstraintViolationException がスローされます。
  • #add(addDTO) メソッドについては、addDTO が POJO オブジェクトであるため、SpringMVCDataBinderメカニズム、DataBinder#validate(Object... validationHints)メソッドを呼び出して検証します。検証が失敗した場合にスローされますBindException

SpringMVC では、デフォルトで例外がDefaultHandlerExceptionResolver処理され。

  • BindException例外の場合は の400ステータスコードとして処理されます。
  • ConstraintViolationExceptionの例外については特別な扱いがないため、500のステータスコードとして処理されます。
    ここで質問を投げかけます. #add(addDTOメソッドの場合、パラメーターが正しい場合、DataBinderでのパラメーター検証を経てMethodValidationInterceptorインターセプターを通過するでしょうか? 100 ミリ秒程度で考えてください...

答えはイエスです。これは浪費につながります。したがって、Controller クラスに只有同様の嵌套校验、Controller クラスに @Validated アノテーションを追加する必要はありません。このように、パラメーターの検証には DataBinder のみが使用されます。

3.3.3 リターンプロンプトが非常に不親切で長すぎる

3 つ目は、#get(id) メソッドであろうと #add(addDTO) メソッドであろうと、リターン プロンプトが非常に不親切なので、どうすればよいでしょうか。これについては、第 4 章で处理校验异常扱います。

4. 検証の例外を処理する

4.1 検証に失敗する列挙型クラス

package com.ratel.validation.enums;

/**
 * 业务异常枚举
 */
public enum ServiceExceptionEnum {
    
    

    // ========== 系统级别 ==========
    SUCCESS(0, "成功"),
    SYS_ERROR(2001001000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(2001001001, "参数缺失"),
    INVALID_REQUEST_PARAM_ERROR(2001001002, "请求参数不合法"),

    // ========== 用户模块 ==========
    USER_NOT_FOUND(1001002000, "用户不存在"),

    // ========== 订单模块 ==========

    // ========== 商品模块 ==========
    ;

    /**
     * 错误码
     */
    private final int code;
    /**
     * 错误提示
     */
    private final String message;

    ServiceExceptionEnum(int code, String message) {
    
    
        this.code = code;
        this.message = message;
    }

    public int getCode() {
    
    
        return code;
    }

    public String getMessage() {
    
    
        return message;
    }

}

4.2 統合されたリターン結果エンティティ クラス

package com.ratel.validation.common;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.util.Assert;

import java.io.Serializable;

/**
 * 通用返回结果
 *
 * @param <T> 结果泛型
 */
public class CommonResult<T> implements Serializable {
    
    

    public static Integer CODE_SUCCESS = 0;

    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误提示
     */
    private String message;
    /**
     * 返回数据
     */
    private T data;

    /**
     * 将传入的 result 对象,转换成另外一个泛型结果的对象
     *
     * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
     *
     * @param result 传入的 result 对象
     * @param <T> 返回的泛型
     * @return 新的 CommonResult 对象
     */
    public static <T> CommonResult<T> error(CommonResult<?> result) {
    
    
        return error(result.getCode(), result.getMessage());
    }

    public static <T> CommonResult<T> error(Integer code, String message) {
    
    
        Assert.isTrue(!CODE_SUCCESS.equals(code), "code 必须是错误的!");
        CommonResult<T> result = new CommonResult<>();
        result.code = code;
        result.message = message;
        return result;
    }

    public static <T> CommonResult<T> success(T data) {
    
    
        CommonResult<T> result = new CommonResult<>();
        result.code = CODE_SUCCESS;
        result.data = data;
        result.message = "";
        return result;
    }

    public Integer getCode() {
    
    
        return code;
    }

    public void setCode(Integer code) {
    
    
        this.code = code;
    }

    public String getMessage() {
    
    
        return message;
    }

    public void setMessage(String message) {
    
    
        this.message = message;
    }

    public T getData() {
    
    
        return data;
    }

    public void setData(T data) {
    
    
        this.data = data;
    }

    @JsonIgnore
    public boolean isSuccess() {
    
    
        return CODE_SUCCESS.equals(code);
    }

    @JsonIgnore
    public boolean isError() {
    
    
        return !isSuccess();
    }

    @Override
    public String toString() {
    
    
        return "CommonResult{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }

}

4.3 グローバル例外処理クラス GlobalExceptionHandler を追加

package com.ratel.validation.exception;


import com.ratel.validation.common.CommonResult;
import com.ratel.validation.enums.ServiceExceptionEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

@ControllerAdvice(basePackages = "com.ratel.validation.cotroller")
public class GlobalExceptionHandler {
    
    

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 处理 MissingServletRequestParameterException 异常
     *
     * SpringMVC 参数不正确
     */
    @ResponseBody
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
    
    
        logger.error("[missingServletRequestParameterExceptionHandler]", ex);
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
    
    
        logger.error("[constraintViolationExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
    
    
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
    
    
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(constraintViolation.getMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public CommonResult bindExceptionHandler(HttpServletRequest req, BindException ex) {
    
    
        logger.info("========进入了 bindException======");
        logger.error("[bindExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getAllErrors()) {
    
    
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
    
    
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {
    
    
        logger.info("-----------------进入了 MethodArgumentNotValidException-----------------");
        logger.error("[MethodArgumentNotValidException]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {
    
    
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
    
    
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }


    /**
     * 处理其它 Exception 异常
     * @param req
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
    
    
        // 记录异常日志
        logger.error("[exceptionHandler]", e);
        // 返回 ERROR CommonResult
        return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
                ServiceExceptionEnum.SYS_ERROR.getMessage());
    }
}

4.4 テスト

アクセス: http://localhost:8080/users/add 異常な結果が文字列につなぎ合わされていることがわかります。これは、以前よりもはるかに新鮮で理解しやすくなっています。
ここに画像の説明を挿入
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weter_drop/article/details/130046637