SpringBoot 应用搭建之始终返回统一的JSON格式
之前的一篇文章:SpringBoot 应用搭建之全局异常处理要这样搞
有网友进行了尝试,留言说404的怎么处理?
上篇文章中的处理方法确实只能保证进入我们的Controller之后的异常都进行捕获,并返回统一的格式。
但是在没进入Controller前的一些异常是无法捕获的。比如404,400、还有一些500。
关于这些错误的处理方法大概有下面这些:
方式一、前端统一处理
其实对于这些错误,不建议后端全部拦截,使用SpringBoot框架的应用应该都进行了前后端分离。可以在前端统一进行网络请求的拦截处理,如果返回的http status 不是200,前端对于不同的status进行相应的处理即可。
方式二、对于404的处理
我们可以在Springboot的配置中设置404时抛出异常。这样我们就可以在GlobalExceptionInterceptor类中进行捕获了。
配置:
# 出现404错误时, 直接抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
# 不要对资源文件建立映射
spring.resources.add-mappings=false
然后我们直接在 类中捕获该类型的异常即可:
package com.xtoad.study.common.web.interceptor;
import com.xtoad.study.common.web.ResultDTO;
import com.xtoad.study.common.web.exception.BusinessException;
import com.xtoad.study.common.web.exception.ResultCodeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常捕捉处理
*
* @author xtoad
* @date 2021/02/18
*/
@RestControllerAdvice
public class GlobalExceptionInterceptor {
private final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionInterceptor.class);
/**
* 全局业务异常捕捉处理
*
* @param ex 业务异常
* @return 结果
*/
@ExceptionHandler(value = BusinessException.class)
public ResultDTO businessExceptionHandler(BusinessException ex) {
LOGGER.info(ex.getMsg(), ex);
return ResultDTO.failed(ex.getCode(), ex.getMsg());
}
/**
* 全局异常捕捉处理
*
* @param ex Exception
* @return 结果
*/
@ExceptionHandler(value = Exception.class)
public ResultDTO exceptionHandler(Exception ex) {
LOGGER.error(ex.getMessage(), ex);
if (ex instanceof MethodArgumentNotValidException) {
return ResultDTO.failed(ResultCodeEnum.INVALID_PARAMETER);
} else if (ex instanceof org.springframework.web.servlet.NoHandlerFoundException) {
// 这里判断异常是404时
return ResultDTO.failed("404", "请求的url不存在");
} else {
return ResultDTO.failed(ResultCodeEnum.UNKNOW_ERROR);
}
}
}
方式三、重写ErrorController,任何时候都返回统一格式
上面方式二是能处理404了,但是也不能映射静态资源了,而且开篇我们说了,除了404,还有他的一些异常,如何捕获呢?
事实上,SpringBoot 在处理异常的时候,500/404默认都会转发到/error这个url下,而这个异常的处理类是ErrorController。
所以我们重写ErrorController:
@RestController
public class GlobalExceptionController extends AbstractErrorController {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionController.class);
private static final String ERROR_PATH = "/error";
public GlobalExceptionController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
@RequestMapping(value = ERROR_PATH)
public ResultDTO error(HttpServletRequest request) {
WebRequest webRequest = new ServletWebRequest(request);
Throwable e = getError(webRequest);
if (e == null) {
Map<String, Object> attributes = getErrorAttributes(request, false);
Object timestamp = attributes.get("timestamp");
Object status = attributes.get("status");
String error = attributes.get("error").toString();
Object path = attributes.get("path");
LOGGER.error("status {} error {} path{} timestamp {}", status, error, path, timestamp);
return ResultDTO.failed(Integer.parseInt(status.toString()), error);
}
if (e instanceof TokenExpiredException) {
TokenExpiredException tokenExpiredException = (TokenExpiredException) e;
return ResultDTO.failed(tokenExpiredException.getHttpStatus().value(), tokenExpiredException.getHttpStatus().getReasonPhrase());
} else if (e instanceof CodeException) {
CodeException codeException = (CodeException) e;
String message = e.getMessage();
if (StringUtils.isEmpty(message)) {
message = String.format("[%s][%s]", codeException.getErrCode(), codeException.getErrMsg());
}
return ResultDTO.failed(codeException.getErrCode(), message);
} else {
return ResultDTO.failed("500", "系统繁忙,请稍后再试");
}
}
private Throwable getError(WebRequest webRequest) {
return (Throwable) this.getAttribute(webRequest, "javax.servlet.error.exception");
}
private Object getAttribute(RequestAttributes requestAttributes, String name) {
return requestAttributes.getAttribute(name, 0);
}
}
该方式,比较简单粗暴,除非你确定你的应用无论什么场合都返回统一的JSON,不否不建议使用。
更推荐方式一,前端统一拦截,对http status 进行判断处理。