1. 错误处理机制
1.1. 默认处理
默认返回如下页面:
若是其他客服端访问,那么返回的是JSON数据:
无论是发生什么错误,SpringBoot都会返回一个状态码以及一个错误页面
默认的发生错误,它会将请求转发到BasicErrorController控制器来处理请求,下面是该controller类的源码:
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
}
它有两个RequestMapping方法来映射错误请求,为什么会是两个呢?
其实errorHtml方法映射的是浏览器发送来的请求,而error方法映射的是不是浏览器而是其他软件app客户端发送的错误请求。
1.2. 处理的原理
可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;
给容器中添加了以下组件
1、DefaultErrorAttributes:
帮我们在页面共享信息;
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
2、BasicErrorController:处理默认/error请求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理
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 ? new ModelAndView("error", model) : modelAndView);
}
@RequestMapping
@ResponseBody //产生json数据,其他客户端来到这个方法处理;
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<Map<String, Object>>(body, status);
}
3、ErrorPageCustomizer:
@Value("${error.path:/error}")
private String path = "/error"; 系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
4、DefaultErrorViewResolver:
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), 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) {
//默认SpringBoot可以去找到一个页面? error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析这个页面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//模板引擎可用的情况下返回到errorViewName指定的视图地址
return new ModelAndView(errorViewName, model);
}
//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
return resolveResource(errorViewName, model);
}
1.3. 定制错误的页面
1.3.1. HTML页面
我们是否可以自己定义404或者500的错误页面返回给客户端呢?
当然可以,我们可以在src/main/resources路径下新建文件夹reources/error文件夹,然后新建404.html和500.html然后编写自己的错误内容即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404页面</title>
</head>
<body>
Hello,这是自定义的404错误页面
</body>
</html>
但是在SpringBoot2.2.x测试中,将error文件夹位于resource/template中都不生效,而只有放到static中才生效。
1.3.2. JSON数据
直接返回HTML页面还是比较容易的,但是我们如何才能返回一个json呢?
1、编写一个exception类继承RuntimeException类
package com.misiai.exception;
public class UserNotExitsException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String id;
public UserNotExitsException(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
2、编写一个handler类处理controller层抛出的异常
package com.misiai.controller;
import com.misiai.exception.UserNotExitsException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExitsException.class)
@ResponseBody
public Map<String, Object> handleException(UserNotExitsException e) {
Map<String, Object> map = new HashMap<>();
map.put("code", "user.notExist.exception");
map.put("msg", "用户不存在");
map.put("trace", e.getMessage());
return map;
}
}
这个类加上@ControllerAdvice注解将会处理controller层抛出的对应的异常,这里我们处理controller抛出的UserNotExistException自定义异常,并且将错误信息以及用户id以json串的格式返回给客户。
接着,我们在controller的请求方法中抛出这个异常,会看到在浏览器中的异常是我们自定义的异常返回的json数据。
3、Controller层代码
package com.misiai.controller;
import com.misiai.exception.UserNotExitsException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@ResponseBody
@GetMapping("/h")
public String h() {
throw new UserNotExitsException("1");
}
}
问题:
浏览器和客户端都是返回的json数据!
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExitsException.class)
// @ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//更改错误状态码
public String handleException(UserNotExitsException e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
request.setAttribute("javax.servlet.error.status_code", 500);
map.put("code", "user.notExist.exception");
map.put("msg", "用户不存在");
map.put("trace", e.getMessage());
return "forward:/error";
}
这种还是有一些问题。
第三种
继承默认的DefaultErrorAttributes
,重写getErrorAttributes
方法,然后通过request域,把自定义的数据放进去。
MyErrorAttributes
package com.misiai.componets;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("compony", "wudao");
Map<String, Object> map2 = (Map<String, Object>) webRequest.getAttribute("ext", 0);
map.put("map2", map2);
return map;
}
}
MyExceptionHandler
package com.misiai.controller;
import com.misiai.exception.UserNotExitsException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExitsException.class)
// @ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//更改错误状态码
public String handleException(UserNotExitsException e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
request.setAttribute("javax.servlet.error.status_code", 500);
map.put("code", "user.notExist.exception");
map.put("msg", "用户不存在");
map.put("trace", e.getMessage());
request.setAttribute("ext", map);
return "forward:/error";
}
}