SpringBoot之全局异常处理

默认情况下的异常现象

创建一个接口 (接口需要传递参数key)
@RestController
@RequestMapping("/exception")
public class ExceptionController {

    @GetMapping("/accept")
    public String acceptKey(@RequestParam("key") String key) {
        return key;
    }
}
访问链接(不传递参数key,使得抛出异常) http://localhost:8080/exception/accept
在浏览器中的现象
在 Postman 中的现象
小结

在浏览器中返回一个 html 页面,在 Postman 中返回一个 json 数据

解决方案

在默认静态资源路径下创建 error 子文件夹,并创建文件 400.html

默认静态资源路径如下:

  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/

400.html 明细如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <h1>404</h1>
</body>
</html>
**再次在浏览器下访问链接 :**http://localhost:8080/exception/accept

PS:错误码需要和 html 名字一致,或者将 html 改成 4xx、5xx,这样以 4 开头的错误码就会跳转到 4xx.html,以 5 开头的错误码就会跳转到 5xx.html

再次在 Postman 中**访问链接 :**http://localhost:8080/exception/accept

好像并没有起作用,我们再尝试其他方法

创建 GlobalExceptionHandler,处理全局异常
创建实体类 ExceptionInfo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExceptionInfo {

    private String msg;
}
创建全局异常处理配置类 GlobalExceptionHandler
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ExceptionInfo resolverException(Exception exception) {
        ExceptionInfo exceptionInfo = new ExceptionInfo();
        exceptionInfo.setMsg(exception.getMessage());
        return exceptionInfo;
    }
}
**访问链接 :**http://localhost:8080/exception/accept

源码解析

ErrorMvcAutoConfiguration

ErrorMvcAutoConfiguration 会定义一个类型为 BasicErrorController 的 Bean。BasicErrorController 中定义了两个接口方法:errorHtml、error ,其中 errorHtml 方法只对 request 的 accept 能兼容 text/html 的请求有效,error 方法则可以认为是一个兜底方法,它对 request 的 accept 没有要求

当我们的请求抛出异常,会转发到这个接口(/error) ,如果这个默认的 URI(/error) 和我们的项目有冲突,我们可以在配置文件中定义 server.error.path | error.path 来修改默认值。

StandardHostValve#custom (请求转发)

DispatcherServlet#processDispatchResult

我们需要关注两个方法 processHandlerExceptionrender

case1:访问一个不存在的接口或者不存在的文件

这种情况 exception 和 mv(ModelAndView)都为 null,所以既不会执行 processHandlerException 方法,也不会执行 render 方法,然后转发到 /error 接口。

