Java Bean Validation 最佳實踐

參數(shù)校驗是我們程序開發(fā)中必不可少的過程魄揉。用戶在前端頁面上填寫表單時鞍盗,前端js程序會校驗參數(shù)的合法性妇押,當數(shù)據(jù)到了后端跷究,為了防止惡意操作,保持程序的健壯性敲霍,后端同樣需要對數(shù)據(jù)進行校驗俊马。后端參數(shù)校驗最簡單的做法是直接在業(yè)務(wù)方法里面進行判斷丁存,當判斷成功之后再繼續(xù)往下執(zhí)行。但這樣帶給我們的是代碼的耦合柴我,冗余解寝。當我們多個地方需要校驗時,我們就需要在每一個地方調(diào)用校驗程序,導致代碼很冗余艘儒,且不美觀聋伦。

那么如何優(yōu)雅的對參數(shù)進行校驗呢?JSR303就是為了解決這個問題出現(xiàn)的彤悔,本篇文章主要是介紹 JSR303嘉抓,Hibernate Validator 等校驗工具的使用,以及自定義校驗注解的使用晕窑。

校驗框架介紹

bean validation
官網(wǎng):http://beanvalidation.org
最新版本:http://beanvalidation.org/2.0/

Bean Validation 2.0 (JSR 380) 是java EE 8的一部分抑片,但也可以用于以前的版本。

hibernate validation
官網(wǎng):http://hibernate.org/validator/
官方文檔:http://hibernate.org/validator/documentation/
jboss文檔倉庫:https://docs.jboss.org/hibernate/validator/
最新中文版:https://docs.jboss.org/hibernate/validator/4.3/reference/zh-CN/html_single

hibernate validation是bean validation目前最好的實現(xiàn)杨赤。
·強烈建議看官方文檔敞斋,還是很容易理解的,看完中文再看英文疾牲。

兼容性

bean validation hibernate validation java spring boot
1.1 5.4 series 6+ 1.5.*.RELEASE
2.0 6.0 series 8+ 2.0.*.RELEASE

Spring Boot支持
引入hibernate validation依賴植捎, Spring Boot會自動配置Validator。
在SpringMVC的Controller中加校驗約束立即生效阳柔;在jsonrpc接口上需在類上加@Validated注解焰枢,校驗約束才會生效。
Spring Boot 1.5.*.RELEASE默認支持Bean Validation1.1舌剂,可強制使用Bean Validation2.0济锄。

常用約束注解
看java api文檔,都可以查看他們支持的數(shù)據(jù)類型霍转,包路徑j(luò)avax.validation.constraints荐绝,共22個。

constraint description
@Null 必須為null
@NotNull 必須不為 null
@AssertTrue 必須為 true 避消,支持boolean低滩、Boolean
@AssertFalse 必須為 false ,支持boolean岩喷、Boolean
@Min(value) 值必須小于value恕沫,支持BigDecimal、BigInteger纱意,byte婶溯、shot、int、long及其包裝類
@Max(value) 值必須大于value爬虱,支持BigDecimal、BigInteger腾它,byte跑筝、shot、int瞒滴、long及其包裝類
@DecimalMin(value) 值必須小于value曲梗,支持BigDecimal、BigInteger妓忍、CharSequence虏两,byte、shot世剖、int定罢、long及其包裝類
@DecimalMax(value) 值必須大于value,支持BigDecimal旁瘫、BigInteger祖凫、CharSequence,byte酬凳、shot惠况、int、long及其包裝類
@Size(max=, min=) 支持CharSequence宁仔、Collection稠屠、Map、Array
@Digits (integer, fraction) 必須是一個數(shù)字
@Negative 必須是一個負數(shù)
@NegativeOrZero 必須是一個負數(shù)或0
@Positive 必須是一個正數(shù)
@PositiveOrZero 必須是個正數(shù)或0
@Past 必須是一個過去的日期
@PastOrPresent 必須是一個過去的或當前的日期
@Future 必須是一個將來的日期
@FutureOrPresent 必須是一個未來的或當前的日期
@Pattern(regex=,flag=) 必須符合指定的正則表達式
@NotBlank(message =) 必須是一個非空字符串
@Email 必須是電子郵箱地址
@NotEmpty 被注釋的字符串的必須非空

JSR303 是一套JavaBean參數(shù)校驗的標準翎苫,它定義了很多常用的校驗注解权埠,我們可以直接將這些注解加在我們JavaBean的屬性上面,就可以在需要校驗的時候進行校驗了拉队。注解如下:

image

Hibernate validator 在JSR303的基礎(chǔ)上對校驗注解進行了擴展弊知,擴展注解如下:

image

Spring validtor 同樣擴展了jsr303,并實現(xiàn)了方法參數(shù)和返回值的校驗

Spring 提供了MethodValidationPostProcessor類,用于對方法的校驗

代碼實現(xiàn)

添加JAR包依賴

在pom.xml中添加如下依賴:

<!--jsr 303-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<!-- hibernate validator-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.0.Final</version>
</dependency>

最簡單的參數(shù)校驗

1粱快、Model 中添加校驗注解

public class Book {
    private long id;

    /**
     * 書名
     */
    @NotEmpty(message = "書名不能為空")
    private String bookName;
    /**
     * ISBN號
     */
    @NotNull(message = "ISBN號不能為空")
    private String bookIsbn;
    /**
     * 單價
     */
    @DecimalMin(value = "0.1",message = "單價最低為0.1")
    private double price; // getter setter .......  }

2秩彤、在controller中使用此校驗

/**
     * 添加Book對象
     * @param book
     */
    @RequestMapping(value = "/book", method = RequestMethod.POST)
    public void addBook(@RequestBody @Valid Book book) {
        System.out.println(book.toString());
    }

當訪問這個post接口時,如果參數(shù)不符合Model中定義的話事哭,程序中就回拋出400異常漫雷,并提示錯誤信息。

自定義校驗注解

雖然jSR303和Hibernate Validtor 已經(jīng)提供了很多校驗注解鳍咱,但是當面對復雜參數(shù)校驗時降盹,還是不能滿足我們的要求,這時候我們就需要 自定義校驗注解谤辜。

下面以“List數(shù)組中不能含有null元素”為實例自定義校驗注解

1蓄坏、注解定義如下:

package com.beiyan.validate.annotation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 自定義參數(shù)校驗注解
 * 校驗 List 集合中是否有null 元素
 */

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)////此處指定了注解的實現(xiàn)類為ListNotHasNullValidatorImpl

public @interface ListNotHasNull {

    /**
     * 添加value屬性价捧,可以作為校驗時的條件,若不需要,可去掉此處定義
     */
    int value() default 0;

    String message() default "List集合中不能含有null元素";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    /**
     * 定義List涡戳,為了讓Bean的一個屬性上可以添加多套規(guī)則
     */
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        ListNotHasNull[] value();
    }
}

2结蟋、注解實現(xiàn)類:

package com.beiyan.validate.annotation;

import org.springframework.stereotype.Service;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;

/**
 * 自定義注解ListNotHasNull 的實現(xiàn)類
 * 用于判斷List集合中是否含有null元素
 */

@Service
public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNull, List> {

    private int value;

    @Override
    public void initialize(ListNotHasNull constraintAnnotation) {
        //傳入value 值,可以在校驗中使用
        this.value = constraintAnnotation.value();
    }

    public boolean isValid(List list, ConstraintValidatorContext constraintValidatorContext) {
        for (Object object : list) {
            if (object == null) {
                //如果List集合中含有Null元素渔彰,校驗失敗
                return false;
            }
        }
        return true;
    }

}

3嵌屎、model添加注解:

public class User {

    //其他參數(shù) .......

/**
     * 所擁有的書籍列表
     */
    @NotEmpty(message = "所擁有書籍不能為空")
    @ListNotHasNull(message = "List 中不能含有null元素")
    @Valid
    private List<Book> books;
    //getter setter 方法.......
}

使用方法同上,在在需要校驗的Model上面加上@Valid 即可

分組驗證

對同一個Model恍涂,我們在增加和修改時對參數(shù)的校驗也是不一樣的宝惰,這個時候我們就需要定義分組驗證,步驟如下

1再沧、定義兩個空接口尼夺,分別代表Person對象的增加校驗規(guī)則和修改校驗規(guī)則

/**
 * 可以在一個Model上面添加多套參數(shù)驗證規(guī)則,此接口定義添加Person模型新增時的參數(shù)校驗規(guī)則
 */
public interface PersonAddView {
}

/**
 * 可以在一個Model上面添加多套參數(shù)驗證規(guī)則炒瘸,此接口定義添加Person模型修改時的參數(shù)校驗規(guī)則
 */
public interface PersonModifyView {
}

2汞斧、Model上添加注解時使用指明所述的分組

public class Person {
    private long id;
    /**
     * 添加groups 屬性,說明只在特定的驗證規(guī)則里面起作用什燕,不加則表示在使用Deafault規(guī)則時起作用
     */
    @NotNull(groups = {PersonAddView.class, PersonModifyView.class}, message = "添加粘勒、修改用戶時名字不能為空", payload = ValidateErrorLevel.Info.class)
    @ListNotHasNull.List({
            @ListNotHasNull(groups = {PersonAddView.class}, message = "添加上Name不能為空"),
            @ListNotHasNull(groups = {PersonModifyView.class}, message = "修改時Name不能為空")})
    private String name;

    @NotNull(groups = {PersonAddView.class}, message = "添加用戶時地址不能為空")
    private String address;

    @Min(value = 18, groups = {PersonAddView.class}, message = "姓名不能低于18歲")
    @Max(value = 30, groups = {PersonModifyView.class}, message = "姓名不能超過30歲")
    private int age;
  //getter setter 方法......
}

3、啟用校驗

此時啟用校驗和之前的不同,需要指明啟用哪一組規(guī)則

/**
     * 添加一個Person對象
     * 此處啟用PersonAddView 這個驗證規(guī)則
     * 備注:此處@Validated(PersonAddView.class) 表示使用PersonAndView這套校驗規(guī)則屎即,若使用@Valid 則表示使用默認校驗規(guī)則庙睡,
     * 若兩個規(guī)則同時加上去,則只有第一套起作用
     */
    @RequestMapping(value = "/person", method = RequestMethod.POST)
    public void addPerson(@RequestBody @Validated({PersonAddView.class, Default.class}) Person person) {
        System.out.println(person.toString());
    }

    /**
     * 修改Person對象
     * 此處啟用PersonModifyView 這個驗證規(guī)則
     */
    @RequestMapping(value = "/person", method = RequestMethod.PUT)
    public void modifyPerson(@RequestBody @Validated(value = {PersonModifyView.class}) Person person) {
        System.out.println(person.toString());
    }

Spring validator 方法級別的校驗

JSR和Hibernate validator的校驗只能對Object的屬性進行校驗技俐,不能對單個的參數(shù)進行校驗乘陪,spring 在此基礎(chǔ)上進行了擴展,添加了MethodValidationPostProcessor攔截器雕擂,可以實現(xiàn)對方法參數(shù)的校驗啡邑,實現(xiàn)如下:

1、實例化MethodValidationPostProcessor

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
}

2井赌、在所要實現(xiàn)方法參數(shù)校驗的類上面添加@Validated谤逼,如下

@RestController
@Validated
public class ValidateController {
}

3、在方法上面添加校驗規(guī)則:

@RequestMapping(value = "/test", method = RequestMethod.GET)
public String paramCheck(@Length(min = 10) @RequestParam String name) {
    System.out.println(name);
    return null;
}

當方法上面的參數(shù)校驗失敗,spring 框架就回拋出異常

{
  "timestamp": 1476108200558,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "javax.validation.ConstraintViolationException",
  "message": "No message available",
  "path": "/test"
}

從此可以優(yōu)雅的對參數(shù)進行校驗了

寫在后面的話:

本篇文章只列舉了常用的幾種校驗方法仇穗,其實關(guān)于校驗的內(nèi)容還有很多:

校驗信息的國際化顯示流部,

組合參數(shù)校驗,

message中使用EL表達式纹坐,

將校驗信息綁定到ModelAndView等枝冀,這里就不一一列出了,下面這幾篇文章寫的也不錯,讀者可以參考:

