分类
Java

springboot如何让404错误统一走自定义异常处理流程

springboot如何让404错误统一走自定义异常处理流程

最近在springboot开发的一个项目中,发现了一个奇怪的问题。

发现随便访问一个不存在的接口,总是能找到一个对应的hander,并没有进入预期中的404处理流程。

为了解决统一异常处理的问题,仔细梳理了一次spring mvc的 DispatcherServlet 的处理流程。让我觉得好奇和感兴趣的是,在debug的过程中,spring boot在默认情况下,为我们准备了7个HandlerMapping,而单独使用springmvc XML配置注解驱动下的spring mvc提供的默认HandlerMapping只有三个。

XML配置中的注解驱动

熟悉spring容器的初始化流程的同学们可能知道,spring 容器在解析XML配置文件的阶段会处理自定义的名字空间,例如 mvc: ,mvc这个名字空间就是在该阶段处理。

在该阶段,spring读取每个包 META-INF 下的 spring.schemas 和 spring.handlers 文件,这些文件映射了名字空间和处理该名字空间的处理器。spring实例化这些处理器,并找到处理对应名字空间的处理器,运行init方法。

现在回到本文的配角:mvc。 该名字空间的处理器是 MvcNamespaceHandler 。MvcNamespaceHandler 的 init方法中,向spring 容器中注册了各种各样的 BeanDefinitionParser实例,这些实例将解析各自的mvc名字空间下的元素。其中,注解驱动 mvc:annotation-driven 节点的annotation-driven 元素由 AnnotationDrivenBeanDefinitionParser 解析器解析 。

AnnotationDrivenBeanDefinitionParser 解析器在解析 annotation-driven 元素时被调用。在解析阶段,会向spring容器中注册一些默认的bean。例如大家熟悉的HandlerMapping 、HandlerAdapter等。在这里,将会向spring 容器注册这些bean的beanDefinition,容器在接下来的流程中会实例化bean并注册到容器中。下面是默认的3个handlerMapping。

spring boot下的handlerMapping

spring boot为了减少开发者的配置工作量,提供了各种各样的自动配置类,官方的自动配置类都在spring-boot-autoconfigure 包下。

spring boot是在什么时候、怎么做到自动配置的呢?

我们注意到 EnableAutoConfiguration 注解上有个@Import注解,该注解相当于XML配置中的 引入其它配置文件。

@Import注解中指定了 选择器 AutoConfigurationImportSelector ,该选择器的作用是在 selectImports() 方法中从spring.factories 文件中获取 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 指定的所有类。然后把这些类都加入到了容器中。

另外,这些类有个共同特点:它们都有个 @Configuration 注解。Spring中对Configuration类的解析是通过 ConfigurationClassPostProcessor 进行的。具体的过程大家可以自行查看代码。总之,@Configuration 注解标识的类中所有的@Bean的类又这样实例化并放到了容器中。

我们花了一些篇幅介绍 EnableAutoConfiguration 的原理,那么有什么用呢?

回到 spring.factories 文件中,我们在 org.springframework.boot.autoconfigure.EnableAutoConfiguration 中发现了关键类:WebMvcAutoConfiguration 。

说到这里,我们看下spring boot启动下,默认情况下,DispatcherServlet中的HandlerMapping有哪些。通过debug,可以看到spring boot默认给我们准备了7个HandlerMapping。

下面简单说明下前5个HandlerMapping的作用和匹配的URL。

(1)第一个SimpleUrlHandlerMapping

该bean在 WebMvcAutoConfiguration 的内部配置类 FaviconConfiguration 中声明。它匹配的URL路径是**/favicon.ico,作用是响应浏览器获取收藏夹中的icon文件。

(2)RequestMappingHandlerMapping

该bean在 WebMvcAutoConfiguration 的内部配置类 EnableWebMvcConfiguration 中声明。它和XML的注解驱动一样,匹配在Controller中@RequestMapping注解指定的URL。

(3)WelcomePageHandlerMapping

该bean在 WebMvcAutoConfiguration 的内部配置类 WebMvcAutoConfigurationAdapter 中声明。给URL路径是/或者/index映射到欢迎页面。

(4)BeanNameUrlHandlerMapping

bean的名称(也就是beanID)作为URL,由匹配该URL的Controller处理业务。Controller必须继承AbstractController。基本不会在工作中使用这个玩意。

(5)第二个SimpleUrlHandlerMapping

该bean在WebMvcAutoConfiguration 的内部配置类 EnableWebMvcConfiguration 的父类 WebMvcConfigurationSupport 中声明。

@Bean
public HandlerMapping resourceHandlerMapping() {}

从该bean的名称,可以知道它是专门用于映射资源文件的handlerMapping。看下它的URL与handler的映射关系

可以看到,其中有一个/**,所有匹配不到的接口请求将由ResourceHttpRequestHandler处理、获取并返回。而ResourceHttpRequestHandler内部将会查找对应的静态资源,如果查不到将直接response.sendError()最终重定向到/error视图,最终交给springboot默认的/error控制器处理。

默认的/error控制器对应rest请求会返回一个json格式的数据,而普通的请求会基于HTTP错误状态码去error/下查找对应状态码的视图。

如何让404请求统一走自定义异常处理流程

通过上述分析,可以知道,对于默认的springboot配置,不存在的接口会走入/error流程,最根本的原因是配置了处理/**的

@Bean
public HandlerMapping resourceHandlerMapping() {}

基于此,我们首先先去除这个配置。

在org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers中加入了/**的配置。

我们通过在spring的配置文件中加入

spring.resources.addMappings=false

表示不加入默认的静态资源路径处理。

这样再次访问一个不存在的接口,我们会看到DispatchServlet中会返回null了,进入noHandlerFound()。

默认配置下,找不到hander,直接sendError()。我们期望让它抛出异常,已便在后面的异常处理器中处理。

在spring的配置文件中加入

spring.mvc.throw-exception-if-no-handler-found=true

让其抛出异常,这样我们就能处理这个异常了。

小结

springboot确实简单方便,你完全不需要任何配置就能让springmvc运转起来。但也正是因为简单而让你失去了内部的细节,因此你在使用前强烈建议必须要十分清楚底层的原理,否则可能会发生很多超乎你预期的结果。

由@不迷失

小白懂编程站长