在 Postman 中发请求,Accept 默认为 */*,在浏览器中发请求 Accept 如下所示:

根据一定的规则,在 Postman 中默认转发到 error 方法,在浏览器中默认转发到 errorHtml 方法

AbstractHandlerMethodMapping#lookupHandlerMethod (请求映射规则)

case1.1 转发到 error 方法

该方法会构建一个 map 对象,设置 timestamp、status、error、path 等信息并响应

case1.2 转发到 errorHtml 方法

默认情况下 errorViewResolvers 只有一个,类型为 DefaultErrorViewResolver,它是在 ErrorMvcAutoConfiguration 内部类 DefaultErrorViewResolverConfiguration 中定义的,相关源码如下:

DefaultErrorViewResolver#resolveErrorView

当我们访问一个不存在的链接,viewName 为 404,errorViewName 为 error/404,如果 TemplateAvailabilityProviders 的 getProvider 方法返回一个非 null 对象,则返回一个 ModelAndView 对象

TemplateAvailabilityProviders#getProvider

如果 TemplateAvailabilityProvider 的 isTemplateAvailable 方法返回 true,则返回当前 TemplateAvailabilityProvider 对象,即最终会返回一个 ModelAndView 对象

根据 SpringBoot 的自动配置,容器中存在五个 TemplateAvailabilityProvider ,我们来看一下 ThymeleafTemplateAvailabilityProvider 的 isTemplateAvailable 方法。

即默认情况下,如果我们的环境中,存在指定的类(org.thymeleaf.spring5.SpringTemplateEngine),并且资源 classpath:/templates/error/404.html 存在,则返回一个 ModelAndView 对象

可以导入下方所示的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.6.13</version>
</dependency>

DefaultErrorViewResolver#resolveResource

如果 TemplateAvailabilityProviders 的 getProvider 方法返回 null,则继续调用 resolveResource 方法。该方法会遍历 staticLocations,判断这些默认静态文件路径下是否存在相关文件(是否存在error/404.html),如果存在则构建一个 HtmlResourceView 对象,staticLocations 明细列表如下:

  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/

DefaultErrorViewResolver#resolveErrorView (2)

如果错误状态码是以 4 或 5 开头,则以 viewName 为 4xx,errorViewName 为 error/4xx,再执行一遍上述的流程

BasicErrorController#errorHtml(2)

即不管我们有没有在当前环境中找到指定文件,都会返回一个 ModelAndView 对象,如果存在以下资源,viewName 则不为 error:

  1. classpath:/templates/error/404.html => view:error/404
  2. classpath:/META-INF/resources/404.html => view:HtmlResourceView
  3. classpath:/resources/404.html => view:HtmlResourceView
  4. classpath:/static/404.html => view:HtmlResourceView
  5. classpath:/public/404.html => view:HtmlResourceView
  6. classpath:/templates/error/4xx.html => view:error/4xx
  7. classpath:/META-INF/resources/4xx.html => view:HtmlResourceView
  8. classpath:/resources/4xx.html => view:HtmlResourceView
  9. classpath:/static/4xx.html => view:HtmlResourceView
  10. classpath:/public/4xx.html => view:HtmlResourceView
  11. 其他 => view:error

DispatcherServlet#processDispatchResult(2)

第二次进入 DispatcherServlet 的 processDispatchResult 方法,此时 mv 不等于null,则进入render 方法

如果 viewName 为 error/404、error/4xx、error 则执行 resolveViewName 方法

一共有5个resolver,我们只需要关注 ContentNegotiatingViewResolver 的 resolveViewName 方法

ContentNegotiatingViewResolver 的内部属性 viewResolvers 有其他四个 resolvers,即 ContentNegotiatingViewResolver 相当于一个大管家,具体还是由其他四个 resolvers 处理,我们简要分析一下 beanNameViewResolver

ErrorMvcAutoConfiguration 的内部类 WhitelabelErrorViewConfiguration 会定义两个bean,其中一个 beanName 为 error,类型为 View,另一个 beanName 为 beanNameViewResolver,类型为 BeanNameViewResolver

即 BeanNameViewResolver 的 resolveViewName 方法会返回一个类型为 StaticView 的 View

View#render

StaticView#render

我们可以看到 StaticView 的 render 方法就是我们经常看到的页面

StaticView#render

HtmlResourceView#render

HtmlResourceView 的 render 方法就是将指定资源用流写出去

case2:访问一个存在的接口且抛出异常
DispatcherServlet#processHandlerException

一共有两个 HandlerExceptionResolver,其中一个类型为 DefaultErrorAttributes,DefaultErrorAttributes 的 resolveException 方法比较简单,主要是给 request 赋值,我们主要关注 HandlerExceptionResolverComposite 的 resolveException 方法

HandlerExceptionResolverComposite#resolveException

一共有三个 HandlerExceptionResolver,我们主要分析一下 ExceptionHandlerExceptionResolver的 resolveException 方法

ExceptionHandlerExceptionResolver#resolveException

最终会执行类上标记 @ControllerAdvice 注解,方法上标记 @ExceptionHandler 注解的符合条件的方法,就是我们在【解决方案】的 GlobalExceptionHandler#resolverException 方法

如何选择 ServletInvocableHandlerMethod

遍历 exceptionHandlerAdviceCache 对象,通过 ExceptionHandlerMethodResolver 的resolveMethod 方法,获取一个 method 对象,并将其封装成 ServletInvocableHandlerMethod 对象

isApplicableToBeanType

以下四种情况,@ControllerAdvice注解生效:

  1. 什么都没有配置
  2. Controller所在的类路径以配置的 basePackages 开头
  3. Controller类型是指定的类或是其子类
  4. Controller上含有指定注解

resolveMethod

遍历 mappedMethods 对象,如果存在多个 @ExceptionHandler 标注的方法,则选择一个优先级最高的

ExceptionHandlerExceptionResolver 的 exceptionHandlerAdviceCache 是如何赋值的

ExceptionHandlerExceptionResolver 继承 InitializingBean 接口,所以 bean 在实例化的过程中会执行其 afterPropertiesSet 方法

ExceptionHandlerExceptionResolver#afterPropertiesSet

如果 bean 上存在 @ControllerAdvice 注解,则构建一个 ControllerAdviceBean 对象

循环遍历查找出来的 adviceBeans,每存在一个 ControllerAdviceBean,则构建一个ExceptionHandlerMethodResolver 并将其 put 到 exceptionHandlerAdviceCache 中

ExceptionHandlerMethodResolver的实例化(给 mappedMethods 赋值)

如果方法存在 @ExceptionHandler 注解,则给 mappedMethods 赋值

PS : 如果 @ExceptionHandler 注解标注的方法也抛出异常,则使用 case1 做兜底

相关推荐
milk_yan2 分钟前
MinIO的安装与使用
linux·数据仓库·spring boot
等一场春雨25 分钟前
Java设计模式 八 适配器模式 (Adapter Pattern)
java·设计模式·适配器模式
一弓虽1 小时前
java基础学习——jdbc基础知识详细介绍
java·学习·jdbc·连接池
王磊鑫1 小时前
Java入门笔记(1)
java·开发语言·笔记
马剑威(威哥爱编程)1 小时前
2025春招 SpringCloud 面试题汇总
后端·spring·spring cloud
硬件人某某某1 小时前
Java基于SSM框架的社区团购系统小程序设计与实现(附源码,文档,部署)
java·开发语言·社区团购小程序·团购小程序·java社区团购小程序
程序员徐师兄1 小时前
Java 基于 SpringBoot 的校园外卖点餐平台微信小程序(附源码,部署,文档)
java·spring boot·微信小程序·校园外卖点餐·外卖点餐小程序·校园外卖点餐小程序
chengpei1472 小时前
chrome游览器JSON Formatter插件无效问题排查,FastJsonHttpMessageConverter导致Content-Type返回不正确
java·前端·chrome·spring boot·json
Quantum&Coder2 小时前
Objective-C语言的计算机基础
开发语言·后端·golang
五味香2 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin