前言
在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