springboot2.x 全局异常处理的正确方式

前言

在web项目中,异常堆栈信息是非常敏感的。因此,需要一个全局的异常处理,捕获异常,给客户端以友好的错误信息提示。基于 Spring boot 很容易实现全局异常处理。

相关jar依赖引入



    org.springframework.boot
    spring-boot-starter-parent
    2.1.6.RELEASE
     



    UTF-8
    UTF-8
    1.8



    
    
        org.springframework.boot
        spring-boot-starter-web
    

全局异常控制器

package com.yb.demo.common.handler;

import com.yb.demo.common.enums.CodeEnum;
import com.yb.demo.common.exception.BizException;
import com.yb.demo.pojo.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ValidationException;
import java.util.StringJoiner;

/**
 * 全局异常处理
 * 

* 规范:流程跳转尽量避免使用抛 BizException 来做控制。 * * @author [email protected] * @date 2019-09-06 18:02 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理自定义异常 * * @param ex * @return */ @ExceptionHandler(BizException.class) public Result handleBizException(BizException ex) { Result result = Result.renderErr(ex.getCode()); if (StringUtils.isNotBlank(ex.getRemark())) { result.withRemark(ex.getRemark()); } return result; } /** * 参数绑定错误 * * @param ex * @return */ @ExceptionHandler(BindException.class) public Result handleBindException(BindException ex) { StringJoiner sj = new StringJoiner(";"); ex.getBindingResult().getFieldErrors().forEach(x -> sj.add(x.getDefaultMessage())); return Result.renderErr(CodeEnum.REQUEST_ERR).withRemark(sj.toString()); } /** * 参数校验错误 * * @param ex * @return */ @ExceptionHandler(ValidationException.class) public Result handleValidationException(ValidationException ex) { return Result.renderErr(CodeEnum.REQUEST_ERR).withRemark(ex.getCause().getMessage()); } /** * 字段校验不通过异常 * * @param ex * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { StringJoiner sj = new StringJoiner(";"); ex.getBindingResult().getFieldErrors().forEach(x -> sj.add(x.getDefaultMessage())); return Result.renderErr(CodeEnum.REQUEST_ERR).withRemark(sj.toString()); } /** * Controller参数绑定错误 * * @param ex * @return */ @ExceptionHandler(MissingServletRequestParameterException.class) public Result handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) { return Result.renderErr(CodeEnum.REQUEST_ERR).withRemark(ex.getMessage()); } /** * 处理方法不支持异常 * * @param ex * @return */ @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) { return Result.renderErr(CodeEnum.METHOD_NOT_ALLOWED); } /** * 其他未知异常 * * @param ex * @return */ @ExceptionHandler(value = Exception.class) public Result handleException(Exception ex) { log.error(ex.getMessage(), ex); return Result.renderErr(CodeEnum.SERVER_ERR); } }

个性化异常处理

自定义异常

在实际web开发过程中,往往会遇到在某些场景下需要终止当前流程,直接返回。那么,通过抛出自定义异常,并在全局异常中捕获,用以友好的提示客户端。

/**
 * 业务异常跳转。
 *
 * @author [email protected]
 * @date 2019-09-09 14:57
 */
@Data
public class BizException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    private CodeEnum code;
    private String remark;

    public BizException(CodeEnum code) {
        super(code.getMessage());
        this.code = code;
    }

    public BizException withRemark(String remark) {
        this.remark = remark;
        return this;
    }
}

使用方式如下:

/**
     * 添加学生
     *
     * @param student
     * @return
     */
    public Student1DO addStudent(Student1DO student) {
        if (StringUtils.isNotBlank(student.getStudName())) {
            // 举例扔个业务异常,实际使用过程中,应该避免扔异常
            throw new BizException(CodeEnum.REQUEST_ERR).withRemark("studName不能为空");
        }
        student1Mapper.insert(student);
        return student;
    }

返回效果如下:

{
    "code": -400,
    "msg": "请求错误(studName不能为空)",
    "data": {},
    "ttl": 0
}

根据阿里巴巴规范,流程控制还是不要通过抛异常的方式。在正常开发过程中,应避免使用这种方式。
【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。

使用 Spring validation 完成数据后端校验

定义实体类,加上validation相关注解

package com.yb.demo.pojo.model.db1;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

/**
 * @author [email protected]
 * @date 2019-08-05 17:58
 */
@Data
@TableName("student")
public class Student1DO {
    private Long id;
    @Size(max = 8, message = "studName长度不能超过8")
    private String studName;
    @Min(value = 12, message = "年龄不能低于12岁")
    private Integer studAge;
    private String studSex;
    @TableField(fill = FieldFill.INSERT)
    private Integer createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateTime;
}

在Controller 方法上加上 @Validated 注解

@PostMapping("/student/add")
public Result addStudent(@Validated @RequestBody Student1DO student) {
    student = studentService.addStudent(student);
    return Result.renderOk(student);
}

实际效果如下:

{
    "code": -400,
    "msg": "请求错误(年龄不能低于12岁)",
    "data": {},
    "ttl": 0
}

更多高级的用法,详见:https://blog.csdn.net/u013815546/article/details/77248003。这个博主已经讲得很详细了,我这里就不赘述。

结束语

具体代码见:https://github.com/daoshenzzg/springboot2.x-example

本系列文章

  • springboot2.x Jackson自定义序列化,优雅实现String、List、Object返回""、[]、{}
  • springboot2.x 集成 Mybatis plus(多数据源),提升20%的开发效率
  • springboot2.x 全局异常处理正确方式
  • springboot2.x 如何优雅的实现API输出?
  • 如何优雅的实现数据置顶、置尾、交换、拖动排序?

你可能感兴趣的:(springboot2.x 全局异常处理的正确方式)