我大概把一次请求分成三个阶段,来分别进行全局的异常处理。
当使用SpringBoot调用controller时,如果系统出现404,405,500等这种报错信息时,系统默认会展示如下信息:
SpringBoot的默认异常处理BasicErrorController,它会根据配置参数server.error.path
来展示错误页面。BasicErrorController这两个方法是关键。
public String getErrorPath() {
return this.errorProperties.getPath();
}
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
通过研究BasicErrorController,我们自定义错误异常需要实现ErrorContrroller大概需要四点。
@Slf4j
@Controller
@RequestMapping("/error")
@EnableConfigurationProperties({ServerProperties.class})
public class ErrorPagesController implements ErrorController {
private ErrorAttributes errorAttributes;
@Autowired
private ServerProperties serverProperties;
/**
* 初始化ExceptionController
*
* @param errorAttributes
*/
@Autowired
public ErrorPagesController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
@RequestMapping("/404")
public ModelAndView errorHtml404(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.NOT_FOUND.value());
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("queryString", request.getQueryString());
return new ModelAndView("error/404", model);
}
@RequestMapping("/403")
public ModelAndView errorHtml403(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.FORBIDDEN.value());
// 404拦截规则,如果是静态文件发生的404则不记录到DB
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("queryString", request.getQueryString());
if (!String.valueOf(model.get("path")).contains(".")) {
model.put("status", HttpStatus.FORBIDDEN.value());
}
return new ModelAndView("error/403", model);
}
@RequestMapping("/400")
public ModelAndView errorHtml400(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("queryString", request.getQueryString());
return new ModelAndView("error/400", model);
}
@RequestMapping("/401")
public ModelAndView errorHtml401(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("queryString", request.getQueryString());
return new ModelAndView("error/401", model);
}
@RequestMapping("/500")
public ModelAndView errorHtml500(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("queryString", request.getQueryString());
return new ModelAndView("error/500", model);
}
/**
* Determine if the stacktrace attribute should be included.
*
* @param request
* the source request
* @param produces
* the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included
*/
protected boolean isIncludeStackTrace(HttpServletRequest request,
MediaType produces) {
ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;
}
return include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM && getTraceParameter(request);
}
/**
* 获取错误的信息
*
* @param webRequest
* @param includeStackTrace
* @return
*/
private Map<String, Object> getErrorAttributes(WebRequest webRequest,
boolean includeStackTrace) {
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
/**
* 是否包含trace
*
* @param request
* @return
*/
private boolean getTraceParameter(HttpServletRequest request) {
String parameter = request.getParameter("trace");
return parameter != null && !"false".equalsIgnoreCase(parameter);
}
/**
* 获取错误编码
*
* @param request
* @return
*/
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
} catch (Exception ex) {
log.error("获取当前HttpStatus发生异常", ex);
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
/**
* 实现错误路径,暂时无用
*
* @return
*/
@Override
public String getErrorPath() {
return "";
}
}
在执行@RequestMapping时,进入逻辑处理阶段前。譬如传的参数类型错误。
/**
* Created by wuwf on 17/3/31.
* 全局异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static Logger logger = LogManager.getLogger(GlobalExceptionHandler.class.getName());
/**
* 在controller里面内容执行之前,校验一些参数不匹配啊,Get post方法不对啊之类的
*/
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("错误");
// TODO
return new ResponseEntity<Object>("出错了", NOT_EXTENDED);
}
}
定义一个类,使用@ControllerAdvice
注解,继承ResponseEntityExceptionHandler
类,这个类里面实现了很多方法,可以去看看,包括一些参数转换,请求方法不支持等等之类的异常都会被捕获。
被捕获的原因是@ExceptionHandler
标签,里面所有的异常类只要发生了,就会被这个方法所捕获。
当第一、第二种都没出异常,进入到实际逻辑执行了,然后发生了异常,这样就可以自己定义一个ExceptionHandler的方法,来处理相应的Exception。
/**
* Created by wuwf on 17/3/31.
* 全局异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static Logger logger = LogManager.getLogger(GlobalExceptionHandler.class.getName());
/**
* 在controller里面内容执行之前,校验一些参数不匹配啊,Get post方法不对啊之类的
*/
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println("错误");
return new ResponseEntity<Object>("出错了", NOT_EXTENDED);
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Map<String, String> jsonExceptionHandler(HttpServletRequest req, ServiceException e) {
Map<String, String> re = new HashMap<String, String>();
re.put("status", e.getStatus());
re.put("msg", e.getMessage());
return re;
}
}