將校驗信息綁定到ModelAndView http://www.voidcn.com/blog/983836259/article/p-5794496.html

集成Bean Validation 1.1(JSR-349)到SpringMVC https://my.oschina.net/qjx1208/blog/200946

Spring Boot參數(shù)校驗:https://www.cnblogs.com/cjsblog/p/8946768.html

SpringBoot全局異常捕獲統(tǒng)一處理及整合Validation

全局異常捕獲處理及參數(shù)校驗

一果漾、為什么要用全局異常處理球切?

在日常開發(fā)中,為了不拋出異常堆棧信息給前端頁面,每次編寫Controller層代碼都要盡可能的catch住所有service層倒得、dao層等異常,代碼耦合性較高,且不美觀畜普,不利于后期維護。為解決該問題涡匀,計劃將Controller層異常信息統(tǒng)一封裝處理拴事,且能區(qū)分對待Controller層方法返回給前端的String、Map弓摘、JSONObject焚鹊、ModelAndView等結(jié)果類型。

二韧献、應用場景是什么末患?

  • 非常方便的去掉了try catch這類冗雜難看的代碼,有利于代碼的整潔和優(yōu)雅
  • 自定義參數(shù)校驗時候全局異常處理會捕獲異常锤窑,將該異常統(tǒng)一返回給前端璧针,省略很多if else代碼
  • 當后端出現(xiàn)異常時,需要返回給前端一個友好的界面的時候就需要全局異常處理
  • 因為異常時層層向上拋出的渊啰,為了避免控制臺打印一長串異常信息

三探橱、如何進行全局異常捕獲和處理?

一共有兩種方法:
  • Spring的AOP(較復雜)
  • @ControllerAdvice結(jié)合@ExceptionHandler(簡單)

四绘证、@ControllerAdvice和@ExceptionHandler怎么用隧膏?

  • 1、Controller Advice字面上意思是“控制器通知”嚷那,Advice除了“勸告”胞枕、“意見”之外,還有“通知”的意思魏宽「海可以將@ExceptionHandler(標識異常類型對應的處理方法)標記的方法提取出來,放到一個類里队询,并將加上@ControllerAdvice贫悄,這樣,所有的控制器都可以用了
@ControllerAdvice
public class ControllerHandlers(){
    @ExceptionHandler
    public String errorHandler(Exception e){
        return "error";
    }
}
  • 2娘摔、 因為@ControllerAdvice被@Componen標記窄坦,所以他可以被組件掃描到并放入Spring容器
  • 3、 如果只想對一部分控制器通知,比如某個包下邊的控制器鸭津,就可以這樣寫:
@ControllerAdvice("com.labor")
public class ControllerHandlers(){}

也可以直接寫類名

@ControllerAdvice(basePackageClasses = ***.class)
public class ControllerHandlers(){}

也可以傳多個類

@ControllerAdvice(assignableTypes = {***.class,***.class})
public class ControllerHandlers(){}
  • 4彤侍、 控制器通知還有一個兄弟,@RestControllerAdvice逆趋,如果用了它盏阶,錯誤處理方法的返回值不會表示用的哪個視圖,而是會作為HTTP body處理闻书,即相當于錯誤處理方法加了@ResponseBody注解名斟。
@RestControllerAdvice
public class ControllerHandlers(){
    @ExceptionHandler
    public String errorHandler(Exception e){
        return "error";
    }
}
  • 5、 @ExceptionHandler注解的方法只能返回一種類型,在前后端分離開發(fā)中我們通常返回魄眉,統(tǒng)一返回類型和優(yōu)化錯誤的提示砰盐,我們可以封裝我們自己的返回Result

自定義基礎(chǔ)接口類
首先定義一個基礎(chǔ)的接口類,自定義的錯誤描述枚舉類需實現(xiàn)該接口坑律。
代碼如下:

public interface BaseErrorInfoInterface {

    /**
     * 錯誤碼
     */
    Integer getCode();

    /**
     * 錯誤描述
     */
    String getMessage();
}

自定義枚舉類
然后我們這里在自定義一個枚舉類岩梳,并實現(xiàn)該接口。
代碼如下:

