SpringBoot - Web开发 自定义错误页面处理
在Web开发中,SpringBoot会自动帮你配置了错误页面。并且在不同端请求,会有不同的效果。那么假如我们想自定义错误页面应该如何做配置呢?
错误请求一.错误页面配置原理
谈到原理,我们就要回到SpringBoot的自动配置类ErrorMvcAutoConfiguration去查看。
通过代码,我们看到SpringBoot配置了以下几个类:
- DefaultErrorAttributes
- BasicErrorController
- errorPageCustomizer
- DefaultErrorViewResolver
步骤:
- 当出现错误时,errorPageCustomizer就会生效,并来到/error请求,由BasicErrorController来处理
//errorPageCustomizer
@Value("${error.path:/error}")
private String path = "/error";
BasicErrorController是一个Controller,其中里面有两种处理方法,一种是HTML形式,一种是JSON格式。其中我们的页面信息可以从getErrorAttributes从获取。DefaultErrorAttributes是ErrorAttributes的实现类。
@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<>(body, status);
}
- 当为HTML模式时,就会构建一个resolveErrorView类,而resolverErrorView继续调用ErrorViewResolver。
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
3.在我们没有做自定义配置时,ErrorViewResolver就会指向DefaultErrorViewResolver。
static {
//可以用4xx,5xx文件名来统一匹配错误,但是会以精确优先的原则
Map<Series, String> views = new EnumMap<>(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
@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) {
//将错误代码拼接到error后
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
//如果模版引擎可用就让模版引擎进行解析如:Template/error/404
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
//如果模版引擎不可用,就在静态资源文件夹下找资源文件,error/404
return resolveResource(errorViewName, model);
}
4.自定义错误页面
- 查看DefaultErrorAttributes我们可以获取到的参数。
- timestamp - The time that the errors were extracted
- status - The status code
- error - The error reason
- exception - The class name of the root exception (if configured)
- message - The exception message
- errors - Any {@link ObjectError}s from a {@link BindingResult} exception
- trace - The exception stack trace
- path - The URL path when the exception was raised
以下为示例代码
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h1>status:[[${status}]]</h1>
<h2>timestamp:[[${timestamp}]]</h2>
</main>
二.自定义错误页面
根据上述原理,只要将我们自定义的错误页面按照错误码放入相应路径。优先会取模版引擎的错误界面(template/error/404,template/error/4xx),其次取静态资源文件夹下(static/error/404,template/error/4xx,public/error/404,template/error/4xx),如果前两者都没有,再取SpringBoot的默认页面。
三.自定义错误返回
自定义错误页面只针对TEXT_HTML的方式,要自定义类似于SpringBoot自动配置(网页访问返回错误页面,移动端请求返回错误JSON串)的返回我们应该如何做呢?
流程:
出现错误之后,先将页面转发到/error,它就会被BasicErrorController处理,而其中数据是由getErrorAttributes得到的。
所以我们的实现思路是:
- 编写一个ErrorController的实现类,放在容器中。
- 页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;重写DefaultErrorAttributes 进行数据处理。
定义一个异常事件:
@Controller
public class TestController {
@ResponseBody
@RequestMapping("/hello")
public String hello(@RequestParam("user") String user)
{
if (user.equals("aaa"))
{
throw new UserNotExistException();
}
return "Hello";
}
}
自定义一个异常类:
public class UserNotExistException extends RuntimeException{
public UserNotExistException() {
super("用户不存在");
}
}
添加一个统一异常处理类:
/**
* @author BaoZhou
* @date 2018/5/22
*/
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
public String handlerException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
//定义错误码,否则默认为200
request.setAttribute("javax.servlet.error.status_code",400);
map.put("code", "user.notexist");
map.put("message", "用户出错");
//将自定义map放入Attribute中
request.setAttribute("ext",map);
//重定向到error页面
return "forward:error";
}
}
自定义ErrorAttributes:
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
/**
* 重写getErrorAttributes方法,将自己需要的错误讯息放入到map中,以便错误时能在map中获取
* 其中webRequest.getAttribute(para,para2))函数第二项表示从何处取数据
* int SCOPE_REQUEST = 0;
* int SCOPE_SESSION = 1;
*/
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String,Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("user_name","baozhou");
map.put("ext",webRequest.getAttribute("ext",0));
return map;
}
访问网页
POSTMAN请求
就此大功告成!