简体中文 繁體中文 English Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français Japanese

站内搜索

搜索

活动公告

通知:为庆祝网站一周年,将在5.1日与5.2日开放注册,具体信息请见后续详细公告
04-22 00:04
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Spring MVC页面输出技术全解析从ModelAndView到ViewResolver详解数据渲染与页面展示的完整流程及实战技巧

SunJu_FaceMall

3万

主题

1174

科技点

3万

积分

白金月票

碾压王

积分
32796

立华奏

发表于 2025-8-23 12:20:35 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

Spring MVC作为Spring框架中用于Web开发的重要模块,提供了一套强大而灵活的页面输出技术。在Web应用开发中,如何将控制器处理后的数据有效地渲染并展示给用户,是每个开发者必须掌握的核心技能。本文将全面解析Spring MVC中的页面输出技术,从ModelAndView到ViewResolver,详细剖析数据渲染与页面展示的完整流程,并结合实际案例分享实战技巧,帮助开发者深入理解并灵活运用这些技术。

Spring MVC页面输出技术概述

Spring MVC的页面输出技术主要涉及以下几个核心组件:

1. Controller:处理用户请求,准备需要展示的数据
2. Model:存储需要传递给视图的数据
3. View:负责渲染数据并生成HTML响应
4. ModelAndView:一个同时包含Model和View的对象
5. ViewResolver:根据视图名称解析具体的View对象

这些组件协同工作,形成了一个完整的数据渲染与页面展示流程。当控制器处理完请求后,会将数据放入Model中,并指定一个视图名称,然后ViewResolver会根据这个视图名称找到具体的视图实现,最后视图将Model中的数据渲染成最终的HTML响应返回给客户端。

ModelAndView详解

ModelAndView的基本概念

ModelAndView是Spring MVC中一个非常重要的类,它同时封装了Model和View两个对象。Model用于存储数据,View用于指定视图。通过ModelAndView,我们可以在控制器方法中方便地将数据和视图信息返回给DispatcherServlet。

ModelAndView的使用方式

ModelAndView提供了多种构造方式:
  1. // 1. 只指定视图名
  2. ModelAndView mav = new ModelAndView("viewName");
  3. // 2. 同时指定视图名和Model
  4. Map<String, Object> model = new HashMap<>();
  5. model.put("key1", "value1");
  6. model.put("key2", "value2");
  7. ModelAndView mav = new ModelAndView("viewName", model);
  8. // 3. 指定视图名并添加单个模型数据
  9. ModelAndView mav = new ModelAndView("viewName", "modelName", modelObject);
  10. // 4. 创建空的ModelAndView,后续再设置
  11. ModelAndView mav = new ModelAndView();
  12. mav.setViewName("viewName");
  13. mav.addObject("key1", "value1");
  14. mav.addObject("key2", "value2");
复制代码

下面是一个完整的Controller示例,展示如何使用ModelAndView:
  1. @Controller
  2. @RequestMapping("/user")
  3. public class UserController {
  4.    
  5.     @Autowired
  6.     private UserService userService;
  7.    
  8.     @RequestMapping("/detail/{id}")
  9.     public ModelAndView getUserDetail(@PathVariable("id") Long id) {
  10.         // 创建ModelAndView对象
  11.         ModelAndView mav = new ModelAndView();
  12.         
  13.         // 获取用户数据
  14.         User user = userService.getUserById(id);
  15.         
  16.         // 添加模型数据
  17.         mav.addObject("user", user);
  18.         
  19.         // 获取用户的订单列表
  20.         List<Order> orders = orderService.getOrdersByUserId(id);
  21.         mav.addObject("orders", orders);
  22.         
  23.         // 设置视图名称
  24.         mav.setViewName("user/detail");
  25.         
  26.         return mav;
  27.     }
  28.    
  29.     @RequestMapping("/list")
  30.     public ModelAndView getUserList(
  31.             @RequestParam(defaultValue = "1") int page,
  32.             @RequestParam(defaultValue = "10") int size) {
  33.         
  34.         // 分页获取用户列表
  35.         Page<User> userPage = userService.getUserList(page, size);
  36.         
  37.         // 使用另一种方式创建ModelAndView
  38.         return new ModelAndView("user/list", "page", userPage);
  39.     }
  40. }
复制代码

ModelAndView提供了一些常用方法来操作模型数据和视图:
  1. // 添加模型数据
  2. mav.addObject("key", value);  // 添加单个数据
  3. mav.addAllObjects(map);       // 批量添加数据
  4. // 获取模型数据
  5. Map<String, Object> model = mav.getModel();  // 获取整个模型
  6. Object value = mav.getModel().get("key");    // 获取特定数据
  7. // 设置和获取视图
  8. mav.setViewName("viewName");  // 设置视图名称
  9. String viewName = mav.getViewName();  // 获取视图名称
  10. View view = mav.getView();  // 获取View对象
  11. // 设置视图状态
  12. mav.setStatus(HttpStatus.OK);  // 设置HTTP状态码
  13. mav.clear();  // 清除ModelAndView中的所有数据
复制代码

ModelAndView的工作原理

当控制器方法返回一个ModelAndView对象后,DispatcherServlet会进行以下处理:

1. 获取ModelAndView中的Model数据,并将其合并到临时的ModelMap中
2. 获取ModelAndView中的View信息(可以是视图名称或View对象)
3. 如果是视图名称,则使用ViewResolver解析为具体的View对象
4. 将Model数据传递给View对象,进行渲染
5. 返回渲染后的响应结果

