回答
Spring MVC 是一个用于构建基于 MVC(Model-View-Controller)设计模式的 Web 应用,它的核心组件包括如下几个:
DispatcherServlet
:前端控制器,复杂接收所有 HTTP 请求并将其分发到具体的处理器。HandlerMapping
:根据请求 URL 找到对应的处理器 Handler。HandlerAdapter
:适配具体的处理器,负责调用对应的处理方法。Controller
:Handler,具体的业务逻辑处理器,处理用户的请求并返回数据获视图。ViewResolver
:视图解析器,负责解析视图名称并返回具体的视图实现。ModelAndView
:用于在 Controller 与 View 之间传递数据和逻辑视图名称的对象。HttpMessageConverter
:用于在请求和响应中处理 JSON、XML 等消息体的转换。ExceptionResolver
:处理请求过程中抛出的异常并生成相应的错误响应。
详解
DispatcherServlet
DispatcherServlet
是 Spring MVC 的核心组件,也是前端控制器,负责接收所有的 HTTP 请求并将其分发到具体的处理器。同时它还协调其他组件(如HandlerMapping、HandlerAdapter、ViewResolver等等)完成请求的处理和响应。主要工作流程是:
- 接收 HTTP 请求。
- 根据 URL 找到对应的 Handler。
- 调用 Handler 处理请求并返回
ModelAndView
。 - 使用
ViewResolver
渲染视图,生成 HTTP 响应。
配置 DispatcherServlet
:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
我们简单看下它的源码部分。我们进去 DispatcherServlet
找对应的 doGet()
、doPost()
(开发过 Servlet 的小伙伴一定知道为什么要找这两个),发现并没有,在父类 FrameworkServlet 中找到:
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 委托给 processRequest
processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 省略代码...
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
// ...
}
catch (Throwable ex) {
// ...
}
finally {
// ...
}
}
最后委托 doService(),该方法就是在 DispatcherServlet
实现:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
try {
doDispatch(request, response);
}
finally {
//...
}
}
继续委托 doDispatch()
:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 处理文件上传
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 获取当前请求的 Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取当前请求的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//...
// 拦截器的前置
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行业务逻辑
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 设置默认视图名
applyDefaultViewName(processedRequest, mv);
// 拦截器后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 选择视图并渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
// ...
}
}
从这个方法我们可以看到整个 Spring MVC 处理请求的逻辑。
HandlerMapping
根据请求 URL 找到对应的处理器 Handler。
在 DispatcherServlet
中,它利用 getHandler() 获取对应的 Handler:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
逻辑很简单就是迭代 handlerMappings
找到对应的 HandlerExecutionChain 对象,找到就停止。
在 Spring MVC 中,当一个 HTTP 请求到达时,DispatcherServlet 会将其分发给相应的处理器,而 HandlerExecutionChain 是这个分发过程中的一部分,它主要负责存储和管理与一个具体请求处理器相关的一系列拦截器,并在请求处理时依次执行这些拦截器。所以,HandlerExecutionChain 主要由两个部分组成:
Handler
:实际的请求处理器Interceptor
:一系列的拦截器。
在调用 Handler 的实际业务逻辑时,DispatcherServlet
会优先一次调用拦截器的 preHandle()
,如果所有拦截器都返回 true,那么请求会被传递给实际的处理器进行处理。如下:
// 拦截器的前置
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
HandlerAdapter
适配具体的处理器,负责调用对应的处理方法。它的主要作用是为不同类型的处理器提供统一的调用适配机制。
HandlerAdapter
提供了一种抽象机制,用于支持多种类型的处理器,同时,它也解耦了 DispatcherServlet
和具体的处理器,负责处理器的请求调用进行适配,使得不同的控制器可以被统一调用。
Spring MVC 内置了几个 HandlerAdapter
:
RequestMappingHandlerAdapter
:用于支持基于注解的处理器(如@Controller
和@RequestMapping
标注的方法)。SimpleControllerHandlerAdapter
:用于支持实现了org.springframework.web.servlet.mvc.Controller
接口的传统控制器。HttpRequestHandlerAdapter
:用于支持实现了org.springframework.web.HttpRequestHandler
接口的处理器。HandlerFunctionAdapter
:用于支持基于函数式编程模型的处理器。在 Spring WebFlux 中较为常用。
在 DispatcherServlet
的 doService() 方法中会调用 getHandlerAdapter()
获取对应的 HandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
ViewResolver
ViewResolver
主要负责将逻辑视图名称解析为实际 View
对象的接口。它是视图渲染流程中的重要一环,将控制器返回的 ModelAndView
中的逻辑视图名称解析为可以渲染的视图实例。主要职责包括:
- 解析视图名称:将逻辑视图名称(如 "home" 或 "user/list")映射为具体的视图实现。
- 返回 View 实例:生成并返回
View
对象,最终负责渲染输出。
在 Spring MVC 中,视图的渲染流程一般如下:
Controller
处理请求后,返回一个ModelAndView
对象。- 其中的逻辑视图名称由
ViewResolver
转换为具体的视图。 - 视图实例调用其
render()
,结合模型数据完成响应渲染。
ViewResolver
是一个接口,定义如下:
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
viewName
:逻辑视图名称locale
:用于国际化视图选择的区域信息
返回的是一个 View 对象,表示可执行渲染的视图。
Spring MVC 提供了很多 ViewResolver
的实现,主要有:
InternalResourceViewResolver
:用于解析 JSP 文件ThymeleafViewResolver
:用于支持 Thymeleaf 模板引擎FreeMarkerViewResolver
:用于支持 FreeMarker 模板引擎BeanNameViewResolver
:根据视图名称从 Spring 容器中直接查找对应的 View BeanXmlViewResolver
:从 XML 配置文件中加载视图定义ContentNegotiatingViewResolver
:根据请求的内容类型(Content-Type
)动态选择视图。可用于返回多种类型的响应,如 JSON、XML 或 HTML。
ModelAndView
ModelAndView
主要用于在控制器方法中返回数据模型和视图信息的组合,它是 Controller 和 View 之间的桥梁,包含了渲染视图所需的逻辑视图名称以及模型数据。通过 ModelAndView
,控制器可以同时定义:
Model
:数据模型,用于传递业务数据到视图。View
:视图信息,用于指定渲染的页面或返回的结果。
所以,ModelAndView
的定义如下:
public class ModelAndView {
// 逻辑视图名称或具体 View 对象
@Nullable
private Object view;
// 模型数据
@Nullable
private ModelMap model;
// HTTP 状态码
@Nullable
private HttpStatus status;
}
HttpMessageConverter
在 Spring MVC 中,HttpMessageConverter
是将 HTTP 请求和响应进行转换的核心组件,负责在 HTTP 消息和 Java 对象之间进行序列化与反序列化操作。
HttpMessageConverter
是一个接口,它提供了一种机制:
- 将 HTTP 请求的内容转换为 Java 对象(反序列化),体现在
@RequestBody
注解上 - 将 Java 对象转换为 HTTP 响应的内容(序列化),体现在
@ResponseBody
注解上
HttpMessageConverter
接口定义如下:
public interface HttpMessageConverter<T> {
// 检查是否支持某种类型
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
// 获取支持的媒体类型列表
List<MediaType> getSupportedMediaTypes();
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
// 读取请求体并转换为目标类型
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 将对象写入响应体
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
常见的实现类有:
MappingJackson2HttpMessageConverter
:基于 Jackson 库,将 JSON 数据与 Java 对象之间进行转换。GsonHttpMessageConverter
:基于 Gson 库实现的 JSON 数据的处理。Jaxb2RootElementHttpMessageConverter
:基于 JAXB 实现,用于将 XML 数据与 Java 对象之间进行转换。StringHttpMessageConverter
:用于处理普通的String
类型数据。ByteArrayHttpMessageConverter
:用于处理字节数组数据。
目前我们最常用的就是 MappingJackson2HttpMessageConverter
。
ExceptionResolver
ExceptionResolver 是 Spring MVC 处理应用程序中未捕获异常的核心接口,它负责捕获控制器方法中的异常,并将其转换为合适的视图或响应结果,从而避免异常直接暴露给用户。其接口定义如下:
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
HttpServletRequest request
:当前的 HTTP 请求对象。HttpServletResponse response
:当前的 HTTP 响应对象。Object handler
:触发异常的处理器对象(通常是控制器方法)。Exception ex
:抛出的异常对象。
返回值 ModelAndView
表示返回的视图和模型数据。
就我们实际开发过程中,@ControllerAdvice
全局异常处理是我们目前使用最为广泛和优雅的方式。详细情况见面试题:Spring MVC 如何做统一异常处理?
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。