Spring MVC 视图机制的核心接口与类

Spring MVC 视图机制的核心接口与类

Spring MVC 的视图机制解决的是"Controller 返回一个逻辑视图名,如何变成最终的 HTML/JSON/PDF 响应"。围绕这个目标,Spring MVC 设计了一组接口和实现类,核心是 ViewResolver(视图解析器) 和 View(视图)。

一、两个核心接口

1. ViewResolver

视图解析器接口,负责把 Controller 返回的逻辑视图名 解析成具体的 View 对象。

java 复制代码
public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

DispatcherServlet 在 processDispatchResult() 中遍历所有 ViewResolver,调用 resolveViewName(),拿到第一个非 null 的 View 后停止。

2. View

视图接口,负责把模型数据渲染成 HTTP 响应。

java 复制代码
public interface View {
    String getContentType();
    void render(Map<String, ?> model, 
                HttpServletRequest request, 
                HttpServletResponse response) throws Exception;
}

render() 是真正执行渲染的地方:把 model 中的数据写入 response。

二、ViewResolver 的常见实现

实现类 作用 典型场景
InternalResourceViewResolver 解析为 JSP 或内部资源 JSP 项目
ThymeleafViewResolver 解析为 Thymeleaf 模板 Spring Boot 推荐
FreeMarkerViewResolver 解析为 FreeMarker 模板 FreeMarker 项目
ContentNegotiatingViewResolver 根据请求的媒体类型选择视图 同一接口返回 JSON/XML/HTML
BeanNameViewResolver 把视图名当作 Bean 名称查找 自定义 View Bean
XmlViewResolver 从 XML 文件加载视图 Bean 老项目
ResourceBundleViewResolver 从 properties 文件加载视图 国际化视图
ViewResolverComposite 组合多个 ViewResolver 多视图技术共存

InternalResourceViewResolver

最经典的实现,通常配置 prefix 和 suffix:

xml 复制代码
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

返回 "user/list" 时,解析为 /WEB-INF/views/user/list.jsp。

它创建的是 InternalResourceView 或 JstlView,最终通过 RequestDispatcher.forward() 转发到 JSP。

ThymeleafViewResolver

Spring Boot 中 Thymeleaf 的视图解析器:

java 复制代码
@Bean
public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
    ThymeleafViewResolver resolver = new ThymeleafViewResolver();
    resolver.setTemplateEngine(templateEngine);
    resolver.setCharacterEncoding("UTF-8");
    return resolver;
}

它依赖 SpringTemplateEngine 和 TemplateResolver 来定位和渲染模板。

ContentNegotiatingViewResolver

根据请求的 Accept 头或扩展名,委托给不同的 ViewResolver:

java 复制代码
@Bean
public ContentNegotiatingViewResolver contentNegotiatingViewResolver() {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setViewResolvers(List.of(
        new ThymeleafViewResolver(),
        new MappingJackson2JsonViewResolver()
    ));
    return resolver;
}

同一个 /users/1,浏览器访问返回 HTML,Apipost 设置 Accept: application/json 返回 JSON。

BeanNameViewResolver

把逻辑视图名直接当作 Spring Bean 名称:

java 复制代码
@Bean
public View pdfView() {
    return new PdfView();
}

Controller 返回 "pdfView",BeanNameViewResolver 会从容器中找到名为 pdfView 的 Bean 作为视图。

三、View 的常见实现

实现类 作用
InternalResourceView 内部资源转发,如 JSP
JstlView 支持 JSTL 的 JSP 视图
RedirectView 重定向
MappingJackson2JsonView 把模型数据序列化为 JSON
MappingJackson2XmlView 序列化为 XML
AbstractPdfView PDF 视图基类
AbstractExcelView Excel 视图基类
FreeMarkerView FreeMarker 模板视图
ThymeleafView Thymeleaf 模板视图

InternalResourceView / JstlView

InternalResourceView 通过 RequestDispatcher.forward() 转发到目标资源。JstlView 是它的子类,增加了对 JSTL 的支持。

java 复制代码
public class InternalResourceView extends AbstractUrlBasedView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        exposeModelAsRequestAttributes(model, request);
        RequestDispatcher rd = getRequestDispatcher(request, getUrl());
        rd.forward(request, response);
    }
}

exposeModelAsRequestAttributes() 把 Model 中的数据逐个 setAttribute 到 request 域,这就是为什么 JSP 里能用 ${key} 取到 Controller 放入 Model 的数据。

RedirectView

处理 redirect: 前缀,向浏览器发送 302/301 响应:

java 复制代码
@GetMapping("/save")
public String save() {
    return "redirect:/list";
}

UrlBasedViewResolver 检测到 redirect: 前缀时,会创建 RedirectView,而不是 InternalResourceView。

MappingJackson2JsonView

把模型数据序列化为 JSON 写入响应。@ResponseBody 和 @RestController 的底层不是这个 View,而是 HttpMessageConverter,但 MappingJackson2JsonView 在需要"通过视图机制返回 JSON"时使用。

四、视图解析的辅助类

ViewResolverComposite

组合多个 ViewResolver,按顺序查找:

java 复制代码
ViewResolverComposite composite = new ViewResolverComposite();
composite.setViewResolvers(List.of(
    new ThymeleafViewResolver(),
    new BeanNameViewResolver()
));

DispatcherServlet 初始化时,会把容器中所有的 ViewResolver 收集起来,如果配置了 ViewResolverComposite,就用它统一管理。

AbstractCachingViewResolver

ViewResolver 的抽象基类,提供视图缓存能力。InternalResourceViewResolver、ThymeleafViewResolver 等都继承它。解析过的 View 会被缓存,避免重复创建。

java 复制代码
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport 
        implements ViewResolver {
    private final Map<Object, View> viewCache = new ConcurrentHashMap<>(16);
    
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        // 先查缓存,未命中再调用 createView()
        View view = this.viewCache.get(cacheKey);
        if (view == null) {
            view = createView(viewName, locale);
            this.viewCache.put(cacheKey, view);
        }
        return view;
    }
    
    protected abstract View createView(String viewName, Locale locale) throws Exception;
}

UrlBasedViewResolver

基于 URL 的视图解析器抽象类,支持 prefix、suffix、redirect:、forward: 等前缀。

java 复制代码
public abstract class UrlBasedViewResolver extends AbstractCachingViewResolver {
    private String prefix = "";
    private String suffix = "";
    
    @Override
    protected View createView(String viewName, Locale locale) throws Exception {
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            return createView(viewName, REDIRECT_URL_PREFIX);
        }
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            return createView(viewName, FORWARD_URL_PREFIX);
        }
        return buildView(prefix + viewName + suffix);
    }
}

InternalResourceViewResolver 继承它,只负责返回 InternalResourceView 或 JstlView。

五、视图解析在 DispatcherServlet 中的位置

一次请求的完整流程:

复制代码
1. DispatcherServlet.doDispatch()
2. 找到 Handler,通过 HandlerAdapter 调用 Controller
3. Controller 返回 ModelAndView
4. DispatcherServlet.processDispatchResult()
   ├── 如果 ModelAndView 中有 View 对象,直接使用
   └── 如果只有视图名,调用 resolveViewName()
       ├── 遍历所有 ViewResolver
       ├── 调用 resolveViewName(viewName, locale)
       └── 拿到第一个非 null 的 View
5. 调用 View.render(model, request, response)
6. 渲染完成,响应返回客户端

核心方法在 DispatcherServlet.resolveViewName():

java 复制代码
protected View resolveViewName(String viewName, Map<String, Object> model,
        Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

多个 ViewResolver 按顺序尝试,谁先返回非 null 的 View,就用谁。

六、各视图技术的组件对应关系

视图技术 ViewResolver View 模板引擎
JSP InternalResourceViewResolver InternalResourceView / JstlView 无(容器处理)
Thymeleaf ThymeleafViewResolver ThymeleafView SpringTemplateEngine
FreeMarker FreeMarkerViewResolver FreeMarkerView FreeMarkerConfigurer
JSON MappingJackson2JsonView MappingJackson2JsonView Jackson ObjectMapper
重定向 UrlBasedViewResolver RedirectView 无

七、总结

维度 核心内容
核心接口 ViewResolver、View
核心流程 DispatcherServlet 遍历 ViewResolver → 拿到 View → 调用 render
ViewResolver 常见实现 InternalResourceViewResolver、ThymeleafViewResolver、ContentNegotiatingViewResolver、BeanNameViewResolver
View 常见实现 InternalResourceView、JstlView、RedirectView、MappingJackson2JsonView
辅助基类 AbstractCachingViewResolver、UrlBasedViewResolver、ViewResolverComposite
关键机制 视图缓存、前缀后缀拼接、redirect: / forward: 前缀、内容协商

Spring MVC 视图机制的核心可以概括为:ViewResolver 负责"把视图名变成 View",View 负责"把模型数据渲染成响应" 。DispatcherServlet 在两者之间做协调。理解 InternalResourceViewResolver 和 ThymeleafViewResolver 这两个最常用的实现,以及 View.render() 中模型数据如何暴露到 request 域,就能解释为什么 Controller 返回的字符串最终变成了页面。

相关推荐
y1su1 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
传奇开心果编程1 小时前
【Rust入门练中学】第5课:引用与借用
开发语言·rust·引用·编程语言理论·通用编程语言·借用
码事漫谈1 小时前
AI 读你的提示词,其实很没耐心
后端
qetfw1 小时前
Bash 变量:export、位置参数与默认值展开验证
linux·开发语言·chrome·bash
电子云与长程纠缠2 小时前
UE5魂系游戏开发教程05
开发语言·学习·游戏·ui·ue5
cspttty2 小时前
2027风险管理岗准备路线:数学与应用数学如何补齐金融、SQL与Python
开发语言·python·金融
用户8356290780512 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123452 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒2 小时前
Java性能优化完全指南
前端·人工智能·后端