Java Bean Validation 最佳实践

参数校验是我们程序开发中必不可少的过程。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。

那么如何优雅的对参数进行校验呢?JSR303就是为了解决这个问题出现的,本篇文章主要是介绍 JSR303,Hibernate Validator 等校验工具的使用,以及自定义校验注解的使用。

校验框架介绍

bean validation
官网:http://beanvalidation.org
最新版本:http://beanvalidation.org/2.0/

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

hibernate validation
官网: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目前最好的实现。
·强烈建议看官方文档,还是很容易理解的,看完中文再看英文。

兼容性

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文档,都可以查看他们支持的数据类型,包路径javax.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) 必须是一个数字
@Negative 必须是一个负数
@NegativeOrZero 必须是一个负数或0
@Positive 必须是一个正数
@PositiveOrZero 必须是个正数或0
@Past 必须是一个过去的日期
@PastOrPresent 必须是一个过去的或当前的日期
@Future 必须是一个将来的日期
@FutureOrPresent 必须是一个未来的或当前的日期
@Pattern(regex=,flag=) 必须符合指定的正则表达式
@NotBlank(message =) 必须是一个非空字符串
@Email 必须是电子邮箱地址
@NotEmpty 被注释的字符串的必须非空

JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。注解如下:

image

Hibernate validator 在JSR303的基础上对校验注解进行了扩展,扩展注解如下:

image

Spring validtor 同样扩展了jsr303,并实现了方法参数和返回值的校验

Spring 提供了MethodValidationPostProcessor类,用于对方法的校验

代码实现

添加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>

最简单的参数校验

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接口时,如果参数不符合Model中定义的话,程序中就回抛出400异常,并提示错误信息。

自定义校验注解

虽然jSR303和Hibernate Validtor 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。

下面以“List数组中不能含有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;

/**
 * 自定义参数校验注解
 * 校验 List 集合中是否有null 元素
 */

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)////此处指定了注解的实现类为ListNotHasNullValidatorImpl

public @interface ListNotHasNull {

    /**
     * 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义
     */
    int value() default 0;

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

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

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

    /**
     * 定义List,为了让Bean的一个属性上可以添加多套规则
     */
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        ListNotHasNull[] value();
    }
}

2、注解实现类:

package com.beiyan.validate.annotation;

import org.springframework.stereotype.Service;

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

/**
 * 自定义注解ListNotHasNull 的实现类
 * 用于判断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 {

    //其他参数 .......

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

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

分组验证

对同一个Model,我们在增加和修改时对参数的校验也是不一样的,这个时候我们就需要定义分组验证,步骤如下

1、定义两个空接口,分别代表Person对象的增加校验规则和修改校验规则

/**
 * 可以在一个Model上面添加多套参数验证规则,此接口定义添加Person模型新增时的参数校验规则
 */
public interface PersonAddView {
}

/**
 * 可以在一个Model上面添加多套参数验证规则,此接口定义添加Person模型修改时的参数校验规则
 */
public interface PersonModifyView {
}

2、Model上添加注解时使用指明所述的分组

public class Person {
    private long id;
    /**
     * 添加groups 属性,说明只在特定的验证规则里面起作用,不加则表示在使用Deafault规则时起作用
     */
    @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、启用校验

此时启用校验和之前的不同,需要指明启用哪一组规则

/**
     * 添加一个Person对象
     * 此处启用PersonAddView 这个验证规则
     * 备注:此处@Validated(PersonAddView.class) 表示使用PersonAndView这套校验规则,若使用@Valid 则表示使用默认校验规则,
     * 若两个规则同时加上去,则只有第一套起作用
     */
    @RequestMapping(value = "/person", method = RequestMethod.POST)
    public void addPerson(@RequestBody @Validated({PersonAddView.class, Default.class}) Person person) {
        System.out.println(person.toString());
    }