public enum CommonEnum implements BaseErrorInfoInterface {

    // 數(shù)據(jù)操作錯誤定義
    SUCCESS(200, "成功!"),
    BODY_NOT_MATCH(400,"請求的數(shù)據(jù)格式不符!"),
    SIGNATURE_NOT_MATCH(401,"請求的數(shù)字簽名不匹配!"),
    NOT_FOUND(404, "未找到該資源!"),
    INTERNAL_SERVER_ERROR(500, "服務(wù)器內(nèi)部錯誤!"),
    SERVER_BUSY(503,"服務(wù)器正忙晃择,請稍后再試!"),
    ;

    /**
     * 錯誤碼
     */
    private Integer code;

    /**
     * 錯誤描述
     */
    private String message;

    CommonEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    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;
    }
}

自定義異常類
然后我們在來自定義一個異常類冀值,用于處理我們發(fā)生的業(yè)務(wù)異常。
代碼如下:

public class BizException extends RuntimeException {

    /**
     * 錯誤碼
     */
    protected Integer errorCode;
    /**
     * 錯誤信息
     */
    protected String errorMsg;

    public BizException() {
        super();
    }

    public BizException(BaseErrorInfoInterface errorInfoInterface) {
        super(errorInfoInterface.getMessage());
        this.errorCode = errorInfoInterface.getCode();
        this.errorMsg = errorInfoInterface.getMessage();
    }

    public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
        super(errorInfoInterface.getMessage(), cause);
        this.errorCode = errorInfoInterface.getCode();
        this.errorMsg = errorInfoInterface.getMessage();
    }

    public BizException(String errorMsg) {
        super(errorMsg);
        this.errorMsg = errorMsg;
    }

    public BizException(Integer errorCode, String errorMsg) {
        super(errorMsg);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public BizException(Integer errorCode, String errorMsg, Throwable cause) {
        super(errorMsg, cause);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }


    public Integer getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(Integer errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getMessage() {
        return errorMsg;
    }

    @Override
    public Throwable fillInStackTrace() {
        return this;
    }
}

自定義數(shù)據(jù)格式
順便這里我們定義一下數(shù)據(jù)的傳輸格式宫屠。
代碼如下:

public class Result<T> implements BaseErrorInfoInterface {
    //返回碼
    private Integer code;

    //提示信息
    private String message;

    //返回具體內(nèi)容
    private T data;

    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;
    }
}

分頁返回數(shù)據(jù)格式

public class Page<T> implements Serializable {

    /**
     * 每頁最大數(shù)據(jù)條數(shù)
     */
    private static final int MAX_PAGE_SIZE = 200;

    /**
     * 當前頁
     */
    private Integer pageNum;

    /**
     * 每頁數(shù)量
     */
    private Integer pageSize;

    /**
     * 總數(shù)據(jù)條數(shù)
     */
    private Integer totalCount;

    /**
     * 頁碼總數(shù)
     */
    private Integer totalPages;

    /**
     * 數(shù)據(jù)集合
     */
    private Collection<T> items;

    public static int getMaxPageSize() {
        return MAX_PAGE_SIZE;
    }

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public Integer getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(Integer totalCount) {
        this.totalCount = totalCount;
    }

    public Integer getTotalPages() {
        return totalPages;
    }

    public void setTotalPages(Integer totalPages) {
        this.totalPages = totalPages;
    }

    public Collection<T> getItems() {
        return items;
    }

    public void setItems(Collection<T> items) {
        this.items = items;
    }
}

定義分頁返回實體

public class PageResult<T> implements BaseErrorInfoInterface {

    //返回碼
    private Integer code;

    //提示信息
    private String message;

    //返回具體內(nèi)容
    private Page<T> content;


    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 Page<T> getContent() {
        return content;
    }

    public void setContent(Page<T> content) {
        this.content = content;
    }
}

定義結(jié)果處理Util
為了防止多次出現(xiàn)new Result()的代碼造成冗余列疗,增加一個封裝統(tǒng)一返回格式工具類ResultUtil

public class ResultUtil {

    /**
     * 成功
     *
     * @return
     */
    public static Result success() {
        return success(null);
    }

