SpringBoot定制错误页面及原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhengzhaoyang122/article/details/84451647

一、SpringBoot默认的错误处理机制


1)、浏览器返回的默认错误页面如下:
        
   ☞ 浏览器发送请求的请求头信息如下:text/html会在后面的源码分析中说到。
      
2)、如果是其他客户端,默认则响应错误的JSON字符串,如下所示:
      
   ☞ 其他客户端发送请求的请求头信息如下:" */* "源码中解释。
      

二、原理分析:参照ErrorMvcAutoConfiguration类:错误处理的自动配置类,以下4项为此类的重要信息。


1)、ErrorMvcAutoConfiguration.ErrorPageCustomizer:当系统出现4xx或者5xx之类的错误时,ErrorPageCustomizer就会生效(定制错误的响应规则),根据如下源码可知,将会来到/error请求。

    @Bean
    public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
        return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties);
    }

    //进入ErrorPageCustomizer方法,发现registerErrorPages方法:注册一个错误也
    private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
        private final ServerProperties properties;

        protected ErrorPageCustomizer(ServerProperties properties) {
            this.properties = properties;
        }

        public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
            ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + 
                                    this.properties.getError().getPath());
            errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
        }
    }

    //进入this.properties.getError().getPath()方法,获取如下信息,得到/error请求。
    @Value("${error.path:/error}")
    private String path = "/error";//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)

2)、BasicErrorController处理/error错误请求:注意:text/html和*/*就是在此处生效。

    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorController.class},
        search = SearchStrategy.CURRENT
    )
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
    }

    //进入BasicErrorController对象,获取如下信息
    @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(
            produces = {"text/html"}//产生html类型的数据;浏览器发送的请求来到这个方法处理
        )
        public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
            HttpStatus status = this.getStatus(request);
            Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
            response.setStatus(status.value());

            //去哪个页面作为错误页面;包含页面地址和页面内容
            ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
            return modelAndView != null?modelAndView:new ModelAndView("error", model);
        }

        @RequestMapping
        @ResponseBody//产生json数据,其他客户端来到这个方法处理;
        public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
            Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
            HttpStatus status = this.getStatus(request);
            return new ResponseEntity(body, status);
        }

☞ 如上代码中提到的错误页面解析代码,进入此方法: this.resolveErrorView(request, response, status, model);

    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        Iterator var5 = this.errorViewResolvers.iterator();
        ModelAndView modelAndView;
        do {
            //从所有的ErrorViewResolver得到ModelAndView
            if(!var5.hasNext()) {
                return null;
            }

            ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
            modelAndView = resolver.resolveErrorView(request, status, model);
        } while(modelAndView == null);

        return modelAndView;
    }

3)、最总的响应页面是由DefaultErrorViewResolver解析得到的:最重要的信息是,SpringBoot默认模板引擎的/error目录下获取‘status’.xml错误页面,也可以通过4xx.xml来统配404.xml和400.xml等等,但是优先获取精准的页面。如果模板引擎中不存在,则会从静态页面中获取错误页面。否则返回系统默认错误页面。

     @Bean
     @ConditionalOnBean({DispatcherServlet.class})
     @ConditionalOnMissingBean
     public DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
        }

    //进入DefaultErrorViewResolver类中
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = this.resolve(String.valueOf(status), model);
        if(modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {

            //调用时viewname = status ***重要
            modelAndView = this.resolve((String)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);

        //模板引擎可用的情况下返回到errorViewName指定的视图地址,
        //当模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
        return provider != null?new ModelAndView(errorViewName, model):this.resolveResource(errorViewName, model);
    }

4)、DefaultErrorAttributes:在页面添加错误信息,供我们使用。

    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes();
    }

    //进入DefaultErrorAttributes类中,发现此方法给视图中添加了status状态等信息,供我们使用。
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, requestAttributes);
        this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
        this.addPath(errorAttributes, requestAttributes);
        return errorAttributes;
    }

三、定制错误JSON数据


1)、自定义异常处理类,返回定制的JSON数据。通过上述的分析,我们得知:①、可以完全编写一个ErrorController的实现类,或者继承AbstractErrorController的子类,放入容器中。②、也可以自定义异常处理类,返回JSON数据。③、页面上的数据或JSON返回的数据都是可以通过errorAttributes.getErrorAttributes得到的。我们可以自定义属于自己的ErrorAttributes。

//首先我们可以通过自定义异常处理,来确定返回的数据,但这个不够灵活,我们可以与③结合使用
/**
 * @RequestMapping启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping
 * 注解的方法上。
 */
@ControllerAdvice
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String,Object> handlerException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<String,Object>();
        request.setAttribute("javax.servlet.error.status_code","500");
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return map;
    }
}

//③自定义ErrorAttributes,一定要加入容器
@Component
public class MyErrorAttributes extends DefaultErrorAttributes{
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        //获取默认的配置,在此基础上添加自己的需求
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        //自定义自己需要的属性
        map.put("company","yintong");

        //获取我们在异常处理类中添加的信息,
        /*注意:当我们需要结合使用的时候异常处理必须return "forward:/error";将请求转发出去,不能直接返回map对象,
        同时要去掉@responseBody注解,否则ErrorAttributes不生效*/
        map.put("ext",requestAttributes.getAttribute("ext",requestAttributes.SCOPE_REQUEST));
        return map;
    }
}

2)、效果展示:
  

猜你喜欢

转载自blog.csdn.net/zhengzhaoyang122/article/details/84451647
今日推荐