基于springboot的全局异常处理
1 编写ResultBuilder类
package com.test.domi.common.utils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public class ResultBuilder implements HandlerExceptionResolver,Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(ResultBuilder.class);
private static final String ERROR_NAME = "fp.error";
private ErrorProperties errorProperties;
public ErrorProperties getErrorProperties() {
return errorProperties;
}
public ResultBuilder(ServerProperties serverProperties){
LOGGER.info("serverProperties:{}",serverProperties.getError());
this.errorProperties = serverProperties.getError();
}
public ResultInfo getErrorInfo(HttpServletRequest request){
return this.getErrorInfo(request,this.getError(request));
}
/**
* 全局异常返回处理
* @param request
* @param error
* @return
*/
public ResultInfo getErrorInfo(HttpServletRequest request,Throwable error){
ResultInfo resultInfo = new ResultInfo();
//根据不同的error获取错误信息
String resultCode = "";
StringBuffer msg = new StringBuffer();
if (error instanceof MethodArgumentNotValidException) {
//1 参数校验异常
resultCode = getString2((MethodArgumentNotValidException) error, resultCode, msg);
}else {
//3 httpStatu枚举code对应的异常
resultCode = getString3(request, msg);
}
resultInfo.setCode(resultCode);
resultInfo.setMessage(msg.toString());
resultInfo.setData((Object)null);
return resultInfo;
}
private String getString3(HttpServletRequest request, StringBuffer msg) {
msg.append(this.getHttpStatus(request).getReasonPhrase());
return String.valueOf(this.getHttpStatus(request).value());
}
private String getString2(MethodArgumentNotValidException error, String resultCode, StringBuffer msg) {
BindingResult bindingResult = error.getBindingResult();
if (bindingResult.hasErrors()) {
List<FieldError> list = bindingResult.getFieldErrors();
resultCode =ResultCode.CONNECT_ERROR.getCode();
for (FieldError fieldError : list) {
msg.append(fieldError.getDefaultMessage() + ";");
}
}
return resultCode;
}
private String getString(Throwable error, StringBuffer msg) {
msg.append(error.getMessage());
return ResultCode.INSERT_ERROR.getCode();
}
/**
* 拿到最根部的error,携带手动抛出的异常信息
* @param request
* @return
*/
public Throwable getError(HttpServletRequest request){
Throwable error = (Throwable)request.getAttribute(ERROR_NAME);
if (error == null) {
error = (Throwable)request.getAttribute("javax.servlet.error.exception");
}
if (error != null) {
//while (error instanceof ServletException && ((Throwable) error).getCause() != null) {
while (error instanceof Exception && ((Throwable) error).getCause() != null) {
error = ((Throwable) error).getCause();
}
} else {
String message = (String)request.getAttribute("javax.servlet.error.message");
if (StringUtils.isNotEmpty(message)) {
HttpStatus status = this.getHttpStatus(request);
message = "Unknown Exception With" + status.value() + " " + status.getReasonPhrase();
}
error = new Exception(message);
}
return (Throwable)error;
}
public HttpStatus getHttpStatus(HttpServletRequest request){
Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
try {
return statusCode != null ? HttpStatus.valueOf(statusCode.intValue()) : HttpStatus.INTERNAL_SERVER_ERROR;
} catch (Exception var4) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
httpServletRequest.setAttribute(ERROR_NAME, e);
return null;
}
}
2 编写ExceptionConfig类(传入ServerProperties ,实例化ResultBuilder。springboot中ErrorProperties类定义了异常自动映射路径@Value("${error.path:/error}")private String path = "/error")
package com.test.domi.config; import com.test.domi.common.system.ResultBuilder; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import javax.annotation.Resource; @Configuration @Order(Ordered.HIGHEST_PRECEDENCE) public class ExceptionConfig { @Resource private ServerProperties serverProperties; @Bean public ResultBuilder resultBuilder(){ return new ResultBuilder(serverProperties); } }
ErrorProperties:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.autoconfigure.web; import org.springframework.beans.factory.annotation.Value; public class ErrorProperties { @Value("${error.path:/error}") private String path = "/error"; private ErrorProperties.IncludeStacktrace includeStacktrace; public ErrorProperties() { this.includeStacktrace = ErrorProperties.IncludeStacktrace.NEVER; } public String getPath() { return this.path; } public void setPath(String path) { this.path = path; } public ErrorProperties.IncludeStacktrace getIncludeStacktrace() { return this.includeStacktrace; } public void setIncludeStacktrace(ErrorProperties.IncludeStacktrace includeStacktrace) { this.includeStacktrace = includeStacktrace; } public static enum IncludeStacktrace { NEVER, ALWAYS, ON_TRACE_PARAM; private IncludeStacktrace() { } } }
3 定义全局 异常Controller接管所有抛出的异常
package spring.cloud.common.controller;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import spring.cloud.common.util.ResultBuilder;
import spring.cloud.common.util.ResultInfo;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/error")
public class GlobalErrorController implements ErrorController{
/**
* 1 ErrorController 接口的默认实现类是abstract:AbstractErrorController
* 2 AbstractErrorController 的子类 BasicErrorController 才是真正干活儿的实现类(分html、json 两个方法处理,我们只需要在GlobalErrorController重写这2个方法即可)
* 3 BasicErrorController 有 private final ErrorProperties errorProperties;属性
* ErrorProperties里面记录了error的路径:
* @Value("${error.path:/error}")
* private String path = "/error";
* -----
* 4 BasicErrorController 的封装只能将状态码的提示信息返回前台,不能拿到手动抛异常的信息,因此需要实现HandlerExceptionResolver
* ------------------------------------------------------------
* BasicErrorController只有有参构造,无法直接继承
* 如果不实现ErrorController,则会造成相同路径/error有2个类,冲突了。启动时报如下异常:
* org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'basicErrorController' method
* public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
* to {[/error],produces=[text/html]}: There is already 'globalErrorController' bean method
*/
private final static String DEFAULT_ERROR_VIEW = "/error";
private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(GlobalErrorController.class);
/**
* ResultBuilder 实现 HandlerExceptionResolver 接口重写public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)
* 通过httpServletRequest.setAttribute("fp.error", e);将Exception放到request中
* 这种方法的好处是能拿到手动抛异常的信息
*/
@Resource
private ResultBuilder resultBuilder;
/** 1- BasicErrorController只有有参构造,无法直接继承,
* 2- 如果不实现ErrorController,则会造成相同路径/error有2个类,冲突了。启动时报如下异常:
* org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'basicErrorController' method
* public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
* to {[/error],produces=[text/html]}: There is already 'globalErrorController' bean method
* ----------------------
* ErrorProperties里面记录了error的路径:
* * @Value("${error.path:/error}")
* * private String path = "/error";
* 如果不需要从GlobalErrorController中的getErrorPath方法获取该路径,则该方法可以空实现
*/
@Override
public String getErrorPath(){
// return null;
return resultBuilder.getErrorProperties().getPath();
}
/**
* 如果请求头返回的类型是text/html,则返回到错误信息页面
* @param request
* @return
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request) {
return new ModelAndView(DEFAULT_ERROR_VIEW,"errorInfo",resultBuilder.getErrorInfo(request));
}
/**
* 除了text/html的请求头信息,其它都返回json格式
* @param request 请求对象
* @return 错误信息字符串
*/
@RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
public ResultInfo error(HttpServletRequest request){
return resultBuilder.getErrorInfo(request);
}
}
配置完毕,后台的未被捕获的异常将从dao层到dervice层到controller层,然后被全局异常controller统一接管,封装之后返回给前台!