    /**
     * 成功
     * @param data
     * @return
     */
    public static Result success(Object data) {
        Result result = new Result();
        result.setCode(CommonEnum.SUCCESS.getCode());
        result.setMessage(CommonEnum.SUCCESS.getMessage());
        result.setData(data);
        return result;
    }

    /**
     * 失敗
     */
    public static Result fail(BaseErrorInfoInterface baseErrorInfoInterface) {
        Result result = new Result();
        result.setCode(baseErrorInfoInterface.getCode());
        result.setMessage(baseErrorInfoInterface.getMessage());
        result.setData(null);
        return result;
    }

    /**
     * 失敗
     */
    public static Result fail(Integer code, String message) {
        Result result = new Result();
        result.setCode(code);
        result.setMessage(message);
        result.setData(null);
        return result;
    }

    /**
     * 失敗
     */
    public static Result fail( String message) {
        Result result = new Result();
        result.setCode(-1);
        result.setMessage(message);
        result.setData(null);
        return result;
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}
public class PageResultUtil {


    /**
     * 成功
     *
     * @return
     */
    public static PageResult success() {
        return success(null);
    }

    /**
     * 成功
     * @param content
     * @return
     */
    public static PageResult success(Page content) {
        PageResult pageResult = new PageResult();
        pageResult.setCode(CommonEnum.SUCCESS.getCode());
        pageResult.setMessage(CommonEnum.SUCCESS.getMessage());
        pageResult.setContent(content);
        return pageResult;
    }

    /**
     * 失敗
     */
    public static PageResult fail(BaseErrorInfoInterface baseErrorInfoInterface) {
        PageResult pageResult = new PageResult();
        pageResult.setCode(baseErrorInfoInterface.getCode());
        pageResult.setMessage(baseErrorInfoInterface.getMessage());
        pageResult.setContent(null);
        return pageResult;
    }

    /**
     * 失敗
     */
    public static PageResult fail(Integer code, String message) {
        PageResult pageResult = new PageResult();
        pageResult.setCode(code);
        pageResult.setMessage(message);
        pageResult.setContent(null);
        return pageResult;
    }

    /**
     * 失敗
     */
    public static PageResult fail( String message) {
        PageResult pageResult = new PageResult();
        pageResult.setCode(-1);
        pageResult.setMessage(message);
        pageResult.setContent(null);
        return pageResult;
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}
  • 6、 完善全局異常處理器
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 請求方式不支持
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public Result handleException(HttpRequestMethodNotSupportedException e) {
        log.error(e.getMessage(), e);
        return ResultUtil.fail("不支持' " + e.getMethod() + "'請求");
    }

    /**
     * 攔截未知的運行時異常
     */
    @ExceptionHandler(RuntimeException.class)
    public Result notFount(RuntimeException e) {
        log.error("運行時異常:", e);
        return ResultUtil.fail("運行時異常:" + e.getMessage());
    }


    /**
     * 校驗方法參數(shù)異常處理
     * 捕獲 MethodArgumentNotValidException 異常
     * @param exception
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Result MethodArgumentNotValidExceptionHandle(MethodArgumentNotValidException exception) {

        List<FieldError> errors = exception.getBindingResult().getFieldErrors();

        String errorMsg = errors.stream().map(e -> e.getField() + ":" + e.getDefaultMessage())
                .reduce("", (s1, s2) -> s1  + s2);
        return ResultUtil.fail(errorMsg);
    }

    /**
     * 校驗異常
     */
    @ExceptionHandler(value = BindException.class)
    public Result validationExceptionHandler(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        String errorMesssage = "";
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            errorMesssage += fieldError.getDefaultMessage() + "!";
        }
        return ResultUtil.fail(errorMesssage);
    }

    /**
     * 校驗異常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Result ConstraintViolationExceptionHandler(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
        List<String> msgList = new ArrayList<>();
        while (iterator.hasNext()) {
            ConstraintViolation<?> cvl = iterator.next();
            msgList.add(cvl.getMessageTemplate());
        }
        return ResultUtil.fail(String.join(",",msgList));
    }

    /**
     * 業(yè)務(wù)異常
     */
    @ExceptionHandler(BusinessException.class)
    public Result businessException(BusinessException e) {
        log.error(e.getErrorMsg(), e);
        return ResultUtil.fail(e.getErrorMsg());
    }

