资讯动态

Spring MVC 异常处理全链路:从抛出到响应

发布时间:2026/10/21 11:22:26 来源:尧图企业网站定制
目录引言一个异常在 Spring MVC 中经历了什么第一章异常是在哪里被“抓住”的1.1 doDispatch 中的 try-catch1.2 processDispatchResult异常处理的总入口1.3 processHandlerException遍历异常解析器1.4 handlerExceptionResolvers 列表是从哪里来的第二章ExceptionHandlerExceptionResolver 是如何介入的2.1 为什么是它最先被调用2.2 resolveException → doResolveHandlerMethodException2.3 getExceptionHandlerMethod查找能处理当前异常的方法2.4 查找细节2.4.1 advice.isApplicableToBeanType()适用范围过滤2.4.2 resolver.resolveMethod(exception)异常类型层级匹配第三章exceptionHandlerAdviceCache 是从哪里来的3.1 启动时已经扫描好了3.2 缓存的数据结构3.3 exceptionHandlerCache 是懒加载的第四章找到方法后怎么执行4.1 包装成 ServletInvocableHandlerMethod4.2 执行“三连击”4.3 processDispatchResult 收尾第五章完整时序图 关键断点坐标5.1 从启动扫描到运行时执行的完整流程5.2 核心设计思想5.3 优先级总结5.4 关键断点坐标引言一个异常在 Spring MVC 中经历了什么假设你的 Controller 中有这样一个方法RestControllerpublicclassUserController{GetMapping(/user/{id})publicUsergetUser(PathVariableLongid){UseruseruserService.getById(id);if(usernull){thrownewUserNotFoundException(用户不存在: id);}returnuser;}}同时你定义了一个全局异常处理器RestControllerAdvicepublicclassGlobalExceptionHandler{ExceptionHandler(UserNotFoundException.class)ResponseStatus(HttpStatus.NOT_FOUND)publicErrorResponsehandleUserNotFound(UserNotFoundExceptione){returnnewErrorResponse(404,e.getMessage());}}当请求/user/999时UserNotFoundException被抛出。这个异常是怎么被 Spring 捕获的怎么找到GlobalExceptionHandler中对应的方法处理方法的返回值又是怎么变成 JSON 返回给前端的本文将从源码角度还原这个完整过程。第一章异常是在哪里被“抓住”的1.1doDispatch中的try-catch在DispatcherServlet.doDispatch()中整个请求处理被包裹在一个try-catch块里protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{HttpServletRequestprocessedRequestrequest;HandlerExecutionChainmappedHandlernull;ModelAndViewmvnull;ExceptiondispatchExceptionnull;// ← 异常暂存变量try{// 路由匹配、参数解析、反射调用 Controller...mvha.handle(processedRequest,response,mappedHandler.getHandler());}catch(Exceptionex){dispatchExceptionex;// ← 异常被捕获存入变量}// 无论是否发生异常最终都进入 processDispatchResultprocessDispatchResult(processedRequest,response,mappedHandler,mv,dispatchException);}关键点异常不会被立即处理而是赋值给dispatchException暂存起来。doDispatch不会把异常往外抛而是继续执行processDispatchResult让收尾环节统一处理。1.2processDispatchResult异常处理的总入口privatevoidprocessDispatchResult(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,ModelAndViewmv,Exceptionexception)throwsException{booleanerrorViewfalse;if(exception!null){// 有异常进入异常处理分支Objecthandler(mappedHandler!null?mappedHandler.getHandler():null);mvprocessHandlerException(request,response,handler,exception);errorView(mv!null);}// ... 后续视图渲染或 JSON 输出}1.3processHandlerException遍历异常解析器processHandlerException是异常处理的真正入口protectedModelAndViewprocessHandlerException(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{ModelAndViewexMvnull;// 遍历所有已注册的 HandlerExceptionResolverfor(HandlerExceptionResolverresolver:this.handlerExceptionResolvers){exMvresolver.resolveException(request,response,handler,ex);if(exMv!null){break;// 第一个能处理的 Resolver 返回结果后立即停止}}if(exMv!null){returnexMv;}// 没有任何 Resolver 能处理将异常重新抛出throwex;}这里出现了一个关键的列表handlerExceptionResolvers。那么这个列表是从哪里来的1.4handlerExceptionResolvers列表是从哪里来的DispatcherServlet在启动时的initStrategies()阶段会调用initHandlerExceptionResolvers()来初始化这个列表privatevoidinitHandlerExceptionResolvers(ApplicationContextcontext){this.handlerExceptionResolversnull;// 从 Spring 容器中找出所有 HandlerExceptionResolver 类型的 BeanMapString,HandlerExceptionResolvermatchingBeansBeanFactoryUtils.beansOfTypeIncludingAncestors(context,HandlerExceptionResolver.class,true,false);if(!matchingBeans.isEmpty()){this.handlerExceptionResolversnewArrayList(matchingBeans.values());// 按 Order 排序AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}在 Spring Boot 的默认自动配置中这个列表包含三个实现类顺序实现类处理目标1ExceptionHandlerExceptionResolverControllerAdviceExceptionHandler2ResponseStatusExceptionResolver异常类上的ResponseStatus注解3DefaultHandlerExceptionResolverSpring 内置标准异常ExceptionHandlerExceptionResolver的Order值最小0所以它会最先被尝试。第二章ExceptionHandlerExceptionResolver是如何介入的2.1 为什么是它最先被调用ExceptionHandlerExceptionResolver实现了Ordered接口getOrder()返回0。而其他两个 Resolver 的Order值更大所以它排在列表的最前面在processHandlerException遍历时第一个被执行。2.2resolveException→doResolveHandlerMethodException当遍历到ExceptionHandlerExceptionResolver时会调用它的resolveException方法最终进入doResolveHandlerMethodExceptionprotectedModelAndViewdoResolveHandlerMethodException(HttpServletRequestrequest,HttpServletResponseresponse,HandlerMethodhandlerMethod,Exceptionexception){// 1. 查找能处理当前异常的 ExceptionHandler 方法ServletInvocableHandlerMethodexceptionHandlerMethodgetExceptionHandlerMethod(handlerMethod,exception);if(exceptionHandlerMethodnull){returnnull;// 没找到交给下一个 Resolver}// 2. 设置参数解析器、返回值处理器、数据绑定工厂// 复用普通 Controller 的同一套工具exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);exceptionHandlerMethod.setDataBinderFactory(getDataBinderFactory(handlerMethod));// 3. 创建 ModelAndView 容器ModelAndViewContainermavContainernewModelAndViewContainer();// 4. 执行异常处理方法exceptionHandlerMethod.invokeAndHandle(webRequest,mavContainer,exception,...);// 5. 返回 ModelAndView可能为 nullreturngetModelAndView(mavContainer,modelFactory,webRequest);}2.3getExceptionHandlerMethod查找能处理当前异常的方法查找逻辑分为两个阶段局部优先于全局第一阶段从exceptionHandlerCache查找 Controller 内部的ExceptionHandler// exceptionHandlerCache 是懒加载的ExceptionHandlerMethodResolverresolverthis.exceptionHandlerCache.get(handlerType);if(resolvernull){resolvernewExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType,resolver);}Methodmethodresolver.resolveMethod(exception);if(method!null){returnnewServletInvocableHandlerMethod(handlerMethod.getBean(),method,...);}如果 Controller 自己定义了ExceptionHandler它会优先处理全局ControllerAdvice不会生效。第二阶段遍历exceptionHandlerAdviceCache查找ControllerAdvicefor(Map.EntryControllerAdviceBean,ExceptionHandlerMethodResolverentry:this.exceptionHandlerAdviceCache.entrySet()){ControllerAdviceBeanadviceentry.getKey();// 检查这个 Advice 是否适用于当前 Controllerif(advice.isApplicableToBeanType(handlerType)){ExceptionHandlerMethodResolverresolverentry.getValue();Methodmethodresolver.resolveMethod(exception);if(method!null){returnnewServletInvocableHandlerMethod(advice.resolveBean(),method,...);}}}returnnull;2.4 查找细节2.4.1advice.isApplicableToBeanType()适用范围过滤ControllerAdvice可以通过以下属性限制生效范围属性作用默认无配置所有 ControllerassignableTypes只对指定的 Controller 类生效basePackages只对指定包下的 Controller 生效annotations只对标注了特定注解的 Controller 生效只有当当前 Controller 满足这些条件时该ControllerAdvice才会被纳入查找范围。2.4.2resolver.resolveMethod(exception)异常类型层级匹配ExceptionHandlerMethodResolver内部维护了一个MapClass? extends Throwable, Method查找时按以下顺序精确匹配查找 Key 是否为exception.getClass()父类匹配向上查找exception.getClass().getSuperclass()接口匹配查找异常类实现的接口递归向上重复 2 和 3一直追溯到Throwable.class“最具体优先”原则如果有多个匹配选择异常类型继承树上距离最近的那个。例如UserNotFoundException会优先匹配ExceptionHandler(UserNotFoundException.class)而不是ExceptionHandler(RuntimeException.class)。第三章exceptionHandlerAdviceCache是从哪里来的3.1 启动时已经扫描好了在第二章中我们看到运行时查找ExceptionHandler依赖于exceptionHandlerAdviceCache。这个缓存不是在运行时构建的而是在启动阶段就已经准备好了。ExceptionHandlerExceptionResolver实现了InitializingBean在afterPropertiesSet()中调用了initExceptionHandlerAdviceCache()protectedvoidinitExceptionHandlerAdviceCache(){// 1. 从容器中找出所有标注了 ControllerAdvice 的 BeanListControllerAdviceBeanadviceBeansControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for(ControllerAdviceBeanadviceBean:adviceBeans){// 2. 解析该 Bean 中所有 ExceptionHandler 方法ExceptionHandlerMethodResolverresolvernewExceptionHandlerMethodResolver(adviceBean.getBeanType());if(resolver.hasExceptionMappings()){// 3. 存入缓存this.exceptionHandlerAdviceCache.put(adviceBean,resolver);}}}3.2 缓存的数据结构exceptionHandlerAdviceCache └── MapControllerAdviceBean, ExceptionHandlerMethodResolver │ └── ExceptionHandlerMethodResolver └── Map异常类型, 处理方法ControllerAdviceBean封装了ControllerAdvice类的实例和元数据ExceptionHandlerMethodResolver解析并缓存该 Advice 中所有ExceptionHandler方法建立异常类型 → Method的映射3.3exceptionHandlerCache是懒加载的与exceptionHandlerAdviceCache不同exceptionHandlerCache存储 Controller 内部的ExceptionHandler是懒加载的在运行时第一个异常到达时才构建ExceptionHandlerMethodResolverresolverthis.exceptionHandlerCache.get(handlerType);if(resolvernull){resolvernewExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType,resolver);}第四章找到方法后怎么执行4.1 包装成ServletInvocableHandlerMethod一旦找到匹配的exceptionHandlerMethodSpring 会把它包装成ServletInvocableHandlerMethod并给它装配上参数解析器和普通 Controller 用的是同一套30 个返回值处理器支持ResponseBody、ResponseEntity等数据绑定工厂支持RequestParam、ModelAttribute等这意味着ExceptionHandler方法和普通 Controller 方法拥有完全相同的参数和返回值处理能力。4.2 执行“三连击”invokeAndHandle内部执行三个步骤第一击参数解析Exception e→ 自动注入当前抛出的异常对象HttpServletRequest→ 自动注入原始请求对象RequestParam→ 从请求参数中取值RequestBody→ 从请求体中解析 JSON第二击反射调用ObjectreturnValuemethod.invoke(bean,args);进入你写的ExceptionHandler方法执行业务逻辑。第三击返回值处理返回值类型处理方式有ResponseBody/RestControllerAdvice通过HttpMessageConverter输出 JSONResponseEntityT自定义状态码和响应头body 通过HttpMessageConverter输出String视图名封装为ModelAndView交给ViewResolver渲染4.3processDispatchResult收尾异常处理方法执行完成后返回到processDispatchResult如果有ModelAndView执行视图渲染如果是 JSON 响应已通过HttpMessageConverter写入response流最终调用mappedHandler.triggerAfterCompletion()触发拦截器的afterCompletion方法第五章完整时序图 关键断点坐标5.1 从启动扫描到运行时执行的完整流程【启动阶段】 ExceptionHandlerExceptionResolver.afterPropertiesSet() │ ├── initExceptionHandlerAdviceCache() │ │ │ ├── findAnnotatedBeans() → 找出所有 ControllerAdvice │ └── 构建 exceptionHandlerAdviceCache │ ├── Key: ControllerAdviceBean │ └── Value: ExceptionHandlerMethodResolver │ └── Map异常类型, 处理方法 【运行时】 Controller 抛出异常 │ ↓ doDispatch() catch → dispatchException │ ↓ processDispatchResult() → processHandlerException() │ ├── 遍历 handlerExceptionResolvers │ 列表在启动时由 initHandlerExceptionResolvers() 构建 │ ↓ ├── ExceptionHandlerExceptionResolver.resolveException() │ ↓ ├── doResolveHandlerMethodException() │ ├── getExceptionHandlerMethod() │ │ ├── exceptionHandlerCache局部懒加载 │ │ └── exceptionHandlerAdviceCache全局启动时构建 │ │ ├── advice.isApplicableToBeanType() │ │ └── resolver.resolveMethod() │ │ └── 精确 → 父类 → 接口 → Throwable │ │ │ ├── 设置参数解析器、返回值处理器 │ │ │ └── invokeAndHandle() │ ├── getMethodArgumentValues() │ ├── doInvoke() │ └── handleReturnValue() │ ├── ResponseBody → HttpMessageConverter 写 JSON │ └── 其他 → 封装 ModelAndView │ └── processDispatchResult() 收尾 → 拦截器 afterCompletion5.2 核心设计思想设计思想体现策略模式HandlerExceptionResolver接口定义了可插拔的异常处理策略多个实现类各司其职优先级控制Order接口控制 Resolver 的执行顺序局部优先于全局复用设计ExceptionHandler方法与普通 Controller 共享同一套参数解析器和返回值处理器缓存机制exceptionHandlerAdviceCache启动时构建运行时直接使用避免重复扫描5.3 优先级总结维度优先级规则Resolver 执行顺序ExceptionHandlerExceptionResolverOrder0ResponseStatusExceptionResolverDefaultHandlerExceptionResolver异常处理位置局部Controller 内部 全局ControllerAdvice异常类型匹配精确匹配 父类匹配 接口匹配 Throwable全局 Advice 排序Order数值越小优先级越高默认最低优先级5.4 关键断点坐标断点位置作用DispatcherServlet.processDispatchResult()异常处理总入口DispatcherServlet.processHandlerException()遍历 Resolver 列表ExceptionHandlerExceptionResolver.doResolveHandlerMethodException()核心查找入口ExceptionHandlerExceptionResolver.getExceptionHandlerMethod()局部/全局匹配逻辑ExceptionHandlerMethodResolver.resolveMethod()异常类型层级匹配ServletInvocableHandlerMethod.invokeAndHandle()执行三连击的入口AbstractMessageConverterMethodProcessor.writeWithMessageConverters()JSON 序列化并写出本文从doDispatch的try-catch出发一路追到processHandlerException遍历 Resolver 列表深入ExceptionHandlerExceptionResolver的getExceptionHandlerMethod查找逻辑覆盖了启动扫描、运行时匹配、反射调用、返回值处理的全链路最后给出完整时序图和断点坐标帮助你在源码中建立清晰的定位。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