下面是一个简化版的处理流程示意图:
  1. // DispatcherServlet处理ModelAndView的简化流程
  2. protected void render(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) throws Exception {
  3.     // 1. 获取视图
  4.     View view = resolveViewName(mav.getViewName(), mav.getModelInternal(), request, response);
  5.    
  6.     // 2. 渲染视图
  7.     if (view != null) {
  8.         view.render(mav.getModelInternal(), request, response);
  9.     }
  10. }
复制代码

ViewResolver详解

ViewResolver的基本概念

ViewResolver是Spring MVC中负责将视图名称解析为具体View对象的组件。当控制器返回一个视图名称时,ViewResolver会根据这个名称找到对应的View实现,如JSP、Thymeleaf、FreeMarker等。

ViewResolver的接口定义

ViewResolver接口非常简单,只定义了一个方法:
  1. public interface ViewResolver {
  2.     View resolveViewName(String viewName, Locale locale) throws Exception;
  3. }
复制代码

其中,viewName是控制器返回的视图名称,locale是本地化信息,返回值是解析后的View对象。

Spring MVC提供的ViewResolver实现

Spring MVC提供了多种ViewResolver的实现,以适应不同的视图技术:

InternalResourceViewResolver是最常用的ViewResolver之一,主要用于解析JSP和HTML等内部资源视图:
  1. @Configuration
  2. @EnableWebMvc
  3. @ComponentScan("com.example.controller")
  4. public class WebConfig implements WebMvcConfigurer {
  5.    
  6.     @Bean
  7.     public InternalResourceViewResolver viewResolver() {
  8.         InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  9.         resolver.setPrefix("/WEB-INF/views/");  // 设置视图前缀
  10.         resolver.setSuffix(".jsp");             // 设置视图后缀
  11.         resolver.setViewClass(JstlView.class);  // 设置视图类,支持JSTL
  12.         return resolver;
  13.     }
  14. }
复制代码

配置完成后,当控制器返回”user/detail”时,InternalResourceViewResolver会将其解析为”/WEB-INF/views/user/detail.jsp”。

UrlBasedViewResolver是InternalResourceViewResolver的父类,提供了一种更简单的方式来解析视图:
  1. @Bean
  2. public UrlBasedViewResolver urlBasedViewResolver() {
  3.     UrlBasedViewResolver resolver = new UrlBasedViewResolver();
  4.     resolver.setPrefix("/WEB-INF/views/");
  5.     resolver.setSuffix(".jsp");
  6.     resolver.setViewClass(JstlView.class);
  7.     return resolver;
  8. }
复制代码

ResourceBundleViewResolver允许我们在属性文件中定义视图名称和对应的View类:
  1. @Bean
  2. public ResourceBundleViewResolver resourceBundleViewResolver() {
  3.     ResourceBundleViewResolver resolver = new ResourceBundleViewResolver();
  4.     resolver.setBasename("views");  // 指定属性文件的基础名称
  5.     return resolver;
  6. }
复制代码

然后在classpath根目录下的views.properties文件中定义视图映射:
  1. user.detail.(class)=org.springframework.web.servlet.view.JstlView
  2. user.detail.url=/WEB-INF/views/user/detail.jsp
  3. user.list.(class)=org.springframework.web.servlet.view.JstlView
  4. user.list.url=/WEB-INF/views/user/list.jsp
复制代码

XmlViewResolver允许我们在XML配置文件中定义视图:
  1. @Bean
  2. public XmlViewResolver xmlViewResolver() {
  3.     XmlViewResolver resolver = new XmlViewResolver();
  4.     resolver.setLocation(new ClassPathResource("views.xml"));  // 指定XML配置文件
  5.     return resolver;
  6. }
复制代码

然后在views.xml文件中定义视图:
  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  4.        http://www.springframework.org/schema/beans/spring-beans.xsd">
  5.    
  6.     <bean id="userDetail" class="org.springframework.web.servlet.view.JstlView">
  7.         <property name="url" value="/WEB-INF/views/user/detail.jsp"/>
  8.     </bean>
  9.    
  10.     <bean id="userList" class="org.springframework.web.servlet.view.JstlView">
  11.         <property name="url" value="/WEB-INF/views/user/list.jsp"/>
  12.     </bean>
  13. </beans>
复制代码

FreeMarkerViewResolver用于解析FreeMarker模板:
  1. @Bean
  2. public FreeMarkerViewResolver freeMarkerViewResolver() {
  3.     FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
  4.     resolver.setPrefix("");  // FreeMarker通常不需要前缀
  5.     resolver.setSuffix(".ftl");  // FreeMarker模板文件后缀
  6.     resolver.setContentType("text/html;charset=UTF-8");
  7.     return resolver;
  8. }
  9. @Bean
  10. public FreeMarkerConfigurer freeMarkerConfigurer() {
  11.     FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
  12.     configurer.setTemplateLoaderPath("/WEB-INF/views/");  // 模板文件路径
  13.     return configurer;
  14. }
复制代码

ThymeleafViewResolver用于解析Thymeleaf模板:
  1. @Bean
  2. public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
  3.     ThymeleafViewResolver resolver = new ThymeleafViewResolver();
  4.     resolver.setTemplateEngine(templateEngine);
  5.     resolver.setCharacterEncoding("UTF-8");
  6.     return resolver;
  7. }
  8. @Bean
  9. public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
  10.     SpringTemplateEngine engine = new SpringTemplateEngine();
  11.     engine.setTemplateResolver(templateResolver);
  12.     return engine;
  13. }
  14. @Bean
  15. public ITemplateResolver templateResolver() {
  16.     SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
  17.     resolver.setPrefix("/WEB-INF/views/");
  18.     resolver.setSuffix(".html");
  19.     resolver.setTemplateMode(TemplateMode.HTML);
  20.     resolver.setCharacterEncoding("UTF-8");
  21.     return resolver;
  22. }
复制代码

ViewResolver的链式处理

Spring MVC支持配置多个ViewResolver,并可以指定它们的优先级。当需要解析视图时,Spring MVC会按照优先级顺序依次调用每个ViewResolver,直到有一个能够解析出View对象为止。
  1. @Configuration
  2. @EnableWebMvc
  3. @ComponentScan("com.example.controller")
  4. public class WebConfig implements WebMvcConfigurer {
  5.    
  6.     @Bean
  7.     public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
  8.         ThymeleafViewResolver resolver = new ThymeleafViewResolver();
  9.         resolver.setTemplateEngine(templateEngine);
  10.         resolver.setCharacterEncoding("UTF-8");
  11.         resolver.setOrder(1);  // 设置优先级,数字越小优先级越高
  12.         return resolver;
  13.     }
  14.    
  15.     @Bean
  16.     public InternalResourceViewResolver internalResourceViewResolver() {
  17.         InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  18.         resolver.setPrefix("/WEB-INF/views/");
  19.         resolver.setSuffix(".jsp");
  20.         resolver.setOrder(2);  // 优先级低于ThymeleafViewResolver
  21.         return resolver;
  22.     }
  23.    
  24.     // 其他Bean定义...
  25. }
复制代码

在上述配置中,当控制器返回一个视图名称时,Spring MVC会首先尝试使用ThymeleafViewResolver进行解析,如果失败,则尝试使用InternalResourceViewResolver。

自定义ViewResolver

如果Spring MVC提供的ViewResolver无法满足需求,我们还可以自定义ViewResolver:
  1. public class CustomViewResolver implements ViewResolver {
  2.    
  3.     private String prefix;
  4.     private String suffix;
  5.    
  6.     public void setPrefix(String prefix) {
  7.         this.prefix = prefix;
  8.     }
  9.    
  10.     public void setSuffix(String suffix) {
  11.         this.suffix = suffix;
  12.     }
  13.    
  14.     @Override
  15.     public View resolveViewName(String viewName, Locale locale) throws Exception {
  16.         // 根据视图名称和本地化信息构建实际视图路径
  17.         String viewPath = prefix + viewName + suffix;
  18.         
  19.         // 这里可以根据实际需求创建不同的View对象
  20.         // 例如,根据文件扩展名决定使用哪种视图技术
  21.         if (viewPath.endsWith(".jsp")) {
  22.             JstlView view = new JstlView();
  23.             view.setUrl(viewPath);
  24.             return view;
  25.         } else if (viewPath.endsWith(".html")) {
  26.             InternalResourceView view = new InternalResourceView();
  27.             view.setUrl(viewPath);
  28.             return view;
  29.         }
  30.         
  31.         // 如果无法解析,返回null,让下一个ViewResolver尝试
  32.         return null;
  33.     }
  34. }
复制代码

然后在配置类中注册自定义的ViewResolver:
  1. @Bean
  2. public CustomViewResolver customViewResolver() {
  3.     CustomViewResolver resolver = new CustomViewResolver();
  4.     resolver.setPrefix("/WEB-INF/views/");
  5.     resolver.setSuffix(".html");
  6.     resolver.setOrder(0);  // 设置最高优先级
  7.     return resolver;
  8. }
复制代码

数据渲染与页面展示流程

完整的请求处理流程

Spring MVC处理请求并渲染页面的完整流程如下:

1. 用户发送请求到DispatcherServlet
2. DispatcherServlet根据请求URL找到对应的HandlerMapping
3. HandlerMapping返回对应的HandlerExecutionChain
4. DispatcherServlet通过HandlerAdapter调用对应的Controller方法
5. Controller方法处理业务逻辑,并返回ModelAndView
6. DispatcherServlet接收ModelAndView,并调用ViewResolver解析视图
7. ViewResolver根据视图名称返回对应的View对象
8. DispatcherServlet将Model数据传递给View对象
9. View对象使用Model数据进行渲染,生成最终的HTML响应
10. DispatcherServlet将HTML响应返回给用户

下面是一个详细流程的示意图:
  1. // DispatcherServlet的简化处理流程
  2. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  3.     // 1. 根据请求获取Handler
  4.     HandlerExecutionChain mappedHandler = getHandler(request);
  5.    
  6.     // 2. 获取HandlerAdapter
  7.     HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  8.    
  9.     // 3. 调用前置拦截器
  10.     if (!mappedHandler.applyPreHandle(request, response)) {
  11.         return;
  12.     }
  13.    
  14.     // 4. 调用Controller方法
  15.     ModelAndView mav = ha.handle(request, response, mappedHandler.getHandler());
  16.    
  17.     // 5. 调用后置拦截器
  18.     mappedHandler.applyPostHandle(request, response, mav);
  19.    
  20.     // 6. 处理分发结果
  21.     processDispatchResult(request, response, mappedHandler, mav);
  22. }
  23. // 处理分发结果
  24. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  25.         HandlerExecutionChain mappedHandler, ModelAndView mav) throws Exception {
  26.     // 渲染视图
  27.     render(mav, request, response);
  28.    
  29.     // 触发完成回调
  30.     mappedHandler.triggerAfterCompletion(request, response, null);
  31. }
  32. // 渲染视图
  33. protected void render(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) throws Exception {
  34.     // 1. 解析视图
  35.     View view = resolveViewName(mav.getViewName(), mav.getModelInternal(), request, response);
  36.    
  37.     // 2. 渲染视图
  38.     if (view != null) {
  39.         view.render(mav.getModelInternal(), request, response);
  40.     }
  41. }
复制代码

数据传递与渲染机制

在Spring MVC中,Model数据可以通过多种方式创建和传递:

方式一:使用ModelAndView
  1. @RequestMapping("/user/{id}")
  2. public ModelAndView getUser(@PathVariable Long id) {
  3.     User user = userService.getUserById(id);
  4.     ModelAndView mav = new ModelAndView();
  5.     mav.addObject("user", user);  // 添加数据到Model
  6.     mav.setViewName("user/detail");
  7.     return mav;
  8. }
复制代码

方式二:使用Model参数
  1. @RequestMapping("/user/{id}")
  2. public String getUser(@PathVariable Long id, Model model) {
  3.     User user = userService.getUserById(id);
  4.     model.addAttribute("user", user);  // 添加数据到Model
  5.     return "user/detail";
  6. }
复制代码

方式三:使用ModelMap参数
  1. @RequestMapping("/user/{id}")
  2. public String getUser(@PathVariable Long id, ModelMap model) {
  3.     User user = userService.getUserById(id);
  4.     model.addAttribute("user", user);  // 添加数据到Model
  5.     return "user/detail";
  6. }
复制代码

方式四:使用Map参数
  1. @RequestMapping("/user/{id}")
  2. public String getUser(@PathVariable Long id, Map<String, Object> model) {
  3.     User user = userService.getUserById(id);
  4.     model.put("user", user);  // 添加数据到Model
  5.     return "user/detail";
  6. }
复制代码

方式五:使用@ModelAttribute注解
  1. @ModelAttribute("user")
  2. public User getUser(@PathVariable Long id) {
  3.     return userService.getUserById(id);
  4. }
  5. @RequestMapping("/user/{id}")
  6. public String getUser() {
  7.     return "user/detail";
  8. }
复制代码

不同的视图技术有不同的渲染机制,下面以JSP和Thymeleaf为例进行说明:

JSP渲染机制

JSP(JavaServer Pages)是一种基于Java的模板技术,它允许在HTML中嵌入Java代码。在Spring MVC中,JSP的渲染过程如下:

1. ViewResolver将视图名称解析为JSP文件路径
2. DispatcherServlet将请求转发到JSP文件
3. JSP引擎解析JSP文件,将其转换为Servlet
4. Servlet执行,将Model数据渲染到HTML中
5. 最终的HTML响应返回给客户端

下面是一个使用JSTL的JSP示例:
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  3. <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7.     <title>User Detail</title>
  8. </head>
  9. <body>
  10.     <h1>User Detail</h1>
  11.    
  12.     <table border="1">
  13.         <tr>
  14.             <th>ID</th>
  15.             <th>Name</th>
  16.             <th>Email</th>
  17.             <th>Registration Date</th>
  18.         </tr>
  19.         <tr>
  20.             <td>${user.id}</td>
  21.             <td>${user.name}</td>
  22.             <td>${user.email}</td>
  23.             <td><fmt:formatDate value="${user.registrationDate}" pattern="yyyy-MM-dd"/></td>
  24.         </tr>
  25.     </table>
  26.    
  27.     <h2>Orders</h2>
  28.     <table border="1">
  29.         <tr>
  30.             <th>ID</th>
  31.             <th>Product</th>
  32.             <th>Amount</th>
  33.             <th>Date</th>
  34.         </tr>
  35.         <c:forEach items="${orders}" var="order">
  36.             <tr>
  37.                 <td>${order.id}</td>
  38.                 <td>${order.product}</td>
  39.                 <td><fmt:formatNumber value="${order.amount}" type="currency"/></td>
  40.                 <td><fmt:formatDate value="${order.date}" pattern="yyyy-MM-dd"/></td>
  41.             </tr>
  42.         </c:forEach>
  43.     </table>
  44. </body>
  45. </html>
复制代码

Thymeleaf渲染机制

Thymeleaf是一种现代的服务器端Java模板引擎,它强调自然模板的概念,允许模板作为静态原型工作。在Spring MVC中,Thymeleaf的渲染过程如下:

1. ViewResolver将视图名称解析为Thymeleaf模板文件
2. Thymeleaf引擎解析模板文件,构建DOM树
3. Thymeleaf引擎将Model数据与DOM树结合,进行数据绑定和逻辑处理
4. 最终的HTML响应返回给客户端

下面是一个使用Thymeleaf的HTML模板示例:
  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4.     <title>User Detail</title>
  5.     <meta charset="UTF-8"/>
  6. </head>
  7. <body>
  8.     <h1>User Detail</h1>
  9.    
  10.     <table border="1">
  11.         <tr>
  12.             <th>ID</th>
  13.             <th>Name</th>
  14.             <th>Email</th>
  15.             <th>Registration Date</th>
  16.         </tr>
  17.         <tr>
  18.             <td th:text="${user.id}">1</td>
  19.             <td th:text="${user.name}">John Doe</td>
  20.             <td th:text="${user.email}">john@example.com</td>
  21.             <td th:text="${#dates.format(user.registrationDate, 'yyyy-MM-dd')}">2023-01-01</td>
  22.         </tr>
  23.     </table>
  24.    
  25.     <h2>Orders</h2>
  26.     <table border="1">
  27.         <tr>
  28.             <th>ID</th>
  29.             <th>Product</th>
  30.             <th>Amount</th>
  31.             <th>Date</th>
  32.         </tr>
  33.         <tr th:each="order : ${orders}">
  34.             <td th:text="${order.id}">1</td>
  35.             <td th:text="${order.product}">Product A</td>
  36.             <td th:text="${#numbers.formatCurrency(order.amount)}">$100.00</td>
  37.             <td th:text="${#dates.format(order.date, 'yyyy-MM-dd')}">2023-01-01</td>
  38.         </tr>
  39.     </table>
  40. </body>
  41. </html>
复制代码

视图解析与渲染的细节

视图名称解析是ViewResolver的核心功能,下面是一个简化的视图名称解析过程:
  1. // InternalResourceViewResolver的简化解析过程
  2. public View resolveViewName(String viewName, Locale locale) throws Exception {
  3.     // 构建实际视图路径
  4.     String path = getPrefix() + viewName + getSuffix();
  5.    
  6.     // 创建View对象
  7.     AbstractUrlBasedView view = buildView(path);
  8.    
  9.     // 应用其他配置
  10.     view.setApplicationContext(getApplicationContext());
  11.    
  12.     return view;
  13. }
  14. protected AbstractUrlBasedView buildView(String viewUrl) throws Exception {
  15.     // 实例化View类
  16.     AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
  17.    
  18.     // 设置URL
  19.     view.setUrl(viewUrl);
  20.    
  21.     // 设置其他属性
  22.     if (getContentType() != null) {
  23.         view.setContentType(getContentType());
  24.     }
  25.    
  26.     return view;
  27. }
复制代码

视图渲染是View的核心功能,下面是一个简化的视图渲染过程:
  1. // InternalResourceView的简化渲染过程
  2. public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  3.     // 暴露Model数据到请求属性中
  4.     exposeModelAsRequestAttributes(model, request);
  5.    
  6.     // 准备请求
  7.     prepareRequest(request);
  8.    
  9.     // 获取请求分发器
  10.     RequestDispatcher rd = getRequestDispatcher(request, getUrl());
  11.    
  12.     // 如果是包含请求,则包含资源
  13.     if (useInclude(request, response)) {
  14.         response.setContentType(getContentType());
  15.         rd.include(request, response);
  16.     }
  17.     // 否则转发请求
  18.     else {
  19.         rd.forward(request, response);
  20.     }
  21. }
  22. protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
  23.     // 将Model中的数据暴露为请求属性
  24.     for (Map.Entry<String, Object> entry : model.entrySet()) {
  25.         String modelName = entry.getKey();
  26.         Object modelValue = entry.getValue();
  27.         if (modelValue != null) {
  28.             request.setAttribute(modelName, modelValue);
  29.         }
  30.     }
  31. }
复制代码

在Spring MVC中,我们可以通过返回特定的视图名称来实现重定向和转发:

重定向
  1. @RequestMapping("/save")
  2. public String saveUser(User user) {
  3.     userService.saveUser(user);
  4.     // 使用"redirect:"前缀实现重定向
  5.     return "redirect:/user/list";
  6. }
复制代码

转发
  1. @RequestMapping("/process")
  2. public String processForm() {
  3.     // 使用"forward:"前缀实现转发
  4.     return "forward:/user/list";
  5. }
复制代码

重定向和转发的区别:

1. 重定向是客户端行为,浏览器地址栏会改变;转发是服务器端行为,浏览器地址栏不会改变
2. 重定向会丢失请求属性;转发会保留请求属性
3. 重定向可以重定向到任意URL;转发只能在同一应用内进行

实战技巧

1. 使用多个ViewResolver处理不同类型的视图

在实际项目中,我们可能需要同时使用多种视图技术,例如使用Thymeleaf处理HTML页面,使用JSP处理报表,使用JSON处理API响应等。这时,我们可以配置多个ViewResolver,并设置它们的优先级:
  1. @Configuration
  2. @EnableWebMvc
  3. public class WebConfig implements WebMvcConfigurer {
  4.    
  5.     @Bean
  6.     public ContentNegotiatingViewResolver contentViewResolver() {
  7.         ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
  8.         
  9.         // 设置内容协商策略
  10.         ContentNegotiationManager manager = new ContentNegotiationManager(
  11.             new HeaderContentNegotiationStrategy(),
  12.             new FixedContentNegotiationStrategy(MediaType.TEXT_HTML)
  13.         );
  14.         resolver.setContentNegotiationManager(manager);
  15.         
  16.         // 设置默认视图
  17.         resolver.setDefaultViews(Arrays.asList(new MappingJackson2JsonView()));
  18.         
  19.         // 设置ViewResolver列表
  20.         List<ViewResolver> resolvers = new ArrayList<>();
  21.         resolvers.add(thymeleafViewResolver());
  22.         resolvers.add(jspViewResolver());
  23.         resolver.setViewResolvers(resolvers);
  24.         
  25.         return resolver;
  26.     }
  27.    
  28.     @Bean
  29.     public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
  30.         ThymeleafViewResolver resolver = new ThymeleafViewResolver();
  31.         resolver.setTemplateEngine(templateEngine);
  32.         resolver.setCharacterEncoding("UTF-8");
  33.         resolver.setOrder(1);
  34.         return resolver;
  35.     }
  36.    
  37.     @Bean
  38.     public InternalResourceViewResolver jspViewResolver() {
  39.         InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  40.         resolver.setPrefix("/WEB-INF/jsp/");
  41.         resolver.setSuffix(".jsp");
  42.         resolver.setViewClass(JstlView.class);
  43.         resolver.setOrder(2);
  44.         return resolver;
  45.     }
  46.    
  47.     // 其他Bean定义...
  48. }
复制代码

2. 使用ContentNegotiatingViewResolver实现内容协商

ContentNegotiatingViewResolver是Spring MVC提供的一个特殊ViewResolver,它可以根据请求的Accept头、文件扩展名或请求参数来决定使用哪种视图技术:
  1. @Configuration
  2. @EnableWebMvc
  3. public class WebConfig implements WebMvcConfigurer {
  4.    
  5.     @Bean
  6.     public ContentNegotiatingViewResolver contentViewResolver() {
  7.         ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
  8.         
  9.         // 设置内容协商策略
  10.         ContentNegotiationManager manager = new ContentNegotiationManager(
  11.             new HeaderContentNegotiationStrategy(),
  12.             new ParameterContentNegotiationStrategy(),
  13.             new PathExtensionContentNegotiationStrategy()
  14.         );
  15.         resolver.setContentNegotiationManager(manager);
  16.         
  17.         // 设置默认内容类型
  18.         resolver.setDefaultContentType(MediaType.APPLICATION_JSON);
  19.         
  20.         // 设置媒体类型映射
  21.         Map<String, MediaType> mediaTypes = new HashMap<>();
  22.         mediaTypes.put("html", MediaType.TEXT_HTML);
  23.         mediaTypes.put("json", MediaType.APPLICATION_JSON);
  24.         mediaTypes.put("xml", MediaType.APPLICATION_XML);
  25.         manager.addMediaTypeMappings(mediaTypes);
  26.         
  27.         // 设置ViewResolver列表
  28.         List<ViewResolver> resolvers = new ArrayList<>();
  29.         resolvers.add(thymeleafViewResolver());
  30.         resolvers.add(jsonViewResolver());
  31.         resolvers.add(xmlViewResolver());
  32.         resolver.setViewResolvers(resolvers);
  33.         
  34.         return resolver;
  35.     }
  36.    
  37.     @Bean
  38.     public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
  39.         ThymeleafViewResolver resolver = new ThymeleafViewResolver();
  40.         resolver.setTemplateEngine(templateEngine);
  41.         resolver.setCharacterEncoding("UTF-8");
  42.         resolver.setContentType(MediaType.TEXT_HTML_VALUE);
  43.         return resolver;
  44.     }
  45.    
  46.     @Bean
  47.     public ViewResolver jsonViewResolver() {
  48.         return new ViewResolver() {
  49.             @Override
  50.             public View resolveViewName(String viewName, Locale locale) throws Exception {
  51.                 MappingJackson2JsonView view = new MappingJackson2JsonView();
  52.                 view.setPrettyPrint(true);
  53.                 return view;
  54.             }
  55.         };
  56.     }
  57.    
  58.     @Bean
  59.     public ViewResolver xmlViewResolver() {
  60.         return new ViewResolver() {
  61.             @Override
  62.             public View resolveViewName(String viewName, Locale locale) throws Exception {
  63.                 MarshallingView view = new MarshallingView(jaxb2Marshaller());
  64.                 return view;
  65.             }
  66.         };
  67.     }
  68.    
  69.     @Bean
  70.     public Jaxb2Marshaller jaxb2Marshaller() {
  71.         Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
  72.         marshaller.setPackagesToScan("com.example.model");
  73.         return marshaller;
  74.     }
  75. }
复制代码

3. 使用异常处理器自定义错误页面

在Spring MVC中,我们可以通过@ExceptionHandler和@ControllerAdvice来处理异常并返回自定义的错误页面:
  1. @ControllerAdvice
  2. public class GlobalExceptionHandler {
  3.    
  4.     // 处理特定异常
  5.     @ExceptionHandler(UserNotFoundException.class)
  6.     public ModelAndView handleUserNotFoundException(UserNotFoundException ex) {
  7.         ModelAndView mav = new ModelAndView();
  8.         mav.addObject("errorMessage", ex.getMessage());
  9.         mav.addObject("errorCode", "404");
  10.         mav.setViewName("error/404");
  11.         return mav;
  12.     }
  13.    
  14.     // 处理所有异常
  15.     @ExceptionHandler(Exception.class)
  16.     public ModelAndView handleAllExceptions(Exception ex) {
  17.         ModelAndView mav = new ModelAndView();
  18.         mav.addObject("errorMessage", ex.getMessage());
  19.         mav.addObject("errorCode", "500");
  20.         mav.setViewName("error/500");
  21.         return mav;
  22.     }
  23. }
复制代码

4. 使用拦截器预处理和后处理Model数据

Spring MVC的拦截器不仅可以用于权限验证、日志记录等,还可以用于预处理和后处理Model数据:
  1. public class ModelDataInterceptor implements HandlerInterceptor {
  2.    
  3.     @Override
  4.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5.         // 在控制器方法执行前执行
  6.         return true;
  7.     }
  8.    
  9.     @Override
  10.     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav) throws Exception {
  11.         // 在控制器方法执行后,视图渲染前执行
  12.         
  13.         if (mav != null) {
  14.             // 添加全局数据
  15.             mav.addObject("currentTime", new Date());
  16.             mav.addObject("applicationName", "Spring MVC Demo");
  17.             
  18.             // 处理Model数据
  19.             Map<String, Object> model = mav.getModel();
  20.             for (Map.Entry<String, Object> entry : model.entrySet()) {
  21.                 // 对特定数据进行处理
  22.                 if (entry.getValue() instanceof String) {
  23.                     String value = (String) entry.getValue();
  24.                     if (value != null && value.length() > 100) {
  25.                         // 截断过长的字符串
  26.                         model.put(entry.getKey(), value.substring(0, 100) + "...");
  27.                     }
  28.                 }
  29.             }
  30.         }
  31.     }
  32.    
  33.     @Override
  34.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  35.         // 在视图渲染后执行
  36.     }
  37. }
复制代码

然后在配置类中注册拦截器:
  1. @Configuration
  2. @EnableWebMvc
  3. public class WebConfig implements WebMvcConfigurer {
  4.    
  5.     @Override
  6.     public void addInterceptors(InterceptorRegistry registry) {
  7.         registry.addInterceptor(new ModelDataInterceptor())
  8.                 .addPathPatterns("/**")
  9.                 .excludePathPatterns("/static/**");
  10.     }
  11.    
  12.     // 其他配置...
  13. }
复制代码

5. 使用Flash属性传递重定向数据

在Spring MVC中,重定向会导致请求属性丢失,但我们可以使用Flash属性在重定向之间传递数据:
  1. @Controller
  2. @RequestMapping("/user")
  3. public class UserController {
  4.    
  5.     @PostMapping("/save")
  6.     public String saveUser(User user, RedirectAttributes redirectAttributes) {
  7.         // 保存用户
  8.         userService.saveUser(user);
  9.         
  10.         // 添加Flash属性
  11.         redirectAttributes.addFlashAttribute("message", "User saved successfully!");
  12.         redirectAttributes.addFlashAttribute("user", user);
  13.         
  14.         // 重定向到列表页面
  15.         return "redirect:/user/list";
  16.     }
  17.    
  18.     @GetMapping("/list")
  19.     public String listUsers(Model model) {
  20.         // Flash属性会自动添加到Model中
  21.         // 不需要额外处理
  22.         
  23.         // 获取用户列表
  24.         List<User> users = userService.getAllUsers();
  25.         model.addAttribute("users", users);
  26.         
  27.         return "user/list";
  28.     }
  29. }
复制代码

6. 使用自定义视图处理特殊需求

有时,标准的视图技术无法满足特殊需求,这时我们可以自定义视图:
  1. public class PdfView extends AbstractView {
  2.    
  3.     public PdfView() {
  4.         setContentType("application/pdf");
  5.     }
  6.    
  7.     @Override
  8.     protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  9.         // 设置响应头
  10.         response.setContentType(getContentType());
  11.         response.setHeader("Content-Disposition", "attachment; filename="report.pdf"");
  12.         
  13.         // 获取数据
  14.         List<User> users = (List<User>) model.get("users");
  15.         
  16.         // 创建PDF文档
  17.         Document document = new Document();
  18.         PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream());
  19.         
  20.         document.open();
  21.         
  22.         // 添加标题
  23.         Font titleFont = new Font(Font.FontFamily.HELVETICA, 18, Font.BOLD);
  24.         Paragraph title = new Paragraph("User Report", titleFont);
  25.         title.setAlignment(Element.ALIGN_CENTER);
  26.         document.add(title);
  27.         
  28.         // 添加表格
  29.         PdfPTable table = new PdfPTable(3);
  30.         table.setWidthPercentage(100);
  31.         
  32.         // 添加表头
  33.         table.addCell(createCell("ID", true));
  34.         table.addCell(createCell("Name", true));
  35.         table.addCell(createCell("Email", true));
  36.         
  37.         // 添加数据行
  38.         for (User user : users) {
  39.             table.addCell(createCell(String.valueOf(user.getId()), false));
  40.             table.addCell(createCell(user.getName(), false));
  41.             table.addCell(createCell(user.getEmail(), false));
  42.         }
  43.         
  44.         document.add(table);
  45.         document.close();
  46.         writer.close();
  47.     }
  48.    
  49.     private PdfPCell createCell(String content, boolean isHeader) {
  50.         Font font = isHeader ?
  51.             new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD) :
  52.             new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL);
  53.         
  54.         PdfPCell cell = new PdfPCell(new Phrase(content, font));
  55.         cell.setHorizontalAlignment(Element.ALIGN_CENTER);
  56.         cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
  57.         cell.setPadding(5);
  58.         
  59.         if (isHeader) {
  60.             cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
  61.         }
  62.         
  63.         return cell;
  64.     }
  65. }
复制代码

然后在控制器中使用自定义视图:
  1. @Controller
  2. @RequestMapping("/report")
  3. public class ReportController {
  4.    
  5.     @Autowired
  6.     private UserService userService;
  7.    
  8.     @GetMapping("/users.pdf")
  9.     public View generateUserReport() {
  10.         // 创建自定义视图
  11.         PdfView view = new PdfView();
  12.         
  13.         // 准备数据
  14.         List<User> users = userService.getAllUsers();
  15.         
  16.         // 添加静态属性
  17.         Map<String, Object> model = new HashMap<>();
  18.         model.put("users", users);
  19.         view.setAttributesMap(model);
  20.         
  21.         return view;
  22.     }
  23. }
复制代码

7. 使用Tiles框架构建复合视图

Apache Tiles是一个模板框架,允许构建可重用的页面组件。在Spring MVC中集成Tiles:

首先,添加Tiles依赖:
  1. <dependency>
  2.     <groupId>org.apache.tiles</groupId>
  3.     <artifactId>tiles-jsp</artifactId>
  4.     <version>3.0.8</version>
  5. </dependency>
复制代码

然后,配置Tiles:
  1. @Configuration
  2. @EnableWebMvc
  3. @ComponentScan("com.example.controller")
  4. public class WebConfig implements WebMvcConfigurer {
  5.    
  6.     @Bean
  7.     public TilesConfigurer tilesConfigurer() {
  8.         TilesConfigurer configurer = new TilesConfigurer();
  9.         configurer.setDefinitions("/WEB-INF/tiles/tiles-defs.xml");
  10.         configurer.setCheckRefresh(true);
  11.         return configurer;
  12.     }
  13.    
  14.     @Bean
  15.     public TilesViewResolver tilesViewResolver() {
  16.         TilesViewResolver resolver = new TilesViewResolver();
  17.         resolver.setViewClass(TilesView.class);
  18.         return resolver;
  19.     }
  20.    
  21.     // 其他配置...
  22. }
复制代码

定义Tiles配置文件(tiles-defs.xml):
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE tiles-definitions PUBLIC
  3.        "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
  4.        "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
  5. <tiles-definitions>
  6.    
  7.     <!-- 基础模板 -->
  8.     <definition name="base" template="/WEB-INF/tiles/layout.jsp">
  9.         <put-attribute name="header" value="/WEB-INF/tiles/header.jsp" />
  10.         <put-attribute name="menu" value="/WEB-INF/tiles/menu.jsp" />
  11.         <put-attribute name="body" value="" />
  12.         <put-attribute name="footer" value="/WEB-INF/tiles/footer.jsp" />
  13.     </definition>
  14.    
  15.     <!-- 首页 -->
  16.     <definition name="home" extends="base">
  17.         <put-attribute name="body" value="/WEB-INF/views/home.jsp" />
  18.         <put-attribute name="title" value="Home Page" />
  19.     </definition>
  20.    
  21.     <!-- 用户列表 -->
  22.     <definition name="user.list" extends="base">
  23.         <put-attribute name="body" value="/WEB-INF/views/user/list.jsp" />
  24.         <put-attribute name="title" value="User List" />
  25.     </definition>
  26.    
  27.     <!-- 用户详情 -->
  28.     <definition name="user.detail" extends="base">
  29.         <put-attribute name="body" value="/WEB-INF/views/user/detail.jsp" />
  30.         <put-attribute name="title" value="User Detail" />
  31.     </definition>
  32.    
  33. </tiles-definitions>
复制代码

创建基础模板(layout.jsp):
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
  3. <!DOCTYPE html>
  4. <html>
  5. <head>
  6.     <title><tiles:getAsString name="title"/></title>
  7.     <meta charset="UTF-8"/>
  8. </head>
  9. <body>
  10.     <div id="header">
  11.         <tiles:insertAttribute name="header"/>
  12.     </div>
  13.    
  14.     <div id="menu">
  15.         <tiles:insertAttribute name="menu"/>
  16.     </div>
  17.    
  18.     <div id="body">
  19.         <tiles:insertAttribute name="body"/>
  20.     </div>
  21.    
  22.     <div id="footer">
  23.         <tiles:insertAttribute name="footer"/>
  24.     </div>
  25. </body>
  26. </html>
复制代码

最后,在控制器中返回Tiles视图名称:
  1. @Controller
  2. @RequestMapping("/user")
  3. public class UserController {
  4.    
  5.     @Autowired
  6.     private UserService userService;
  7.    
  8.     @RequestMapping("/list")
  9.     public String listUsers(Model model) {
  10.         List<User> users = userService.getAllUsers();
  11.         model.addAttribute("users", users);
  12.         return "user.list";  // 返回Tiles视图名称
  13.     }
  14.    
  15.     @RequestMapping("/detail/{id}")
  16.     public String getUserDetail(@PathVariable Long id, Model model) {
  17.         User user = userService.getUserById(id);
  18.         model.addAttribute("user", user);
  19.         return "user.detail";  // 返回Tiles视图名称
  20.     }
  21. }
复制代码

总结

本文全面解析了Spring MVC中的页面输出技术,从ModelAndView到ViewResolver,详细介绍了数据渲染与页面展示的完整流程及实战技巧。

我们首先了解了ModelAndView的基本概念和使用方式,掌握了如何在控制器中准备数据和指定视图。然后,我们深入探讨了ViewResolver的工作机制,学习了Spring MVC提供的多种ViewResolver实现,以及如何配置和使用它们。

接着,我们详细分析了Spring MVC中的数据渲染与页面展示流程,包括完整的请求处理流程、数据传递与渲染机制,以及视图解析与渲染的细节。最后,我们分享了一些实用的实战技巧,如使用多个ViewResolver处理不同类型的视图、使用ContentNegotiatingViewResolver实现内容协商、使用异常处理器自定义错误页面、使用拦截器预处理和后处理Model数据、使用Flash属性传递重定向数据、使用自定义视图处理特殊需求,以及使用Tiles框架构建复合视图。

通过本文的学习,读者应该能够深入理解Spring MVC的页面输出技术,并能够灵活运用这些技术解决实际开发中的问题。希望本文能够帮助读者在Spring MVC开发中更加得心应手,构建出更加优秀的Web应用。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

手机版|联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.

>