springboot2.x 全局異常處理的正確方式

前言

在web項(xiàng)目中,異常堆棧信息是非常敏感的。因此,需要一個全局的異常處理,捕獲異常,給客戶端以友好的錯誤信息提示。基于 Spring boot 很容易實(shí)現(xiàn)全局異常處理。

相關(guān)jar依賴引入

<!-- Spring Boot 啟動父依賴 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <!-- Spring Boot Web 依賴 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

全局異常控制器

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;

/**
 * 全局異常處理
 * <p>
 * 規(guī)范:流程跳轉(zhuǎn)盡量避免使用拋 BizException 來做控制。
 *
 * @author daoshenzzg@163.com
 * @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;
    }

    /**
     * 參數(shù)綁定錯誤
     *
     * @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());
    }

    /**
     * 參數(shù)校驗(yàn)錯誤
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(ValidationException.class)
    public Result handleValidationException(ValidationException ex) {
        return Result.renderErr(CodeEnum.REQUEST_ERR).withRemark(ex.getCause().getMessage());
    }

    /**
     * 字段校驗(yàn)不通過異常
     *
     * @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參數(shù)綁定錯誤
     *
     * @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);
    }
}

個性化異常處理

自定義異常

在實(shí)際web開發(fā)過程中,往往會遇到在某些場景下需要終止當(dāng)前流程,直接返回。那么,通過拋出自定義異常,并在全局異常中捕獲,用以友好的提示客戶端。

/**
 * 業(yè)務(wù)異常跳轉(zhuǎn)。
 *
 * @author daoshenzzg@163.com
 * @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;
    }
}

使用方式如下:

/**
     * 添加學(xué)生
     *
     * @param student
     * @return
     */
    public Student1DO addStudent(Student1DO student) {
        if (StringUtils.isNotBlank(student.getStudName())) {
            // 舉例扔個業(yè)務(wù)異常,實(shí)際使用過程中,應(yīng)該避免扔異常
            throw new BizException(CodeEnum.REQUEST_ERR).withRemark("studName不能為空");
        }
        student1Mapper.insert(student);
        return student;
    }

返回效果如下:

{
    "code": -400,
    "msg": "請求錯誤(studName不能為空)",
    "data": {},
    "ttl": 0
}

根據(jù)阿里巴巴規(guī)范,流程控制還是不要通過拋異常的方式。在正常開發(fā)過程中,應(yīng)避免使用這種方式。
【強(qiáng)制】異常不要用來做流程控制,條件控制,因?yàn)楫惓5奶幚硇时葪l件分支低。

使用 Spring validation 完成數(shù)據(jù)后端校驗(yàn)

定義實(shí)體類,加上validation相關(guān)注解

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 daoshenzzg@163.com
 * @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);
}

實(shí)際效果如下:

{
    "code": -400,
    "msg": "請求錯誤(年齡不能低于12歲)",
    "data": {},
    "ttl": 0
}

更多高級的用法,詳見:https://blog.csdn.net/u013815546/article/details/77248003。這個博主已經(jīng)講得很詳細(xì)了,我這里就不贅述。

結(jié)束語

具體代碼見:https://github.com/daoshenzzg/springboot2.x-example

本系列文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容