Spring Boot1.x web错误处理

前言

对于web开发,要保证良好的用户体验和站点安全,需要对错误情况进行处理。虽然文档可能告诉你一部分细节操作,但如果不充分理解整个错误处理的流程,操作起来犹如盲人摸象。本文按照Servlet API、Spring Boot Web支持、Spring MVC的从底到上的逐层分析在使用Spring Boot时错误的处理流程,可能缺少一些操作细节,但了解了本质,在解决问题累计经验就不是什么难事了。

最底层Servlet错误处理

Servlet API提供了errorpage的错误处理机制,提供了基于status code/exception type到resource的映射的错误处理方式。

<error-page>
    <error-code>404</error-code>
    <location>/pageNotFound</location>
</error-page>

Spring Boot的web部分基于Servlet API,自然而然,Servlet提供的异常机制是Spring Boot中web错误处理的基础。

但问题是现代Java Web开发早已抛弃了Servlet API的大部分功能,包括使用web.xml,而且servlet3.0也没有提供相关的Java接口来提供该功能

错误状态码的补充

虽然我们习惯性的认为HTTP 4XX表示用户错误,HTTP 5XX表示服务器错误,但是对于Servlet API而言,是否需要作为错误情况处理不取决于其值,而是通过HttpServletResponse.sendError方法来发起的,可选参数message,并不是HTTP协议的部分,只是方便视图层使用。

public void sendError(int sc, String msg) throws 
IOException;

如果我们直接返回内容,如:

return new ResponseEntity(HttpStatus.NOT_FOUND);

其并不会触发错误处理机制。

中层的Spring Boot Web错误处理

由于Servlet缺乏相关的Java API来满足该功能,故Spring Boot提供了对其的支持。同样支持对status code/exception进行错误处理,并同样采取转发到目标资源的方式。

处理机制

Spring Boot Web错误处理机制是独立于MVC框架的,它提供了两个抽象类ErrorPageRegistrarErrorPageRegistry,和一个模型类ErrorPage。前两个抽象接口分别表示错误页面注册者和错误页面注册表。而ErrorPage封装了一个错误页面的映射信息。

嵌入式Servlet容器

对于jar包和war包,Spring Boot分别提供了两种方式来对以上定义的错误页面进行处理。当采取嵌入式Servlet容器时,容器工厂实现了错误页面注册表:

public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry {
}

容器工厂在初始化时会注册配置的ErrorPages,其在创建嵌入式Servlet容器时会进行错误页面的配置,这往往依赖于具体的底层实现,如Tomcat会调用其相关API转换并注册为其原生的ErrorPage实现。以Tomcat为例:

TomcatEmbeddedServletContainerFactory:

protected void configureContext(Context context,
      ServletContextInitializer[] initializers) {
   //...
   for (ErrorPage errorPage : getErrorPages()) {
      new TomcatErrorPage(errorPage).addToContext(context);
   }
   //...
}

TomcatErrorPage:

public void addToContext(Context context) {
   Assert.state(this.nativePage != null,
         "Neither Tomcat 7 nor 8 detected so no native error page exists");
   if (ClassUtils.isPresent(ERROR_PAGE_CLASS, null)) {
      org.apache.tomcat.util.descriptor.web.ErrorPage errorPage = (org.apache.tomcat.util.descriptor.web.ErrorPage) this.nativePage;
      errorPage.setLocation(this.location);
      errorPage.setErrorCode(this.errorCode);
      errorPage.setExceptionType(this.exceptionType);
      context.addErrorPage(errorPage);
   }
   else {
      callMethod(this.nativePage, "setLocation", this.location, String.class);
      callMethod(this.nativePage, "setErrorCode", this.errorCode, int.class);
      callMethod(this.nativePage, "setExceptionType", this.exceptionType,
            String.class);
      callMethod(context, "addErrorPage", this.nativePage,
            this.nativePage.getClass());//注册为容器原生的错误页面
   }
}

这其实是容器对前面提到servlet-errorpage的底层实现,只是Spring Boot对其封装了一层,还是通过容器对Servlet API的支持来实现错误处理。

war包

而如果是独立部署的war包,Spring Boot则使用Filter来实现。具体为ErrorPageFilter:

public class ErrorPageFilter implements Filter, ErrorPageRegistry {
//...
}

spring mvc-start

以上只是介绍了Spring Boot Web提供的类Servlet错误处理机制。对于Spring MVC,Spring Boot提供了具体的实现,可以在ErrorMvcAutoConfiguration类找到。

下面简单的介绍相关配置信息,详细的可以参考Spring Boot的文档以及该配置文件源码。

ErrorMvcAutoConfiguration.ErrorPageCustomizer类

默认情况下注册了路径为/error的全局ErrorPage(全局意味则处理所有未匹配的情况)。

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

BasicErrorController

默认的ErrorController,会匹配/error路径的请求,支持内容协商。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")//默认为“/error”
public class BasicErrorController extends AbstractErrorController {
    //...
  @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {
        //...
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    //...
    }
}

最上层SpringMVC Web错误处理

理论上Spring Boot支持多种MVC框架,但一般采用SpringMVC。Spring MVC只提供了异常处理。

具体的细节,参考对SpringMVC异常处理机制的分析。这里不重复叙述。

总结

servlet-errorpage提供了对错误状态码和异常的处理。错误状态码的处理通过相关API的触发,逻辑实现由servlet容器提供。

由于Servlet 3.0缺乏errorpage的Java API,Spring Boot自己封装了一套。对于嵌入式Servlet容器,委托给Servlet容器对errorpage的底层支持;对于war包,通过Servlet Filter来实现。

Spring Boot对错误的默认处理机制是转发给映射到/error的Controller来处理。

Spring MVC本身在Controller层的前端又提供了异常处理机制。

参考:
spring boot-27.1.9 Error Handling

猜你喜欢

转载自www.cnblogs.com/redreampt/p/10448075.html