SpringBoot 错误处理
SpringBoot 默认错误机制,返回一个错误页面和 json数据
- 浏览器
- 其它客户端
1. 原理
源码 ErrorMvcAutoConfiguration
其中有几个重要的组件:
-
DefaultErrorAttributes
: 共享信息timestamp
时间status
状态码error
错误提示exception
异常对象message
异常信息errors
JSR303 数据校验的信息
-
DefaultErrorViewResolver
@Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; //使用模板引擎 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } //没有模板引擎 自动在静态资源下面去找 return resolveResource(errorViewName, model); }
-
ErrorPageCustomizer
: 自定义 -
BasicErrorController
: 处理 /error 请求@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { //html 浏览器 @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } //json 其他客户端 @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); }
2. 自定义
-
自定义页面
- 有模板引擎,将404页面放在templates/error下面,(4xx)都可以找到 , 同理5xx也是可以的(首先是精确匹配)。
- 没有模板引擎,static 目录下创建error目录以及错误页面。
-
自定义 JSON 数据
-
以下测试的controller
/** * 测试 * @param user 测试用户名:aa * @return Exception or String */ @RequestMapping("/hello") @ResponseBody public String hello(@RequestParam("user") String user) { if (user.equals("aa")) { throw new UserNotException(); } return "hello"; }
-
统一显示json
/** * @program: spring-boot-restful-crud * @description: 异常处理器 * @author: YuanChangYue * @create: 2019-08-19 12:53 */ @ControllerAdvice public class MyExceptionHandler { /** * 处理UserNotException * 同意返回的是json数据 而不是分开显示为页面或者json * @return 处理信息 */ @ResponseBody @ExceptionHandler(UserNotException.class) public Map<String, Object> handlerException(Exception e) { Map<String, Object> map = new HashMap<>(); map.put("code", "user not exist"); map.put("message", e.getMessage()); return map; } } /** * @program: spring-boot-restful-crud * @description: 用户不存在异常类 * @author: YuanChangYue * @create: 2019-08-19 12:44 */ public class UserNotException extends RuntimeException { public UserNotException() { super("用户不存在"); } }
-
-
页面和json分开 (通过转发到error)
/** * @program: spring-boot-restful-crud * @description: 异常处理器 * @author: YuanChangYue * @create: 2019-08-19 12:53 */ @ControllerAdvice public class MyExceptionHandler { // /** // * 处理UserNotException // * // * @return 处理信息 // */ // @ResponseBody // @ExceptionHandler(UserNotException.class) // public Map<String, Object> handlerException(Exception e) { // Map<String, Object> map = new HashMap<>(); // map.put("code", "user not exist"); // map.put("message", e.getMessage()); // return map; // } /** * 处理UserNotException * 注意:一定要设置错误代码 * 根据:org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#getStatus(javax.servlet.http.HttpServletRequest) * 关键点:request.setAttribute("javax.servlet.error.status_code", 500); * * @return 处理信息 */ @ResponseBody @ExceptionHandler(UserNotException.class) public String handlerException(Exception e, HttpServletRequest request) { Map<String, Object> map = new HashMap<>(); //传入错误代码 4xx 5xx //Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); request.setAttribute("javax.servlet.error.status_code", 500); map.put("code", "user not exist"); map.put("message", e.getMessage()); return "forward:/error"; } }
-
将我们的定制数据传递出去
当出现
error
时候,会来到/error
请求中,这样就会被BasicErrorController
类处理,查看这个类发现,相应的响应数据是由getErrorAttributes
(在BasicErrorController
继承的父类AbstractErrorController
(ErrorController.class
实现类))方法的得到的。再看ErrorMvcAutoConfiguration
自动装载BasicErrorController
:@Bean //当没有ErrorController.class就会加载BasicErrorController到容器中 @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);}
- 这样就可以来编写一个
BasicErrorController
的实现类,放在容器中。 BasicErrorController
最终会在ErrorAttributes
中取出响应数据,所以 我们可以编写自己的ErrorAttribute
.
/** * @program: spring-boot-restful-crud * @description: 自定义ErrorAttributes * @author: YuanChangYue * @create: 2019-08-19 15:02 */ public class MyErrorAtrributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); errorAttributes.put("owner", "ChangYue"); return errorAttributes; } }
- 这样就可以来编写一个
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190819164733763.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0MjUyOTg3,size_16,color_FFFFFF,t_70)
将自定义异常处理类需要传递的数据传递到自定义ErrorAttributes中,并添加进去
/**
* @program: spring-boot-restful-crud
* @description: 自定义ErrorAttributes
* @author: YuanChangYue
* @create: 2019-08-19 15:02
*/
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
/**
* @return 页面和json获取的字段
*/
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
errorAttributes.put("owner", "ChangYue");
//自定义需要的传递的数据
Map<String, Object> exc = (Map<String, Object>) webRequest.getAttribute("exc", 0);
errorAttributes.put("exc", exc);
return errorAttributes;
}
}
/**
* @program: spring-boot-restful-crud
* @description: 异常处理器
* @author: YuanChangYue
* @create: 2019-08-19 12:53
*/
@ControllerAdvice
public class MyExceptionHandler {
/**
* 处理UserNotException
* 注意:一定要设置错误代码
* 根据:org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#getStatus(javax.servlet.http.HttpServletRequest)
* 关键点:request.setAttribute("javax.servlet.error.status_code", 500);
*
* @return 处理信息
*/
@ExceptionHandler(UserNotException.class)
public String handlerException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
//传入错误代码 4xx 5xx
//Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
request.setAttribute("javax.servlet.error.status_code", 500);
map.put("code", "user not exist");
map.put("message", e.getMessage());
request.setAttribute("exc", map);
return "forward:/error";
}
}
同时在错误页面中也可以显示出来