SpringBoot(六) -- SpringBoot错误处理机制
一.SpringBoot中的默认的错误处理机制
1.在SpringBootWeb开发中,当我们访问请求出现错误时,会返回一个默认的错误页面:
2.在使用其他客户端访问的时候,则返回一个json数据:
3.原理:可以参看原码ErrorMvcAutoConfiguration:
(1)给容器中添加了以下组件
DefaultErrorAttributes:帮我们在页面共享信息
1 @Override
2 public Map getErrorAttributes(RequestAttributes requestAttributes,
3 boolean includeStackTrace) {
4 Map errorAttributes = new LinkedHashMap();
5 errorAttributes.put("timestamp", new Date());
6 addStatus(errorAttributes, requestAttributes);
7 addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
8 addPath(errorAttributes, requestAttributes);
9 return errorAttributes;
10 }
11
12 private void addStatus(Map errorAttributes,
13 RequestAttributes requestAttributes) {
14 Integer status = getAttribute(requestAttributes,
15 "javax.servlet.error.status_code");
16 if (status == null) {
17 errorAttributes.put("status", 999);
18 errorAttributes.put("error", "None");
19 return;
20 }
21 errorAttributes.put("status", status);
22 try {
23 errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
24 }
25 catch (Exception ex) {
26 // Unable to obtain a reason
27 errorAttributes.put("error", "Http Status " + status);
28 }
29 }
--可获取到的属性
timestamp:时间戳
status:状态码
error:错误提示
exception:异常
message:异常消息
errors:JSR303数据校验的错误都在这
BasicErrorController:处理默认的/eerror请求
1 @RequestMapping(produces = "text/html")
2 public ModelAndView errorHtml(HttpServletRequest request,
3 HttpServletResponse response) {
4 HttpStatus status = getStatus(request);
5 Map model = Collections.unmodifiableMap(getErrorAttributes(
6 request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
7 response.setStatus(status.value());
8 ModelAndView modelAndView = resolveErrorView(request, response, status, model);
9 return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
10 }
11
12 @RequestMapping
13 @ResponseBody
14 public ResponseEntity
--将会存在两种情况,得到一个html页面或者一个JSON数据,在浏览器发起的请求中,可发现其请求头accept中标识着优先接收html页面:
--当其他客户端发送请求时,可以发现其请求头没有标注优先接收html数据(使用了*/*)
--浏览器发送的请求将来到错误的html页面,而其他客户端则会接收到一个错误的JSON数据.在浏览器发送错误请求时,使用了ModelAndView声明了当前错误页面的地址和页面内容.
1 protected ModelAndView resolveErrorView(HttpServletRequest request,
2 HttpServletResponse response, HttpStatus status, Map model) {
3 for (ErrorViewResolver resolver : this.errorViewResolvers) {
4 ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
5 if (modelAndView != null) {
6 return modelAndView;
7 }
8 }
9 return null;
10 }
--在该方法中将所有的ErrorViewResolver都获取到,而后返回,而我们去哪个页面则是由ErrorViewResolver得到的.
ErrorPageCustomizer:系统出现错误时,来到error请求进行处理
DefaultErrorViewResolver:
1 @Override
2 public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
3 Map model) {
4 ModelAndView modelAndView = resolve(String.valueOf(status), model);
5 if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
6 modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
7 }
8 return modelAndView;
9 }
10
11 private ModelAndView resolve(String viewName, Map model) {
12 String errorViewName = "error/" + viewName;
13 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
14 .getProvider(errorViewName, this.applicationContext);
15 if (provider != null) {
16 return new ModelAndView(errorViewName, model);
17 }
18 return resolveResource(errorViewName, model);
19 }
--SpringBoot可以去找到某个页面,error/404,如果模板引擎可以解析这个地址,那么就是用模板引擎解析.如果模板引擎不可用,就在静态资源文件夹下寻找errorViewName对应的页面
(2)一旦系统出现4XX或者5XX之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到/error请求,就会被BasicErrorController进行处理
二.如何定制错误响应
1.如何定制错误的页面:在我们有模板引擎的情况下(error/404.html),将错误页面命名为错误状态码.html,放在模板引擎文件夹里面的error文件夹下,发生此状态码的错误就会来到对应的页面.如果我们为每一个状态码都配置一个错误页面的话,十分麻烦,我们可以给页面命名为4xx.html,这样当错误码不匹配的时候,将默认来到该html页面(优先寻找精确的状态码).
当没有相对应的模板引擎时,将会在静态资源文件夹中寻找
当模板引擎和静态资源的错误文件夹都没有的时候,将会来到SpringBoot的默认提示信息页面.
2.如何定制错误的JSON数据
1 package com.zhiyun.springboot.web_restfulcrud.exception;
2
3 /**
4 * @author : S K Y
5 * @version :0.0.1
6 */
7 public class UserNotExistException extends RuntimeException {
8 public UserNotExistException() {
9 super("用户不存在");
10 }
11 }
1 @ResponseBody
2 @RequestMapping("/hello")
3 public String hello(@RequestParam("user") String user) {
4 if (user.equals("AAA")) {
5 throw new UserNotExistException();
6 }
7 return "hello";
8 }
三.自定义JSON错误数据响应
1.使用SpringMvc的异常处理机制(ExceptionHandler)捕获到这个异常之后,运行自定义的方法
1 package com.zhiyun.springboot.web_restfulcrud.controller;
2
3 import com.zhiyun.springboot.web_restfulcrud.entity.Employee;
4 import com.zhiyun.springboot.web_restfulcrud.exception.UserNotExistException;
5 import org.springframework.web.bind.annotation.ControllerAdvice;
6 import org.springframework.web.bind.annotation.ExceptionHandler;
7 import org.springframework.web.bind.annotation.ResponseBody;
8
9 import java.util.HashMap;
10 import java.util.Map;
11
12 /**
13 * @author : S K Y
14 * @version :0.0.1
15 */
16 //在Spring中要成为一个异常处理器,需要使用该注解
17 @ControllerAdvice
18 public class MyExceptionHandler {
19
20 @ResponseBody
21 @ExceptionHandler(UserNotExistException.class)
22 public Map handlerException(Exception e) {
23 Map map = new HashMap<>();
24 map.put("code", "user.not exist");
25 map.put("message", e.getMessage());
26 return map;
27 }
28
29 }
2.到目前为止还没有实现我们的自适应效果(如果是浏览器访问则返回页面,其他客户端访问则是返回JSON数据):
1 @ExceptionHandler(UserNotExistException.class)
2 public String handlerException(Exception e) {
3 /* Map map = new HashMap<>();
4 map.put("code", "user.not exist");
5 map.put("message", e.getMessage());*/
6 //转发到error请求
7 return "forward:/error";
8 }
3.按照上述的方法,可以实现自适应的页面响应,但是此时响应的是默认的页面 ,想要实现自适应的效果,需要传入我们自定义的状态码:
1 @ExceptionHandler(UserNotExistException.class)
2 public String handlerException(Exception e, HttpServletRequest request) {
3 //传入我们自己的错误状态码
4 request.setAttribute("javax.servlet.error.status_code",500);
5 //转发到error请求
6 return "forward:/error";
7 }
4.将我们的定制数据携带出去:
(1)出现错误以后会来到error请求,这个请求会被BasicErrorController处理,他要返回的内容是由getErrorAttributes()方法得到的,而该方式是AbstractErrorController中随规定的方法,因此我们完全可以自定义一个Controller的实现类,或者是编写AbstractErrorController的子类来完成.
(2)页面上能用的数据或者是JSON返回能用的数据都是通过errorAttributes.getErrorAttributes(requestAttributes,includeStackTrace);来得到的
容器中DefaultErrorAttributes默认来进行数据处理,我们可以扩展该类来自定义返回信息:
1 package com.zhiyun.springboot.web_restfulcrud.component;
2
3 import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
4 import org.springframework.web.context.request.RequestAttributes;
5
6 import java.util.Map;
7
8 /**
9 * @author : S K Y
10 * @version :0.0.1
11 */
12 public class MyErrorAttributes extends DefaultErrorAttributes {
13 @Override
14 public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
15 Map map = super.getErrorAttributes(requestAttributes, includeStackTrace);
16 map.put("company", "skykuqi");
17 return map;
18 }
19 }
(3)想要传递更多的信息,我们可以使用request来进行传递
1 @ExceptionHandler(UserNotExistException.class)
2 public String handlerException(Exception e, HttpServletRequest request) {
3 //传入我们自己的错误状态码
4 request.setAttribute("javax.servlet.error.status_code", 500);
5 //传递错误信息
6 Map map = new HashMap<>();
7 map.put("code", "user.not exist");
8 map.put("message", e.getMessage());
9 request.setAttribute("errorMessage", map);
10 //转发到error请求
11 return "forward:/error";
12 }
1 package com.zhiyun.springboot.web_restfulcrud.component;
2
3 import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
4 import org.springframework.web.context.request.RequestAttributes;
5
6 import java.util.Map;
7
8 /**
9 * @author : S K Y
10 * @version :0.0.1
11 */
12 public class MyErrorAttributes extends DefaultErrorAttributes {
13 @Override
14 public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
15 Map map = super.getErrorAttributes(requestAttributes, includeStackTrace);
16 map.put("company", "skykuqi");
17 //自定义异常处理器所携带的数据
18 Map errorMap = (Map) requestAttributes.getAttribute("errorMessage", RequestAttributes.SCOPE_REQUEST);
19 map.put("errorMessage", errorMap);
20 return map;
21 }
22 }
--最终的效果:我们可以定制ErrorAttributes来进行自定义错误信息的响应