为什么需要统一异常处理: 在后端发生异常或者请求出错时,如果前端展示如下:
There was an unexpected error (type=Not Found, status=404).
No message available
对于用户来说非常不友好,用户看了还是不明白具体的错误信息,往往后端就需要在代码块里加上try / catch,捕获该异常以一种更友好的方式告诉用户发生了何种异常,比如:"该页面不存在,请检查访问路径是否正确!"。当业务逻辑很多,代码很多,如果在每个业务逻辑处都上面try / catch,就显得代码很乱很臃肿。
全局异常处理: 通过Spring的AOP特性使用@ControllerAdvice、@ExceptionHandler定义全局的运行时异常处理。
1、在common创建exception子目录,通用异常处理设计的类均放在该目录下:
2、新建通用业务异常接口BaseException:
package com.cxzx.cnctest.common.exception;
/**
* @author 某角落的IT打杂工
* @version 1.0
* @description: TODO
* @date 2024/5/28 9:24
*/
public interface BaseException {
String getErrorCode();
String getErrorMsg();
}
3、新建异常code的枚举类BizCodeEnum:
package com.cxzx.cnctest.common.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author 某角落的IT打杂工
* @version 1.0
* @description: TODO
* @date 2024/5/28 11:11
*/
@Getter
@AllArgsConstructor
public enum BizCodeEnum implements BaseException{
// ----------- 通用异常状态码 -----------
SYSTEM_ERROR("10000", "出错啦,后台小哥正在努力修复中..."),
;
// 异常码
private String errorCode;
// 错误信息
private String errorMsg;
}
4、新建通用业务异常处理类:BizException:
package com.cxzx.cnctest.common.exception;
import lombok.Getter;
import lombok.Setter;
/**
* @author 某角落的IT打杂工
* @version 1.0
* @description: TODO
* @date 2024/5/28 11:22
*/
@Getter
@Setter
public class BizException extends RuntimeException{
// 异常码
private String errorCode;
// 错误信息
private String errorMsg;
public BizException(BaseException baseException) {
this.errorCode = baseException.getErrorCode();
this.errorMsg = baseException.getErrorMsg();
}
public BizException(String errorMsg){
this.errorMsg = errorMsg;
}
public BizException(String errorCode, String errorMsg){
this.errorMsg = errorMsg;
}
}
BaseResponse新增fail方法(入参为BaseException和BizException):
public static <T> BaseResponse<T> fail(BaseException baseException) {
BaseResponse<T> response = new BaseResponse<>();
response.setSuccess(false);
response.setErrorCode(baseException.getErrorCode());
response.setErrorMsg(baseException.getErrorMsg());
return response;
}
public static <T> BaseResponse<T> fail(BizException bizException) {
BaseResponse<T> response = new BaseResponse<>();
response.setSuccess(false);
response.setErrorCode(bizException.getErrorCode());
response.setErrorMsg(bizException.getErrorMsg());
return response;
}
5、定义全局异常处理类 GlobalExceptionHandler:
common层的pom.xml引入web的依赖(@ControllerAdvice注解的类在该依赖里)
<!-- ........ 省略 -->
<dependencies>
<!-- ........ 省略 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- ........ 省略 -->
该类的代码如下:
package com.cxzx.cnctest.common.exception;
import com.cxzx.cnctest.common.utils.BaseResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* @author 某角落的IT打杂工
* @version 1.0
* @description: TODO
* @date 2024/5/28 9:15
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
//捕获通用异常
@ExceptionHandler({ Exception.class })
@ResponseBody
public BaseResponse<Object> handleCommonException(HttpServletRequest request, Exception e) {
log.error("{} request error, ", request.getRequestURI(), e);
return BaseResponse.fail(BizCodeEnum.SYSTEM_ERROR);
}
// 捕获业务异常
@ExceptionHandler({BizException.class})
@ResponseBody
public BaseResponse<Object> handleBizException(HttpServletRequest request, BizException e) {
log.error("{} request fail, errorCode: {}, errorMsg: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg());
return BaseResponse.fail(e);
}
}
最终common层异常类定义如下:
6、测试效果:
starter层新建测试controller:
package com.cxzx.cnctest.start.test;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 某角落的IT打杂工
* @version 1.0
* @description: TODO
* @date 2024/5/28 13:47
*/
@RestController
@RequestMapping("/cnc")
public class TestController {
@GetMapping("/test")
public String test(@RequestParam(name = "test") String name){
return name;
}
}
- 比如浏览器访问该path,制造参数名称错误:
http://localhost:8080/cnc/test?temp=test
后端controller层捕获到异常后按照异常处理返回的格式返回数据给前端:
前端就可以提取errorMsg展示给用户看。
- 再比如主动抛出业务异常
BizCodeEnum 定义业务异常code:
PRODUCT_NOT_EXISTS("10001","该产品不存在"),
service层新建TestService类,代码如下:
package com.cxzx.cnctest.service;
import com.cxzx.cnctest.common.exception.BizCodeEnum;
import com.cxzx.cnctest.common.exception.BizException;
import org.springframework.stereotype.Service;
/**
* @author 某角落的IT打杂工
* @version 1.0
* @description: TODO
* @date 2024/5/28 17:18
*/
@Service
public class TestService {
public String test(){
throw new BizException(BizCodeEnum.PRODUCT_NOT_EXISTS);
}
}
TestController 类中调用该service的方法
package com.cxzx.cnctest.start.test;
import com.cxzx.cnctest.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 某角落的IT打杂工
* @version 1.0
* @description: TODO
* @date 2024/5/28 13:47
*/
@RestController
@RequestMapping("/cnc")
public class TestController {
@Autowired
TestService testService;
@GetMapping("/test")
public String test(@RequestParam(name = "test") String name){
return testService.test();
}
}
浏览器中访问:http://localhost:8080/cnc/test?test=test
抛出业务异常: