文章目录
- 前言
- 一、先定义一个业务异常类
- 二、定义全局异常处理器
- 二、测试
- 小插曲
- 抛出异常:
- 抛出自定义异常:
- 总结
前言
上一章对统一返回值进行封装,但是都是基于正常情况下的返回,系统难免会出现异常的情况,我们不可能在每一个地方都try...catch
,但是异常有分为很多种类,我们需要对异常进行统一的拦截,希望友好的返回给客户端信息,而不是报一堆错误代码…
Spring中提供了一个@RestControllerAdvice
它是 @ControllerAdvice
和 @ResponseBody
的结合体,意味着使用该注解的类可以为所有的@RequestMapping
处理方法提供通用的异常处理和数据绑定等增强功能,并且方法的返回值将被直接写入 HTTP 响应体中,这里用来做全局异常处理正合适不过。
我们需要配合@ExceptionHandler
和@ResponseStatus
一起结合着使用
-
@ExceptionHandler
该注解用于标注在异常处理方法,该方法需要@RestControllerAdvice
写到类中,其作用是当控制器抛出异常时候,Spring
会根据异常的类型调用相应的异常处理方法,我们可以利用该注解实现各种不同类型的异常处理逻辑 -
@ResponseStatus
该注解也是注解到异常处理方法上的,主要是为了设置返回的http状态码。如果我们希望在后端异常的时候,客户端的http
状态码也跟随变化,则需要用到此注解
一、先定义一个业务异常类
在com.light.common
中创建一个exception
包,并在exception
中创建一个ServiceException
代码如下:
import com.light.common.result.IResultCode;import java.io.Serial;/*** 自定义业务异常类*/
public class ServiceException extends RuntimeException {@Serialprivate static final long serialVersionUID = -2184933109131865121L;private IResultCode resultCode;public ServiceException(IResultCode resultCode) {super(resultCode.getMessage());this.resultCode = resultCode;}public ServiceException(String message) {super(message);}public ServiceException(Throwable cause) {super(cause);}public ServiceException(String message, Throwable cause) {super(message, cause);}public IResultCode getErrorCode() {return resultCode;}
}
二、定义全局异常处理器
在exception
中创建一个GlobalExceptionHandler
代码如下:
import cn.hutool.core.convert.Convert;
import cn.hutool.http.HTMLFilter;
import com.light.common.result.BaseResult;
import com.light.common.result.ResultCode;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;/*** 全局异常处理器*/
@RestControllerAdvice
public class GlobalExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 自定义业务异常*/@ExceptionHandler(ServiceException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic Object handleServiceException(ServiceException e, HttpServletRequest request) {log.error("自定义业务异常,'{}'", e.getMessage(), e);if (e.getErrorCode() != null) {return BaseResult.error(e.getErrorCode());}return BaseResult.error(e.getMessage());}/*** 自定义验证异常*/@ExceptionHandler(BindException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic BaseResult<?> handleBindException(BindException e) {log.error(e.getMessage(), e);BindingResult bindingResult = e.getBindingResult();String message = null;if (bindingResult.hasErrors()) {FieldError fieldError = bindingResult.getFieldError();if (fieldError != null) {message = fieldError.getField() + fieldError.getDefaultMessage();}}return BaseResult.error(message);}/*** 请求参数类型不匹配*/@ExceptionHandler(MethodArgumentTypeMismatchException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic BaseResult<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,HttpServletRequest request) {String requestURI = request.getRequestURI();String value = Convert.toStr(e.getValue());if (StringUtils.isNotEmpty(value)) {value = new HTMLFilter().filter(value);}log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);return BaseResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value));}/*** 请求路径中缺少必需的路径变量*/@ExceptionHandler(MissingPathVariableException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic BaseResult<?> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);return BaseResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));}/*** 参数验证异常*/@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic BaseResult<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {String requestURI = request.getRequestURI();BindingResult bindingResult = e.getBindingResult();String message = null;if (bindingResult.hasErrors()) {FieldError fieldError = bindingResult.getFieldError();if (fieldError != null) {message = fieldError.getField() + fieldError.getDefaultMessage();}}log.error("请求地址'{}',参数验证失败", requestURI);return BaseResult.error(ResultCode.VALIDATE_FAILED, message);}/*** 请求方式异常*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)@ResponseBodypublic BaseResult<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());return BaseResult.error(ResultCode.METHOD_FAILED, "请求方式不支持:{}" + e.getMethod());}/*** 拦截未知的运行时异常*/@ExceptionHandler(RuntimeException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic BaseResult exceptionHandler(RuntimeException e, HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("请求地址'{}',发生未知异常.", requestURI, e);return BaseResult.error("未知异常:"+e.getMessage());}/*** 拦截系统异常*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic BaseResult handleException(Exception e, HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("请求地址'{}',发生系统异常.", requestURI, e);return BaseResult.error("系统异常:"+e.getMessage());}
}
二、测试
通过上述代码处理后,我们在写业务逻辑的时候,只需要关心自己的主要业务,无需花费大量精力去写try...catch
小插曲
直接测试会发现无效,返回的信息并不是我们定义的消息类结构,原因是默认情况下,Spring Boot会扫描启动类所在包及其子包,而我们这里的Controller
和GlobalExceptionHandler
分别在不同的子模块中,所以没有被关联上,我们只需要在启动类上添加手动扫描包路径即可
@SpringBootApplication
@ComponentScan(basePackages = {"com.light.api", "com.light.common"})
public class LightApiApplication {public static void main(String[] args) {SpringApplication.run(LightApiApplication.class, args);}
}
抛出异常:
- 我们在控制器中人为制造一个异常比如,除数为0
@GetMapping("/getMsg")public BaseResult<?> getTest() {int s=1/0;
// throw new ServiceException("自定义异常");return BaseResult.success("成功");}
测试请求,返回结果如下
抛出自定义异常:
如果需要对自己的业务代码进行自定义抛出异常可采用此方式
@GetMapping("/getMsg")public BaseResult<?> getTest() {throw new ServiceException("自定义异常");}
测试请求,返回结果如下
总结
至此,我们对整个系统进行的全局异常的统一处理,写代码的时候不再花费大量精力考虑try...catch
的事情了,这也是后端需要做到的统一返回标准
下一章写统一日志处理