    /**
     * 修改Person对象
     * 此处启用PersonModifyView 这个验证规则
     */
    @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的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数的校验,实现如下:

1、实例化MethodValidationPostProcessor

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

2、在所要实现方法参数校验的类上面添加@Validated,如下

@RestController
@Validated
public class ValidateController {
}

3、在方法上面添加校验规则:

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

当方法上面的参数校验失败,spring 框架就回抛出异常

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

从此可以优雅的对参数进行校验了

写在后面的话:

本篇文章只列举了常用的几种校验方法,其实关于校验的内容还有很多:

校验信息的国际化显示,

组合参数校验,

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参数校验:https://www.cnblogs.com/cjsblog/p/8946768.html

SpringBoot全局异常捕获统一处理及整合Validation

全局异常捕获处理及参数校验

一、为什么要用全局异常处理?

在日常开发中,为了不抛出异常堆栈信息给前端页面,每次编写Controller层代码都要尽可能的catch住所有service层、dao层等异常,代码耦合性较高,且不美观,不利于后期维护。为解决该问题,计划将Controller层异常信息统一封装处理,且能区分对待Controller层方法返回给前端的String、Map、JSONObject、ModelAndView等结果类型。

二、应用场景是什么?

  • 非常方便的去掉了try catch这类冗杂难看的代码,有利于代码的整洁和优雅
  • 自定义参数校验时候全局异常处理会捕获异常,将该异常统一返回给前端,省略很多if else代码
  • 当后端出现异常时,需要返回给前端一个友好的界面的时候就需要全局异常处理
  • 因为异常时层层向上抛出的,为了避免控制台打印一长串异常信息

三、如何进行全局异常捕获和处理?

一共有两种方法:
  • Spring的AOP(较复杂)
  • @ControllerAdvice结合@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注解的方法只能返回一种类型,在前后端分离开发中我们通常返回,统一返回类型和优化错误的提示,我们可以封装我们自己的返回Result

自定义基础接口类
首先定义一个基础的接口类,自定义的错误描述枚举类需实现该接口。
代码如下:

public interface BaseErrorInfoInterface {

    /**
     * 错误码
     */
    Integer getCode();

    /**
     * 错误描述
     */
    String getMessage();
}

自定义枚举类
然后我们这里在自定义一个枚举类,并实现该接口。
代码如下:

public enum CommonEnum implements BaseErrorInfoInterface {

    // 数据操作错误定义
    SUCCESS(200, "成功!"),
    BODY_NOT_MATCH(400,"请求的数据格式不符!"),
    SIGNATURE_NOT_MATCH(401,"请求的数字签名不匹配!"),
    NOT_FOUND(404, "未找到该资源!"),
    INTERNAL_SERVER_ERROR(500, "服务器内部错误!"),
    SERVER_BUSY(503,"服务器正忙,请稍后再试!"),
    ;

    /**
     * 错误码
     */
    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;
    }
}

自定义异常类
然后我们在来自定义一个异常类,用于处理我们发生的业务异常。
代码如下:

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

自定义数据格式
顺便这里我们定义一下数据的传输格式。
代码如下:

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

    //提示信息
    private String message;

    //返回具体内容
    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;
    }
}

分页返回数据格式

public class Page<T> implements Serializable {

    /**
     * 每页最大数据条数
     */
    private static final int MAX_PAGE_SIZE = 200;

    /**
     * 当前页
     */
    private Integer pageNum;

    /**
     * 每页数量
     */
    private Integer pageSize;

    /**
     * 总数据条数
     */
    private Integer totalCount;

    /**
     * 页码总数
     */
    private Integer totalPages;

    /**
     * 数据集合
     */
    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;

    //返回具体内容
    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;
    }
}

定义结果处理Util
为了防止多次出现new Result()的代码造成冗余,增加一个封装统一返回格式工具类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());
    }


    /**
     * 校验方法参数异常处理
     * 捕获 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));
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result businessException(BusinessException e) {
        log.error(e.getErrorMsg(), e);
        return ResultUtil.fail(e.getErrorMsg());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        log.error(e.getMessage(), e);
        return ResultUtil.fail("服务器错误,请联系管理员");
    }
}

六、@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 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注解的请求实体,校验异常类是不同的

七、自定义异常以及事务回滚

public class MyException extends RuntimeException {
    //这个地方不要写exception,因为Spring是只对运行时异常进行事务回滚,
    //如果抛出的是exception是不会进行事务回滚的。
   
}

如果是在service层里捕获异常统一去处理,那为了保证事务的回滚,需要抛出RuntimeException

try {
    
} catch (Exception e) {
    e.printStackTrace();
    logger.error("发生异常");
    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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容