基于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统一接管,封装之后返回给前台!
|