第三篇:SpringMVC——一个HTTP请求在Spring中经历了什么?
前言在前两篇文章中我们拆解了Spring的IoC容器和AOP机制——它们解决了对象管理和横切逻辑的问题。但作为一个Web框架Spring最直接的面孔是SpringMVC它负责接收HTTP请求找到对应的Controller方法执行并返回响应。面试中SpringMVC是必考题“一个HTTP请求进入Spring后经历了哪些步骤”“DispatcherServlet是什么它怎么知道该调用哪个Controller”“拦截器和过滤器有什么区别”“RequestBody和ResponseBody是怎么实现JSON自动转换的”这些问题考察的不是会不会写Controller而是理不理解请求处理的完整链路。本文从一个HTTP请求的视角出发逐层拆解SpringMVC的核心组件和协作流程。本文核心问题DispatcherServlet是什么它在前端控制器模式中扮演什么角色一个HTTP请求从到达Tomcat到返回响应的完整链路是怎样的HandlerMapping、HandlerAdapter、Handler分别做什么ViewResolver和消息转换器HttpMessageConverter各自解决什么问题拦截器和过滤器有什么区别各自在什么时机执行RequestBody和ResponseBody是怎么实现JSON自动转换的SpringBoot是如何自动配置SpringMVC的读完本文你将对SpringMVC的请求处理拥有从入口到响应的完整理解。一、SpringMVC的核心架构——前端控制器模式疑问SpringMVC最核心的组件是什么整个框架是怎么协调工作的回答SpringMVC采用前端控制器模式——DispatcherServlet作为统一入口接收所有请求然后将请求分发给具体的Controller处理。1.1 没有前端控制器时每个Servlet处理一种请求 /user/add → UserAddServlet /user/delete → UserDeleteServlet /order/create → OrderCreateServlet 问题每增加一个接口就要新增一个Servlet类需要做很多重复的请求参数解析和响应封装。1.2 有了前端控制器DispatcherServlet唯一的Servlet │ ├── /user/add → UserController.add() ├── /user/delete → UserController.delete() └── /order/create → OrderController.create() 一个DispatcherServlet接收所有请求分发给对应的Controller方法。 参数解析、返回值处理、异常处理全部统一管理。1.3 核心组件与职责DispatcherServlet │ ├── HandlerMapping处理器映射器 │ 作用根据请求URL找到对应的HandlerController方法 │ 实现RequestMappingHandlerMapping 解析GetMapping、PostMapping等 │ ├── HandlerAdapter处理器适配器 │ 作用执行具体的Handler处理参数绑定和返回值处理 │ 实现RequestMappingHandlerAdapter 处理Controller注解的类 │ ├── HandlerInterceptor拦截器 │ 作用在Handler执行前后进行拦截处理 │ ├── HttpMessageConverter消息转换器 │ 作用将请求体转换为Java对象RequestBody将Java对象转换为响应体ResponseBody │ 实现MappingJackson2HttpMessageConverter 处理JSON │ └── ViewResolver视图解析器 作用将逻辑视图名解析为实际的视图JSP、Thymeleaf等 注意前后端分离后这个组件逐渐被消息转换器替代二、一个HTTP请求的完整链路疑问从浏览器发送请求到返回响应SpringMVC到底经历了什么回答整个流程可以拆解为九个核心步骤每个步骤都有专门的组件负责。2.1 九个步骤HTTP请求 → Tomcat → Filter链 → DispatcherServlet → 响应 DispatcherServlet内部处理步骤 步骤1doService() 接收请求 ↓ 步骤2doDispatch() 开始分发 ↓ 步骤3getHandler() └── HandlerMapping 根据请求URL找到对应的Handler └── 返回 HandlerExecutionChainHandler 拦截器列表 ↓ 步骤4getHandlerAdapter() └── 找到支持该Handler的HandlerAdapter ↓ 步骤5applyPreHandle() └── 执行所有拦截器的preHandle()方法 └── 如果某个拦截器返回false → 请求终止 ↓ 步骤6handle() └── HandlerAdapter调用HandlerController方法 ├── 参数解析RequestParam、PathVariable、RequestBody等 └── 返回值处理ResponseBody、视图名等 ↓ 步骤7applyPostHandle() └── 执行所有拦截器的postHandle()方法 ↓ 步骤8processDispatchResult() └── 处理返回值视图渲染 / 消息转换器写JSON ↓ 步骤9afterCompletion() └── 执行所有拦截器的afterCompletion()方法视图渲染完成后2.2 各步骤对应的源码位置步骤核心方法作用获取HandlergetHandler(request)遍历所有HandlerMapping找到第一个能处理当前请求的获取HandlerAdaptergetHandlerAdapter(handler)遍历所有HandlerAdapter找到支持当前Handler的执行拦截器前置applyPreHandle(request, response)按顺序执行preHandle有一个返回false即中断执行Handleradapter.handle(request, response, handler)参数解析→调用方法→处理返回值执行拦截器后置applyPostHandle(request, response, mv)逆序执行postHandle处理结果processDispatchResult(request, response, mappedHandler, mv, dispatchException)视图渲染或JSON序列化三、HandlerMapping——谁来决定调用哪个Controller疑问一个请求URL过来SpringMVC怎么知道该调用哪个Controller的哪个方法回答HandlerMapping负责建立请求URL→Controller方法的映射关系。最常用的是RequestMappingHandlerMapping它解析GetMapping、PostMapping、RequestMapping等注解建立路径到方法的映射表。3.1 RequestMappingHandlerMapping的工作原理Spring容器启动时 1. 扫描所有Controller注解的类 2. 遍历每个类中GetMapping、PostMapping等注解的方法 3. 将路径请求方法 → Method 的映射存入内部Map 请求到来时 1. 根据请求的URL和HTTP方法查找Map 2. 找到对应的HandlerMethod 3. 将HandlerMethod和拦截器列表包装成HandlerExecutionChain返回3.2 路径匹配规则RestControllerRequestMapping(/api/user)publicclassUserController{GetMapping(/{id})// GET /api/user/123publicUsergetById(PathVariableLongid){...}PostMapping// POST /api/userpublicUsercreate(RequestBodyUserReqreq){...}DeleteMapping(/{id})// DELETE /api/user/123publicvoiddelete(PathVariableLongid){...}}HandlerMapping内部维护的映射表大致是GET /api/user/{id} → UserController.getById() POST /api/user → UserController.create() DELETE /api/user/{id} → UserController.delete()四、HandlerAdapter——参数是怎么解析的疑问HandlerMapping找到了要调用的方法但方法的参数RequestParam、RequestBody、PathVariable是怎么自动填进去的回答HandlerAdapter不仅要调用Controller方法还要负责解析方法参数、处理返回值。不同类型的参数由不同的ArgumentResolver处理。4.1 HandlerAdapter的职责HandlerAdapter.handle()做了三件事 1. 参数解析将HTTP请求中的参数转换成Controller方法的入参 2. 调用方法反射调用Controller方法 3. 返回值处理将方法返回值转换成HTTP响应4.2 参数解析器ArgumentResolver家族参数注解解析器数据来源RequestParamRequestParamMethodArgumentResolverURL查询参数或表单参数PathVariablePathVariableMethodArgumentResolverURL路径中的变量RequestBodyRequestResponseBodyMethodProcessor请求体通过消息转换器解析RequestHeaderRequestHeaderMethodArgumentResolverHTTP请求头ModelAttributeModelAttributeMethodProcessor表单参数绑定到对象无注解的普通参数根据类型自动匹配解析器—4.3 返回值处理器ReturnValueHandler返回值注解处理器处理方式ResponseBodyRequestResponseBodyMethodProcessor通过消息转换器序列化为JSON写入响应体ResponseStatus—设置HTTP状态码无注解返回StringViewNameMethodReturnValueHandler解析为视图名无注解返回ModelAndView—直接使用其中的视图和模型五、HttpMessageConverter——JSON是怎么自动转换的疑问RequestBody怎么能把JSON字符串自动变成Java对象ResponseBody怎么把Java对象自动变成JSON回答HttpMessageConverter是SpringMVC中的消息转换器——它负责HTTP请求体和响应体与Java对象之间的双向转换。MappingJackson2HttpMessageConverter专门处理JSON。5.1 工作原理请求阶段RequestBody 请求体JSON字符串 → 找到合适的HttpMessageConverter根据Content-Type → read() 方法反序列化 → 生成Java对象 → 作为Controller方法参数 响应阶段ResponseBody Controller方法返回Java对象 → 找到合适的HttpMessageConverter根据Accept头 → write() 方法序列化 → 生成JSON字符串 → 写入响应体5.2 SpringBoot默认注册的转换器转换器Content-Type作用MappingJackson2HttpMessageConverterapplication/jsonJSON↔Java对象StringHttpMessageConvertertext/plain字符串读写ByteArrayHttpMessageConverterapplication/octet-stream字节数组读写5.3 自定义转换器ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{OverridepublicvoidconfigureMessageConverters(ListHttpMessageConverter?converters){MappingJackson2HttpMessageConverterconverternewMappingJackson2HttpMessageConverter();// 自定义日期格式converter.setObjectMapper(newObjectMapper().setDateFormat(newSimpleDateFormat(yyyy-MM-dd HH:mm:ss)));converters.add(converter);}}六、拦截器 vs 过滤器疑问拦截器和过滤器有什么区别什么时候用拦截器什么时候用过滤器回答过滤器是Servlet规范定义的运行在Servlet容器层面可以拦截所有请求包括静态资源。拦截器是SpringMVC定义的运行在DispatcherServlet内部只能拦截进入SpringMVC的请求。6.1 核心区别维度过滤器Filter拦截器Interceptor规范Servlet规范SpringMVC特有执行时机在Servlet之前后在HandlerController方法之前后作用范围所有请求包括静态资源只拦截进入DispatcherServlet的请求IoC支持不能直接注入Spring Bean可以直接注入Spring Bean执行顺序通过Order或web.xml的filter-mapping顺序控制通过addInterceptor的注册顺序控制6.2 执行顺序请求 → Filter1 → Filter2 → DispatcherServlet → Interceptor1.preHandle → Interceptor2.preHandle → Handler(Controller方法) → Interceptor2.postHandle → Interceptor1.postHandle → 视图渲染/JSON序列化 → Interceptor2.afterCompletion → Interceptor1.afterCompletion → Filter2 → Filter1 → 响应记忆口诀Filter包在最外层进出都经过。Interceptor包在Handler外层前置顺序执行、后置逆序执行。afterCompletion在视图渲染完成后执行适合做资源清理。6.3 什么时候用什么场景选择原因字符编码设置Filter需要在SpringMVC之前处理Spring Security安全校验Filter独立于SpringMVC保护所有资源用户登录校验Interceptor可以注入UserService等Bean操作日志记录Interceptor可以获取到Controller方法和参数跨域处理CORSFilter需要在请求最早期处理七、SpringBoot对SpringMVC的自动配置疑问SpringBoot项目中没有写任何SpringMVC配置它是怎么工作的回答SpringBoot通过WebMvcAutoConfiguration自动配置了SpringMVC所需的核心组件包括DispatcherServlet、HandlerMapping、HandlerAdapter、HttpMessageConverter等。7.1 自动配置做了什么WebMvcAutoConfiguration 自动配置 1. 注册 DispatcherServlet 2. 配置 RequestMappingHandlerMapping路径映射 3. 配置 RequestMappingHandlerAdapter参数解析和返回值处理 4. 配置 HttpMessageConverterJSON转换自动引入Jackson 5. 配置 ViewResolver视图解析默认是Thymeleaf或InternalResourceViewResolver 6. 配置静态资源处理/static、/public、/resources等目录7.2 为什么引入Jackson依赖就能处理JSONSpringBoot的JacksonAutoConfiguration会自动创建ObjectMapper。WebMvcAutoConfiguration检测到ObjectMapper存在后自动注册MappingJackson2HttpMessageConverter。这就是为什么只引入Jackson依赖RequestBody和ResponseBody就能自动处理JSON。八、面试中这样回答面试官“SpringMVC的请求处理流程是怎样的”回答框架“所有请求统一由DispatcherServlet接收。第一步通过HandlerMapping找到处理请求的Handler——通常是Controller中对应的方法。第二步通过HandlerAdapter执行这个Handler——在调用之前会解析请求参数RequestParam、RequestBody等调用之后会处理返回值视图名或ResponseBody的JSON序列化。第三步处理响应——如果是页面请求走ViewResolver渲染视图前后端分离场景则通过HttpMessageConverter将返回值序列化为JSON写入响应体。整个过程前后还有拦截器链和过滤器链的包裹——Filter先于Interceptor执行。”面试官“拦截器和过滤器有什么区别”回答“过滤器是Servlet规范定义的运行在DispatcherServlet之外可以拦截所有请求包括静态资源。拦截器是SpringMVC特有的运行在DispatcherServlet内部只能拦截进入SpringMVC的请求。过滤器不能直接注入Spring Bean拦截器可以。执行顺序上过滤器在最外层包裹整个Servlet处理过程拦截器在DispatcherServlet内部包裹Handler的执行。”总结DispatcherServlet是SpringMVC的核心——它采用前端控制器模式统一接收所有请求并按标准步骤分发处理请求处理的三个关键组件HandlerMapping负责路由定位HandlerAdapter负责参数解析和反射调用HttpMessageConverter负责JSON的双向序列化RequestBody通过MappingJackson2HttpMessageConverter反序列化JSON为Java对象ResponseBody通过同一个转换器完成对象的JSON序列化。这一对组件让前后端分离的自然语言交互成为可能Filter在Servlet容器层拦截所有请求Interceptor在SpringMVC内部只能拦截进入DispatcherServlet的请求。两者的执行时机和作用范围不同SpringBoot的WebMvcAutoConfiguration自动配置了所有核心组件——你不需要写任何XML配置引入Jackson依赖即自动获得JSON处理能力下一篇预告Spring原理四——SpringBoot自动配置约定大于配置的底层原理。拆解SpringBootApplication背后的EnableAutoConfiguration是如何通过spring.factories加载配置类的以及如何自定义一个starter。