
你是否在开发Spring MVC应用时,遇到过Controller返回了视图名,但页面却报404错误的情况?即使检查了ViewResolver的配置,问题依旧难以定位。本文将深入Spring MVC源码,详细剖析视图从解析到渲染的完整过程,帮助你从根本上理解并解决这类问题。
一、视图解析的入口:DispatcherServlet的render方法
在Spring MVC处理请求的整个链条中,视图渲染是最后一个关键步骤。当Controller方法执行完毕并返回一个ModelAndView对象后,DispatcherServlet会调用其render方法来启动视图渲染流程。我们先来看render方法的核心源码片段:
// DispatcherServlet.java
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// 关键步骤1:通过ViewResolver解析视图名,获取View对象实例
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + viewName + "'");
}
} else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView contains neither view name nor View object");
}
}
// 关键步骤2:调用获取到的View对象的render方法进行最终页面渲染
view.render(mv.getModelInternal(), request, response);
}
这段代码清晰地揭示了视图渲染的两个核心阶段:
- 解析视图名:通过
resolveViewName方法,根据Controller返回的视图名称,在Spring容器中寻找能够处理它的ViewResolver,并最终得到一个具体的View实例。
- 执行渲染:调用
View实例的render方法,将模型数据与请求/响应对象传递进去,完成最终的页面输出。
其中,resolveViewName方法会遍历所有已注册的ViewResolver,按优先级尝试解析,直到有一个解析成功或全部失败。
二、ViewResolver的核心逻辑:从视图名到View实例
在实际应用中,最常用的视图解析器是InternalResourceViewResolver,它主要用于解析JSP视图。我们来看其resolveViewName方法的简化逻辑:
// InternalResourceViewResolver.java
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 拼接完整的视图资源路径:前缀 + 视图名 + 后缀
String url = getPrefix() + viewName + getSuffix();
// 创建并返回一个封装了此路径的InternalResourceView实例
return buildView(url);
}
关键点解析:
- 这里拼接路径所使用的
prefix(前缀)和suffix(后缀),正是我们在Spring配置文件中(如application.properties或XML)定义的部分。例如,配置spring.mvc.view.prefix=/WEB-INF/views/和spring.mvc.view.suffix=.jsp后,对于视图名home,最终生成的路径就是/WEB-INF/views/home.jsp。
buildView方法负责实例化一个InternalResourceView对象,该对象内部持有了上述拼接好的URL路径,为后续的渲染做好准备。
三、View的渲染:把模型数据交给视图模板
获得View实例后,下一步便是执行其render方法。我们以InternalResourceView(对应JSP)为例,查看其渲染的核心逻辑:
// InternalResourceView.java
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 关键步骤1:将Model中的数据暴露为Request属性
exposeModelAsRequestAttributes(model, request);
// 关键步骤2:将请求转发给目标JSP资源
RequestDispatcher rd = getRequestDispatcher(request, getUrl());
rd.forward(request, response);
}
渲染过程详解:
- 暴露模型数据:
exposeModelAsRequestAttributes方法会将Model中的所有键值对,通过request.setAttribute(key, value)的方式设置到HttpServletRequest的属性中。这正是JSP页面中能够使用EL表达式${key}直接访问到后台数据的根本原因。
- 请求转发:通过
RequestDispatcher.forward方法,将当前的请求和响应对象转发给之前拼接好的JSP文件路径。此后,控制权移交给了Servlet容器(如Tomcat)的JSP引擎,由它负责执行JSP页面,生成最终的HTML内容并写入响应。
四、视图解析的完整流程:UML时序图
为了更直观地理解上述组件间的交互顺序,以下时序图清晰地展示了从DispatcherServlet调用render开始,到页面完成渲染的完整过程:

五、常见问题的源码定位与解决思路
理解了源码流程后,许多常见的视图问题就变得易于定位:
总结:Spring MVC的视图解析机制并不复杂,其核心就是ViewResolver寻址和View渲染这两个清晰的步骤。深入理解DispatcherServlet、ViewResolver及View这几个核心类在源码层面的协作,能让你在遇到视图层问题时快速定位根源,从而高效地解决问题。