从零开始学习SpringBoot

SpringBoot - Web开发 自定义错误页面处理

2018-05-23  本文已影响8人  BzCoder

在Web开发中,SpringBoot会自动帮你配置了错误页面。并且在不同端请求,会有不同的效果。那么假如我们想自定义错误页面应该如何做配置呢?

错误请求

一.错误页面配置原理

谈到原理,我们就要回到SpringBoot的自动配置类ErrorMvcAutoConfiguration去查看。

通过代码,我们看到SpringBoot配置了以下几个类:

  1. DefaultErrorAttributes
  2. BasicErrorController
  3. errorPageCustomizer
  4. DefaultErrorViewResolver

步骤:

  1. 当出现错误时,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);
    }
  1. 当为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.自定义错误页面

以下为示例代码

<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得到的。
所以我们的实现思路是:

定义一个异常事件:

@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请求

就此大功告成!

上一篇下一篇

猜你喜欢

热点阅读