    /**
     * 系統(tǒng)異常
     */
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        log.error(e.getMessage(), e);
        return ResultUtil.fail("服務(wù)器錯誤浪蹂,請聯(lián)系管理員");
    }
}

六作彤、@Validated 校驗器注解的異常,也可以一起處理乌逐,無需手動判斷綁定校驗結(jié)果 BindingResult/Errors 了

  • pom文件引入validation的jar包
<!-- 校驗-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  • 等待校驗的object
public class Person {
    /**
     * @PersonName(prefix = "song"):自定義注解
     */
    @NotNull
    @PersonName(prefix = "song")
    private String name;
    @Min(value = 18)
    @Max(value = 30, message = "超過30歲的不要竭讳!")
    private Integer age;
}
/**
 * 開啟校驗注解:@Valid
 */
@RestController
public class PersonController {
    @PostMapping("/person")
    public Result<Person> savePerson(@Valid @RequestBody Person person){
        return ResultUtil.success(person);
    }
}
  • 全局異常處理里有相應的處理方法
/**
 * 校驗異常
 */
@ExceptionHandler(value = BindException.class)
public Result<String> validationExceptionHandler(BindException e) {
    BindingResult bindingResult = e.getBindingResult();
    String errorMesssage = "";
    for (FieldError fieldError : bindingResult.getFieldErrors()) {
        errorMesssage += fieldError.getDefaultMessage() + "!";
    }
    return ResultUtil.fail(errorMesssage);
}

被@RequestBody和@RequestParam注解的請求實體,校驗異常類是不同的

七浙踢、自定義異常以及事務(wù)回滾

public class MyException extends RuntimeException {
    //這個地方不要寫exception绢慢,因為Spring是只對運行時異常進行事務(wù)回滾,
    //如果拋出的是exception是不會進行事務(wù)回滾的洛波。
   
}

如果是在service層里捕獲異常統(tǒng)一去處理胰舆,那為了保證事務(wù)的回滾,需要拋出RuntimeException

try {
    
} catch (Exception e) {
    e.printStackTrace();
    logger.error("發(fā)生異常");
    throw new RuntimeException();
}

參考:
https://www.cnblogs.com/beiyan/p/5946345.html

https://blog.csdn.net/songguopeng/article/details/98961787

https://www.cnblogs.com/xuwujing/p/10933082.html

https://www.cnblogs.com/yloved/p/11553430.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蹬挤,一起剝皮案震驚了整個濱河市缚窿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌焰扳,老刑警劉巖倦零,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件误续,死亡現(xiàn)場離奇詭異,居然都是意外死亡扫茅,警方通過查閱死者的電腦和手機蹋嵌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葫隙,“玉大人栽烂,你說我怎么就攤上這事×到牛” “怎么了腺办?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糟描。 經(jīng)常有香客問我怀喉,道長,這世上最難降的妖魔是什么蚓挤? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮驻子,結(jié)果婚禮上灿意,老公的妹妹穿的比我還像新娘。我一直安慰自己崇呵,他們只是感情好缤剧,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著域慷,像睡著了一般荒辕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上犹褒,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天抵窒,我揣著相機與錄音,去河邊找鬼叠骑。 笑死李皇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的宙枷。 我是一名探鬼主播掉房,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慰丛!你這毒婦竟也來了卓囚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诅病,失蹤者是張志新(化名)和其女友劉穎哪亿,沒想到半個月后粥烁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡锣夹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年页徐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片银萍。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡变勇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贴唇,到底是詐尸還是另有隱情搀绣,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布戳气,位于F島的核電站链患,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瓶您。R本人自食惡果不足惜麻捻,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呀袱。 院中可真熱鬧贸毕,春花似錦、人聲如沸夜赵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寇僧。三九已至摊腋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘁傀,已是汗流浹背兴蒸。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留细办,地道東北人类咧。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像蟹腾,于是被迫代替她去往敵國和親痕惋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容