Skip to content

SpringBoot错误处理 #26

@4rnold

Description

@4rnold

SpringBoot错误处理

对于SpringBoot的错误处理,一般用HandlerExceptionResolver或@ExceptionHandler接收异常信息,做进一步处理。

对于没有处理的则直接返回错误页面。

HandlerExceptionResolver

可以捕获Handler中的异常,做自定义处理

@ExceptionHandler

配合@ControllerAdvice使用

ErrorPage

ErrorPageFilter BasicErrorController 什么关系

产生处理错误流程

ResourceHttpRequestHandler 拦截 /** 所有路径
在ResourceHttpRequestHandler.handleRequest 处理时 如果获取不到resource就设置responseStatus为404。

tomcat收到response为404,dispatch到/error页面。

通常有以下几种错误页面

  1. 自定义的错误页面
  2. SpringBoot的错误页面
  3. Tomcat的错误页面

ErrorPage原理

ErrorPage从哪来的呢?

/error 在Tomcat中定义

1. ErrorPageCustomizer中定义"/error" ErrorPage
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

  private final ServerProperties properties;

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

  @Override
  public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
     ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
           + this.properties.getError().getPath());//这里就是“/error”
     errorPageRegistry.addErrorPages(errorPage);
  }

  @Override
  public int getOrder() {
     return 0;
  }

}
2. ErrorMvcAutoConfiguration配置ErrorPageRegistrar(ErrorPageCustomizer)

ErrorMvcAutoConfiguration 中配置ErrorPageCustomizer

@Bean
public ErrorPageCustomizer errorPageCustomizer() {
  return new ErrorPageCustomizer(this.serverProperties);
}
3. ErrorPageRegistrarBeanPostProcessor 处理ErrorPageRegistry?

registerErrorPages 在ErrorPageRegistrarBeanPostProcessor 中被调用,注册给ErrorPageRegistry。


ErrorPageFilter是针对非嵌入容器的。

4. WebApplicationContext.onRefresh()中createWebServer()注册ErrorPage

最终在WebApplicationContext.onRefresh() 方法中要createWebServer(),创建内嵌的Tomcat。
通过TomcatEmbeddedServletContainerFactory(SpringBoot1.5有),将ErrorPage注册到context中(StandardContext)。

for (ErrorPage errorPage : getErrorPages()) {
  new TomcatErrorPage(errorPage).addToContext(context);
}

tomcat转到/error页面

上面文章介绍的很清楚。

先从StandardContext中获取到ErrorPage。

ErrorPage errorPage = context.findErrorPage(statusCode);
if (errorPage == null) {
    // Look for a default error page
    errorPage = context.findErrorPage(0);
}

根据errorPage获取到Dispatcher,然后forward到地址("/error")

RequestDispatcher rd = servletContext.getRequestDispatcher(errorPage.getLocation());
...
rd.forward(request.getRequest(), response.getResponse());

如果没有配置errorPage就是下面这样子

/error页面SpringBoot处理

产生错误,交给tomcat,tomcat dispatcher到 /error页面

response.sendError(HttpServletResponse.SC_NOT_FOUND); 只是设置状态,Exception为null,ExceptionResolver不处理。

最后被tomcat处理,转到“/error”页面。

SpringBoot自带的 /error页面

就是第二种错误页面

在ErrorMvcAutoConfiguration 中会自动配置BasicErrorController,BasicErrorController处理/error页面。BasicErrorController配置view为“error”。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    @RequestMapping(produces = "text/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);
    }
}

同样在ErrorMvcAutoConfiguration中配置了name = error的view。
resolveErrorView 用到了ErrorViewResolver,也可以自定义ErrorViewResolver。

@Configuration
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {

   private final SpelView defaultErrorView = new SpelView(
         "<html><body><h1>Whitelabel Error Page</h1>"
               + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
               + "<div id='created'>${timestamp}</div>"
               + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
               + "<div>${message}</div></body></html>");

   @Bean(name = "error")
   @ConditionalOnMissingBean(name = "error")
   public View defaultErrorView() {
      return this.defaultErrorView;
   }
}

如果不想使用Springboot自带的Error可以不去加载ErrorMvcAutoConfiguration

@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})

自定义ErrorPage的配置方式

SpringBoot自带的错误页面太丑了。可以自定义。

方式一:添加ErrorPage

老:

@Configuration
public class ErrorPageConfig {

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {
        return new EmbeddedServletContainerCustomizer() {
            public void customize(ConfigurableEmbeddedServletContainer container) {

                ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/static/html/500.html");
                ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/static/html/500.html");

                container.addErrorPages(error404Page, error500Page);
            }
        };
    }

}

新:

@Configuration
public class ContainerConfig implements ErrorPageRegistrar {
    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        ErrorPage[] errorPages = new ErrorPage[2];
        errorPages[0] = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
        errorPages[1] = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
        registry.addErrorPages(errorPages);
    }
}

方式二:重写名字为Error的View

定义view

public class GunsErrorView implements View {

    @Override
    public String getContentType() {
        return "text/html";
    }

    @Override
    public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        httpServletRequest.getRequestDispatcher("/global/error").forward(httpServletRequest, httpServletResponse);
    }
}

配置error view

@Bean("error")
public GunsErrorView error() {
    return new GunsErrorView();
}

方式三:自定义ErrorViewResolver

ErrorViewResolver在BasicErrorController中被使用。返回ModelAndView。
...

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions