可以进一步优化的方向
Spring异常处理应该是使用了 HandlerExceptionResolver
接口的实现类,继承层次如下:
HandlerExceptionResolver
└─ AbstractHandlerExceptionResolver
└─ DefaultHandlerExceptionResolver
这里可以去参考 DefaultHandlerExceptionResolver
去实现自己的统一异常处理
统一响应VO
import java.util.HashMap;
import java.util.Map;
public class ResponseVo<T> {
private int code;
private String msg;
private T data;
private ResponseVo() {}
private ResponseVo(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> ResponseVo<T> ok(T data) {
return new ResponseVo<>(200, "", data);
}
public static <T> ResponseVo<T> fail(int errorCode, String errorMessage) {
return new ResponseVo<>(errorCode, errorMessage, null);
}
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("code", code);
map.put("msg", msg);
map.put("data", data);
return map;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
统一异常处理控制器
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.parenting.applet.controller.vo.ResponseVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;
/**
* 统一异常处理
*
* @author 吕相成
* @date 2020-05-27 11:03
*/
@RestController
@ControllerAdvice
public class ExceptionController {
private final static Logger LOG = LoggerFactory.getLogger(ExceptionController.class);
/**
* 数据绑定异常处理
*
* @param result 绑定结果
* @return 统一响应对象
*/
@ExceptionHandler(BindException.class)
public ResponseVo bindException(HttpServletRequest request, BindingResult result) {
StringBuilder builder = new StringBuilder();
// 如果存在错误
if (result.hasErrors()) {
// 往错误消息中添加对象级错误
for (ObjectError error : result.getGlobalErrors()) {
String errorMessage = String.format("%s: %s; ", error.getObjectName(), error.getDefaultMessage());
builder.append(errorMessage);
}
// 往错误消息中添加字段级错误
for (FieldError error : result.getFieldErrors()) {
String errorMessage;
if (error.isBindingFailure()) {
// 数据绑定失败,默认信息会暴露太多细节,替换为自定义信息
errorMessage = String.format("%s: 非法数据; ", error.getField());
} else {
// 数据校验不通过,返回默认信息
errorMessage = String.format("%s: %s; ", error.getField(), error.getDefaultMessage());
}
builder.append(errorMessage);
}
}
LOG.info(String.format("拒绝访问。非法参数: %s -> %s", request.getServletPath(), builder.toString()));
return ResponseVo.fail(400, builder.toString());
}
/**
* 处理统一处理404异常
* 此异常默认不会被抛出,需要进行一些配置
* 首先配置spring.mvc.throw-exception-if-no-handler-found=true
* 其次关闭ResourceHandler:spring.resources.add-mappings=false
* 或者修改ResourceHandler路径:spring.mvc.static-path-pattern=/static/**
*
* @param ex 异常对象
* @return 统一响应对象
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseVo notFoundException(NoHandlerFoundException ex) {
String msg = String.format("拒绝访问。未找到资源: %s %s %s",
ex.getHttpMethod(), ex.getRequestURL(), ex.getHeaders());
LOG.info(msg);
return ResponseVo.fail(404, "资源未找到");
}
/**
* 处理缺少请求参数的异常
*
* @param ex 异常对象
* @return 统一响应对象
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseVo missingReqParamException(HttpServletRequest request, MissingServletRequestParameterException ex) {
String url = request.getServletPath();
String paramName = ex.getParameterName();
String msg = String.format("拒绝访问。缺少必需参数: %s -> %s", url, paramName);
LOG.info(msg);
return ResponseVo.fail(400, "缺少必需参数: " + paramName);
}
/**
* 处理请求参数类型不匹配的异常
*
* @param ex 异常对象
* @return 统一响应对象
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseVo methodArgumentTypeMismatchException(HttpServletRequest request, MethodArgumentTypeMismatchException ex) {
String url = request.getServletPath();
String paramName = ex.getName();
String paramValue = request.getParameter(paramName);
String msg = String.format("拒绝访问。请求参数类型错误: %s -> %s=%s", url, paramName, paramValue);
LOG.info(msg);
return ResponseVo.fail(400, "参数类型错误" + paramName);
}
/**
* 处理请求方法未支持异常
*
* @param request 请求对象
* @param ex 异常对象
* @return 统一响应对象
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseVo methodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
String url = request.getServletPath();
String method = ex.getMethod();
String msg = String.format("拒绝访问。不支持的请求方法: [%s] %s", method, url);
LOG.info(msg);
return ResponseVo.fail(400, "不支持该请求方法: " + method);
}
/**
* 处理Http请求转换失败的异常(非可读格式)
*
* @param ex 异常对象
* @return 统一响应对象
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseVo httpMessageNotReadableException(HttpMessageNotReadableException ex) {
Throwable cause = ex.getCause();
// Json绑定失败
if (cause instanceof JsonMappingException) {
LOG.info("Json绑定失败");
return handleJsonMappingException((JsonMappingException) cause);
}
// Json解析失败
if (cause instanceof JsonParseException) {
LOG.info("Json解析失败");
return ResponseVo.fail(400, "非法的json");
}
LOG.info("请求解析失败", ex);
return ResponseVo.fail(400, "非法请求体");
}
/**
* 更加友好的json数据转换异常处理
* 从{@link JsonMappingException}中读取异常字段名,拼接到异常消息中
*
* @param ex 异常对象
* @return 统一响应对象
*/
private ResponseVo handleJsonMappingException(JsonMappingException ex) {
StringBuilder builder = new StringBuilder();
for (JsonMappingException.Reference reference : ex.getPath()) {
builder.append(String.format("%s: 格式非法;", reference.getFieldName()));
}
String msg = builder.toString();
return ResponseVo.fail(400, msg);
}
/**
* 处理其他未定义的内部异常
*
* @param ex 异常对象
* @return 统一响应对象
*/
@ExceptionHandler(Exception.class)
public ResponseVo globalException(Exception ex) {
LOG.error("服务器内部异常!", ex);
return ResponseVo.fail(500, "服务器内部异常");
}
}
404异常统一处理需要在application.yml中做的配置
spring:
mvc:
static-path-pattern: /static/**
throw-exception-if-no-handler-found: true
或者
spring:
mvc:
throw-exception-if-no-handler-found: true
resources:
add-mappings: false