Salmon的全栈知识 Salmon的全栈知识
首页
  • JavaSE
  • JavaWeb
  • Spring生态
  • JUC
  • JVM
  • Netty
  • Java各版本特性
  • 23种设计模式
  • Maven
  • Java常用框架
  • Dubbo
  • OpenFeign
  • Nacos
  • Zookeeper
  • Sentinel
  • Seata
  • SpringCloud Gateway
  • Apollo
  • Eureka
  • Go基础
  • Gin
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
    • ElasticSearch
  • 消息中间件

    • RabbitMQ
    • RocketMQ
    • Kafka
    • ActiveMQ
    • MQTT
    • NATS
  • 网关中间件

    • Nginx
  • Linux
  • Docker
  • Git
  • K8s
  • Solidity
  • Java
  • 计算机网络
  • 操作系统
GitHub (opens new window)
首页
  • JavaSE
  • JavaWeb
  • Spring生态
  • JUC
  • JVM
  • Netty
  • Java各版本特性
  • 23种设计模式
  • Maven
  • Java常用框架
  • Dubbo
  • OpenFeign
  • Nacos
  • Zookeeper
  • Sentinel
  • Seata
  • SpringCloud Gateway
  • Apollo
  • Eureka
  • Go基础
  • Gin
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
    • ElasticSearch
  • 消息中间件

    • RabbitMQ
    • RocketMQ
    • Kafka
    • ActiveMQ
    • MQTT
    • NATS
  • 网关中间件

    • Nginx
  • Linux
  • Docker
  • Git
  • K8s
  • Solidity
  • Java
  • 计算机网络
  • 操作系统
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Spring框架

    • 传统Javaweb开发的困惑
    • IoC、DI和AOP思想提出
    • Spring框架的诞生
    • 基于xml的Spring应用
    • 基于注解的Spring应用
    • AOP 简介
    • 基于xml配置的AOP
    • 基于注解配置的AOP
    • 基于AOP的声明式事务控制
  • SpringMVC框架

    • Spring整合web环境
    • web层MVC框架思想与设计思路
    • SpringMVC简介
    • SpringMVC的请求处理
    • SpringMVC的响应处理
    • SpringMVC的拦截器
    • SpringMVC的全注解开发
    • SpringMVC的组件原理剖析
    • SpringMVC的异常处理机制
      • 1、SpringMVC 异常的处理流程
      • 2、SpringMVC 的异常处理方式
      • 3、异常处理机制原理剖析
      • 4、SpringMVC 常用的异常解析器
  • 《Spring生态》笔记
  • SpringMVC框架
Salmon
2025-07-23
目录

SpringMVC的异常处理机制

# 1、SpringMVC 异常的处理流程

异常分为编译时异常和运行时异常,编译时异常我们 try-cache 进行捕获,捕获后自行处理,而运行时异常是不可预期的,就需要规范编码来避免,在SpringMVC 中,不管是编译异常还是运行时异常,都可以最终由SpringMVC提供的异常处理器进行统一处理,这样就避免了随时随地捕获处理的繁琐性。

当然除了繁琐之外,我们在进行前后端分离异步开发时,往往返回统一格式的结果给客户端,例如:{"code":200,"message":"","data":{"username":"haohao","age":null}},即使报异常了,也不能把状态码500直接扔给客户端丢给用户,需要将异常转换成符合上面格式的数据响应给客户端更友好。

SpringMVC 处理异常的思路是,一路向上抛,都抛给前端控制器 DispatcherServlet ,DispatcherServlet 在调用异常处理器ExceptionResolver进行处理,如下图:

image-20250729010754169

# 2、SpringMVC 的异常处理方式

SpringMVC 提供了以下三种处理异常的方式:

  • 简单异常处理器:使用SpringMVC 内置的异常处理器处理 SimpleMappingExceptionResolver;

  • 自定义异常处理器:实现HandlerExceptionResolver接口,自定义异常进行处理;

  • 注解方式:使用@ControllerAdvice + @ExceptionHandler 来处理。

使用SimpleMappingExceptionResolver处理一些简单异常,配置开启SimpleMappingExceptionResolver,并指定异常捕获后的处理动作,当发生了异常后,会被 SimpleMappingExceptionResolver 处理,跳转到我们配置的错误页面error.html给用户进行友好展示

<!--配置简单异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
   <!-- 异常捕获后动作:展示视图 -->
   <property name="defaultErrorView" value="/error.html"/>
</bean>

可以在配置SimpleMappingExceptionResolver时,指定一些参数,例如:异常的类型

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="defaultErrorView" value="/error.html"/>
    <property name="exceptionMappings">
        <props>
            <!-- 配置异常类型对应的展示视图 -->
            <prop key="java.lang.RuntimeException">/error.html</prop>
            <prop key="java.io.FileNotFoundException">/io.html</prop>
        </props>
    </property>
</bean>

注解方式配置简单映射异常处理器

@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
    // 创建 SimpleMappingExceptionResolver
    SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
    
    // 设置默认错误展示视图
    resolver.setDefaultErrorView("/error.html");

    // 定义 Properties 设置特殊异常对应的映射视图
    Properties properties = new Properties();
    properties.setProperty("java.lang.RuntimeException", "/error.html");
    properties.setProperty("java.io.FileNotFoundException", "/io.html");

    resolver.setExceptionMappings(properties);
    return resolver;
}

自定义异常处理器,实现HandlerExceptionResolver接口自定义异常处理器,可以完成异常逻辑的处理

public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler, Exception ex) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/error.html");
        return modelAndView;
    }
}

交给Spring管理异常处理器

<bean class="com.itheima.exception.MyHandlerExceptionResolver"></bean>

自定义异常处理器,返回Json格式字符串信息

@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception ex) {
        
        // 编写要返回的 JSON 格式的字符串
        String jsonStr = "{\"code\":0,\"message\":\"error\",\"data\":\"\"}";

        try {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(jsonStr);
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        return null;
    }
}

使用注解 @ControllerAdvice + @ExceptionHandler 配置异常,@ControllerAdvice 注解本质是一个@Component,也会被扫描到,与此同时,具备AOP功能,默认情况下对所有的Controller都进行拦截操作,拦截后干什么呢?就需要在结合@ExceptionHandler、@InitBinder、@ModelAttribute 注解一起使用了,此处我们讲解的是异常,所以是@ControllerAdvice + @ExceptionHandler的组合形式。

编写全局异常处理器类,使用@ControllerAdvice标注,且@ExceptionHandler指定异常类型

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ModelAndView runtimeHandleException(RuntimeException e) {
        System.out.println("全局异常处理器执行...." + e);
        return new ModelAndView("/error.html");
    }

    @ExceptionHandler(IOException.class)
    @ResponseBody
    public ResultInfo ioHandleException(IOException e) {
        // 模拟一个ResultInfo
        return new ResultInfo(0, "IOException", null);
    }
}

如果全局异常处理器响应的数据都是Json格式的字符串的话,可以使用@RestControllerAdvice替代@ControllerAdvice 和 @ResponseBody

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResultInfo runtimeHandleException(RuntimeException e){
        return new ResultInfo(0, "RuntimeException", null);
    }

    @ExceptionHandler(IOException.class)
    public ResultInfo ioHandleException(IOException e){
        return new ResultInfo(0, "IOException", null);
    }
}

# 3、异常处理机制原理剖析

初始化加载的处理器异常解析器,SpringMVC 的前置控制器在进行初始化的时候,会初始化处理器异常解析器HandlerExceptionResolver

//初始化处理器异常解析器
this.initHandlerExceptionResolvers(context);
private void initHandlerExceptionResolvers(ApplicationContext context) {
    // 从容器中获得所有 HandlerExceptionResolver 类型的 Bean(包括祖先上下文)
    Map<String, HandlerExceptionResolver> matchingBeans =
        BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context,
            HandlerExceptionResolver.class,
            true,
            false
        );

    // 如果当前没有配置 handlerExceptionResolvers,就加载默认策略
    if (this.handlerExceptionResolvers == null) {
        // 从 dispatcherServlet.properties 中加载默认的 HandlerExceptionResolver 实现类
        this.handlerExceptionResolvers = this.getDefaultStrategies(
            context,
            HandlerExceptionResolver.class
        );
    }
}

加载DispatcherServlet.properties中默认的异常处理器

org.springframework.web.servlet.HandlerExceptionResolver=\
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

配置了自定义的异常处理器后,默认的异常处理器就不会被加载,当配置 <mvc:annotation-driven /> 或配置了注解@EnableWebMvc后,默认异常处理器和自定的处理器异常解析器都会被注册

image-20250729011335074

异常处理器加载完毕后,当发生异常时,就会进行处理,跟踪 DispatcherServlet 的 doDispatch() 方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    // 定义异常对象,用于捕获并统一处理异常
    Object dispatchException = null;

    try {
        // ... 此处省略主流程代码,例如 handler 映射、拦截器前置处理、调用 Controller 方法等 ...
    } catch (Exception e) {
        // 捕获 Exception 类型异常(如运行时异常、IO 异常等)
        dispatchException = e;
    } catch (Throwable te) {
        // 捕获更严重的 Throwable 错误(如 OutOfMemoryError),封装为 Servlet 异常
        dispatchException = new NestedServletException("Handler dispatch failed", te);
    }

    // 最终处理阶段:视图渲染、拦截器 afterCompletion、异常处理
    this.processDispatchResult(
        processedRequest,            // 实际处理的请求(可能是原始请求或包装后的请求)
        response,                    // HTTP 响应对象
        mappedHandler,              // 当前匹配到的 Handler(包含 Handler 对象与拦截器链)
        mv,                         // 处理结果 ModelAndView
        (Exception) dispatchException // 转换为 Exception 类型的异常对象(可能为 null)
    );
}

跟踪processDispatchResult方法

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler,
                                   @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {
    boolean errorView = false; //定义错误视图标识,默认为false
    if (exception != null) {
        //判断当前捕获的异常是否是ModelAndViewDefiningException类型的异常
        if (exception instanceof ModelAndViewDefiningException) {
            //获得ModelAndViewDefiningException异常对象中的ModelAndView对象
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        } else {
            //捕获到其他异常,获得当前发生异常的Handler对象
            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
            //执行processHandlerException 方法
            mv = this.processHandlerException(request, response, handler, exception);
            //如果异常处理返回了ModelAndView 则修改错误视图的标识为true
            errorView = mv != null;
        }
    }
    // ... 省略其他代码 ...
}

跟踪processHandlerException 方法

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
                                               @Nullable Object handler, Exception ex) throws Exception {
    ModelAndView exMv = null; // 定义ModelAndView
    // 判断处理器异常解析器集合是否是空的
    if (this.handlerExceptionResolvers != null) {
        // 遍历处理器异常解析器List集合
        Iterator var6 = this.handlerExceptionResolvers.iterator();
        while (var6.hasNext()) {
            // 取出每一个异常解析器
            HandlerExceptionResolver resolver = (HandlerExceptionResolver) var6.next();
            // 执行异常解析器的resolveException方法
            exMv = resolver.resolveException(request, response, handler, ex);
            // 只要有一个异常处理器返回ModelAndView 则不再执行后面的异常处理器
            if (exMv != null) {
                break;
            }
        }
    }
    // 如果视图解析器不为null则返回,否则抛出异常
    if (exMv != null) {
        return exMv;
    } else {
        throw ex;
    }
}

# 4、SpringMVC 常用的异常解析器

SpringMVC 相关的处理器异常解析器继承体系如下:

image-20250729011556685

接口或类 说明
HandlerExceptionResolver 异常处理器类的顶级接口,实现该接口的类都会作为异常处理器使用
MyHandlerExceptionResolver 自定义异常处理器,实现了HandlerExceptionResolver接口
HandlerExceptionResolverComposite 异常解析器混合器,内部维护多个异常解析器集合,用于组合多种异常处理策略
SimpleMappingExceptionResolver 简单映射异常处理器,可通过配置文件映射异常类型与错误视图
ExceptionHandlerExceptionResolver 默认注册到Spring容器中,支持基于@ExceptionHandler注解的异常处理
DefaultHandlerExceptionResolver 默认异常处理器,所有其他异常处理器不匹配时执行
ResponseStatusExceptionResolver 结合@ResponseStatus注解使用的异常处理器,响应指定HTTP状态码
上次更新: 2025/07/28, 17:18:00
SpringMVC的组件原理剖析

← SpringMVC的组件原理剖析

Theme by Vdoing | Copyright © 2022-2025 Salmon's Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式