|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
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. 只指定视图名
- ModelAndView mav = new ModelAndView("viewName");
- // 2. 同时指定视图名和Model
- Map<String, Object> model = new HashMap<>();
- model.put("key1", "value1");
- model.put("key2", "value2");
- ModelAndView mav = new ModelAndView("viewName", model);
- // 3. 指定视图名并添加单个模型数据
- ModelAndView mav = new ModelAndView("viewName", "modelName", modelObject);
- // 4. 创建空的ModelAndView,后续再设置
- ModelAndView mav = new ModelAndView();
- mav.setViewName("viewName");
- mav.addObject("key1", "value1");
- mav.addObject("key2", "value2");
复制代码
下面是一个完整的Controller示例,展示如何使用ModelAndView:
- @Controller
- @RequestMapping("/user")
- public class UserController {
-
- @Autowired
- private UserService userService;
-
- @RequestMapping("/detail/{id}")
- public ModelAndView getUserDetail(@PathVariable("id") Long id) {
- // 创建ModelAndView对象
- ModelAndView mav = new ModelAndView();
-
- // 获取用户数据
- User user = userService.getUserById(id);
-
- // 添加模型数据
- mav.addObject("user", user);
-
- // 获取用户的订单列表
- List<Order> orders = orderService.getOrdersByUserId(id);
- mav.addObject("orders", orders);
-
- // 设置视图名称
- mav.setViewName("user/detail");
-
- return mav;
- }
-
- @RequestMapping("/list")
- public ModelAndView getUserList(
- @RequestParam(defaultValue = "1") int page,
- @RequestParam(defaultValue = "10") int size) {
-
- // 分页获取用户列表
- Page<User> userPage = userService.getUserList(page, size);
-
- // 使用另一种方式创建ModelAndView
- return new ModelAndView("user/list", "page", userPage);
- }
- }
复制代码
ModelAndView提供了一些常用方法来操作模型数据和视图:
- // 添加模型数据
- mav.addObject("key", value); // 添加单个数据
- mav.addAllObjects(map); // 批量添加数据
- // 获取模型数据
- Map<String, Object> model = mav.getModel(); // 获取整个模型
- Object value = mav.getModel().get("key"); // 获取特定数据
- // 设置和获取视图
- mav.setViewName("viewName"); // 设置视图名称
- String viewName = mav.getViewName(); // 获取视图名称
- View view = mav.getView(); // 获取View对象
- // 设置视图状态
- mav.setStatus(HttpStatus.OK); // 设置HTTP状态码
- mav.clear(); // 清除ModelAndView中的所有数据
复制代码
ModelAndView的工作原理
当控制器方法返回一个ModelAndView对象后,DispatcherServlet会进行以下处理:
1. 获取ModelAndView中的Model数据,并将其合并到临时的ModelMap中
2. 获取ModelAndView中的View信息(可以是视图名称或View对象)
3. 如果是视图名称,则使用ViewResolver解析为具体的View对象
4. 将Model数据传递给View对象,进行渲染
5. 返回渲染后的响应结果
下面是一个简化版的处理流程示意图:
- // DispatcherServlet处理ModelAndView的简化流程
- protected void render(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) throws Exception {
- // 1. 获取视图
- View view = resolveViewName(mav.getViewName(), mav.getModelInternal(), request, response);
-
- // 2. 渲染视图
- if (view != null) {
- view.render(mav.getModelInternal(), request, response);
- }
- }
复制代码
ViewResolver详解
ViewResolver的基本概念
ViewResolver是Spring MVC中负责将视图名称解析为具体View对象的组件。当控制器返回一个视图名称时,ViewResolver会根据这个名称找到对应的View实现,如JSP、Thymeleaf、FreeMarker等。
ViewResolver的接口定义
ViewResolver接口非常简单,只定义了一个方法:
- public interface ViewResolver {
- View resolveViewName(String viewName, Locale locale) throws Exception;
- }
复制代码
其中,viewName是控制器返回的视图名称,locale是本地化信息,返回值是解析后的View对象。
Spring MVC提供的ViewResolver实现
Spring MVC提供了多种ViewResolver的实现,以适应不同的视图技术:
InternalResourceViewResolver是最常用的ViewResolver之一,主要用于解析JSP和HTML等内部资源视图:
- @Configuration
- @EnableWebMvc
- @ComponentScan("com.example.controller")
- public class WebConfig implements WebMvcConfigurer {
-
- @Bean
- public InternalResourceViewResolver viewResolver() {
- InternalResourceViewResolver resolver = new InternalResourceViewResolver();
- resolver.setPrefix("/WEB-INF/views/"); // 设置视图前缀
- resolver.setSuffix(".jsp"); // 设置视图后缀
- resolver.setViewClass(JstlView.class); // 设置视图类,支持JSTL
- return resolver;
- }
- }
复制代码
配置完成后,当控制器返回”user/detail”时,InternalResourceViewResolver会将其解析为”/WEB-INF/views/user/detail.jsp”。
UrlBasedViewResolver是InternalResourceViewResolver的父类,提供了一种更简单的方式来解析视图:
- @Bean
- public UrlBasedViewResolver urlBasedViewResolver() {
- UrlBasedViewResolver resolver = new UrlBasedViewResolver();
- resolver.setPrefix("/WEB-INF/views/");
- resolver.setSuffix(".jsp");
- resolver.setViewClass(JstlView.class);
- return resolver;
- }
复制代码
ResourceBundleViewResolver允许我们在属性文件中定义视图名称和对应的View类:
- @Bean
- public ResourceBundleViewResolver resourceBundleViewResolver() {
- ResourceBundleViewResolver resolver = new ResourceBundleViewResolver();
- resolver.setBasename("views"); // 指定属性文件的基础名称
- return resolver;
- }
复制代码
然后在classpath根目录下的views.properties文件中定义视图映射:
- user.detail.(class)=org.springframework.web.servlet.view.JstlView
- user.detail.url=/WEB-INF/views/user/detail.jsp
- user.list.(class)=org.springframework.web.servlet.view.JstlView
- user.list.url=/WEB-INF/views/user/list.jsp
复制代码
XmlViewResolver允许我们在XML配置文件中定义视图:
- @Bean
- public XmlViewResolver xmlViewResolver() {
- XmlViewResolver resolver = new XmlViewResolver();
- resolver.setLocation(new ClassPathResource("views.xml")); // 指定XML配置文件
- return resolver;
- }
复制代码
然后在views.xml文件中定义视图:
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd">
-
- <bean id="userDetail" class="org.springframework.web.servlet.view.JstlView">
- <property name="url" value="/WEB-INF/views/user/detail.jsp"/>
- </bean>
-
- <bean id="userList" class="org.springframework.web.servlet.view.JstlView">
- <property name="url" value="/WEB-INF/views/user/list.jsp"/>
- </bean>
- </beans>
复制代码
FreeMarkerViewResolver用于解析FreeMarker模板:
- @Bean
- public FreeMarkerViewResolver freeMarkerViewResolver() {
- FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
- resolver.setPrefix(""); // FreeMarker通常不需要前缀
- resolver.setSuffix(".ftl"); // FreeMarker模板文件后缀
- resolver.setContentType("text/html;charset=UTF-8");
- return resolver;
- }
- @Bean
- public FreeMarkerConfigurer freeMarkerConfigurer() {
- FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
- configurer.setTemplateLoaderPath("/WEB-INF/views/"); // 模板文件路径
- return configurer;
- }
复制代码
ThymeleafViewResolver用于解析Thymeleaf模板:
- @Bean
- public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
- ThymeleafViewResolver resolver = new ThymeleafViewResolver();
- resolver.setTemplateEngine(templateEngine);
- resolver.setCharacterEncoding("UTF-8");
- return resolver;
- }
- @Bean
- public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
- SpringTemplateEngine engine = new SpringTemplateEngine();
- engine.setTemplateResolver(templateResolver);
- return engine;
- }
- @Bean
- public ITemplateResolver templateResolver() {
- SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
- resolver.setPrefix("/WEB-INF/views/");
- resolver.setSuffix(".html");
- resolver.setTemplateMode(TemplateMode.HTML);
- resolver.setCharacterEncoding("UTF-8");
- return resolver;
- }
复制代码
ViewResolver的链式处理
Spring MVC支持配置多个ViewResolver,并可以指定它们的优先级。当需要解析视图时,Spring MVC会按照优先级顺序依次调用每个ViewResolver,直到有一个能够解析出View对象为止。
- @Configuration
- @EnableWebMvc
- @ComponentScan("com.example.controller")
- public class WebConfig implements WebMvcConfigurer {
-
- @Bean
- public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
- ThymeleafViewResolver resolver = new ThymeleafViewResolver();
- resolver.setTemplateEngine(templateEngine);
- resolver.setCharacterEncoding("UTF-8");
- resolver.setOrder(1); // 设置优先级,数字越小优先级越高
- return resolver;
- }
-
- @Bean
- public InternalResourceViewResolver internalResourceViewResolver() {
- InternalResourceViewResolver resolver = new InternalResourceViewResolver();
- resolver.setPrefix("/WEB-INF/views/");
- resolver.setSuffix(".jsp");
- resolver.setOrder(2); // 优先级低于ThymeleafViewResolver
- return resolver;
- }
-
- // 其他Bean定义...
- }
复制代码
在上述配置中,当控制器返回一个视图名称时,Spring MVC会首先尝试使用ThymeleafViewResolver进行解析,如果失败,则尝试使用InternalResourceViewResolver。
自定义ViewResolver
如果Spring MVC提供的ViewResolver无法满足需求,我们还可以自定义ViewResolver:
- public class CustomViewResolver implements ViewResolver {
-
- private String prefix;
- private String suffix;
-
- public void setPrefix(String prefix) {
- this.prefix = prefix;
- }
-
- public void setSuffix(String suffix) {
- this.suffix = suffix;
- }
-
- @Override
- public View resolveViewName(String viewName, Locale locale) throws Exception {
- // 根据视图名称和本地化信息构建实际视图路径
- String viewPath = prefix + viewName + suffix;
-
- // 这里可以根据实际需求创建不同的View对象
- // 例如,根据文件扩展名决定使用哪种视图技术
- if (viewPath.endsWith(".jsp")) {
- JstlView view = new JstlView();
- view.setUrl(viewPath);
- return view;
- } else if (viewPath.endsWith(".html")) {
- InternalResourceView view = new InternalResourceView();
- view.setUrl(viewPath);
- return view;
- }
-
- // 如果无法解析,返回null,让下一个ViewResolver尝试
- return null;
- }
- }
复制代码
然后在配置类中注册自定义的ViewResolver:
- @Bean
- public CustomViewResolver customViewResolver() {
- CustomViewResolver resolver = new CustomViewResolver();
- resolver.setPrefix("/WEB-INF/views/");
- resolver.setSuffix(".html");
- resolver.setOrder(0); // 设置最高优先级
- return resolver;
- }
复制代码
数据渲染与页面展示流程
完整的请求处理流程
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响应返回给用户
下面是一个详细流程的示意图:
- // DispatcherServlet的简化处理流程
- protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
- // 1. 根据请求获取Handler
- HandlerExecutionChain mappedHandler = getHandler(request);
-
- // 2. 获取HandlerAdapter
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
-
- // 3. 调用前置拦截器
- if (!mappedHandler.applyPreHandle(request, response)) {
- return;
- }
-
- // 4. 调用Controller方法
- ModelAndView mav = ha.handle(request, response, mappedHandler.getHandler());
-
- // 5. 调用后置拦截器
- mappedHandler.applyPostHandle(request, response, mav);
-
- // 6. 处理分发结果
- processDispatchResult(request, response, mappedHandler, mav);
- }
- // 处理分发结果
- private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
- HandlerExecutionChain mappedHandler, ModelAndView mav) throws Exception {
- // 渲染视图
- render(mav, request, response);
-
- // 触发完成回调
- mappedHandler.triggerAfterCompletion(request, response, null);
- }
- // 渲染视图
- protected void render(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) throws Exception {
- // 1. 解析视图
- View view = resolveViewName(mav.getViewName(), mav.getModelInternal(), request, response);
-
- // 2. 渲染视图
- if (view != null) {
- view.render(mav.getModelInternal(), request, response);
- }
- }
复制代码
数据传递与渲染机制
在Spring MVC中,Model数据可以通过多种方式创建和传递:
方式一:使用ModelAndView
- @RequestMapping("/user/{id}")
- public ModelAndView getUser(@PathVariable Long id) {
- User user = userService.getUserById(id);
- ModelAndView mav = new ModelAndView();
- mav.addObject("user", user); // 添加数据到Model
- mav.setViewName("user/detail");
- return mav;
- }
复制代码
方式二:使用Model参数
- @RequestMapping("/user/{id}")
- public String getUser(@PathVariable Long id, Model model) {
- User user = userService.getUserById(id);
- model.addAttribute("user", user); // 添加数据到Model
- return "user/detail";
- }
复制代码
方式三:使用ModelMap参数
- @RequestMapping("/user/{id}")
- public String getUser(@PathVariable Long id, ModelMap model) {
- User user = userService.getUserById(id);
- model.addAttribute("user", user); // 添加数据到Model
- return "user/detail";
- }
复制代码
方式四:使用Map参数
- @RequestMapping("/user/{id}")
- public String getUser(@PathVariable Long id, Map<String, Object> model) {
- User user = userService.getUserById(id);
- model.put("user", user); // 添加数据到Model
- return "user/detail";
- }
复制代码
方式五:使用@ModelAttribute注解
- @ModelAttribute("user")
- public User getUser(@PathVariable Long id) {
- return userService.getUserById(id);
- }
- @RequestMapping("/user/{id}")
- public String getUser() {
- return "user/detail";
- }
复制代码
不同的视图技术有不同的渲染机制,下面以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示例:
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
- <!DOCTYPE html>
- <html>
- <head>
- <title>User Detail</title>
- </head>
- <body>
- <h1>User Detail</h1>
-
- <table border="1">
- <tr>
- <th>ID</th>
- <th>Name</th>
- <th>Email</th>
- <th>Registration Date</th>
- </tr>
- <tr>
- <td>${user.id}</td>
- <td>${user.name}</td>
- <td>${user.email}</td>
- <td><fmt:formatDate value="${user.registrationDate}" pattern="yyyy-MM-dd"/></td>
- </tr>
- </table>
-
- <h2>Orders</h2>
- <table border="1">
- <tr>
- <th>ID</th>
- <th>Product</th>
- <th>Amount</th>
- <th>Date</th>
- </tr>
- <c:forEach items="${orders}" var="order">
- <tr>
- <td>${order.id}</td>
- <td>${order.product}</td>
- <td><fmt:formatNumber value="${order.amount}" type="currency"/></td>
- <td><fmt:formatDate value="${order.date}" pattern="yyyy-MM-dd"/></td>
- </tr>
- </c:forEach>
- </table>
- </body>
- </html>
复制代码
Thymeleaf渲染机制
Thymeleaf是一种现代的服务器端Java模板引擎,它强调自然模板的概念,允许模板作为静态原型工作。在Spring MVC中,Thymeleaf的渲染过程如下:
1. ViewResolver将视图名称解析为Thymeleaf模板文件
2. Thymeleaf引擎解析模板文件,构建DOM树
3. Thymeleaf引擎将Model数据与DOM树结合,进行数据绑定和逻辑处理
4. 最终的HTML响应返回给客户端
下面是一个使用Thymeleaf的HTML模板示例:
- <!DOCTYPE html>
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <title>User Detail</title>
- <meta charset="UTF-8"/>
- </head>
- <body>
- <h1>User Detail</h1>
-
- <table border="1">
- <tr>
- <th>ID</th>
- <th>Name</th>
- <th>Email</th>
- <th>Registration Date</th>
- </tr>
- <tr>
- <td th:text="${user.id}">1</td>
- <td th:text="${user.name}">John Doe</td>
- <td th:text="${user.email}">john@example.com</td>
- <td th:text="${#dates.format(user.registrationDate, 'yyyy-MM-dd')}">2023-01-01</td>
- </tr>
- </table>
-
- <h2>Orders</h2>
- <table border="1">
- <tr>
- <th>ID</th>
- <th>Product</th>
- <th>Amount</th>
- <th>Date</th>
- </tr>
- <tr th:each="order : ${orders}">
- <td th:text="${order.id}">1</td>
- <td th:text="${order.product}">Product A</td>
- <td th:text="${#numbers.formatCurrency(order.amount)}">$100.00</td>
- <td th:text="${#dates.format(order.date, 'yyyy-MM-dd')}">2023-01-01</td>
- </tr>
- </table>
- </body>
- </html>
复制代码
视图解析与渲染的细节
视图名称解析是ViewResolver的核心功能,下面是一个简化的视图名称解析过程:
- // InternalResourceViewResolver的简化解析过程
- public View resolveViewName(String viewName, Locale locale) throws Exception {
- // 构建实际视图路径
- String path = getPrefix() + viewName + getSuffix();
-
- // 创建View对象
- AbstractUrlBasedView view = buildView(path);
-
- // 应用其他配置
- view.setApplicationContext(getApplicationContext());
-
- return view;
- }
- protected AbstractUrlBasedView buildView(String viewUrl) throws Exception {
- // 实例化View类
- AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
-
- // 设置URL
- view.setUrl(viewUrl);
-
- // 设置其他属性
- if (getContentType() != null) {
- view.setContentType(getContentType());
- }
-
- return view;
- }
复制代码
视图渲染是View的核心功能,下面是一个简化的视图渲染过程:
- // InternalResourceView的简化渲染过程
- public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
- // 暴露Model数据到请求属性中
- exposeModelAsRequestAttributes(model, request);
-
- // 准备请求
- prepareRequest(request);
-
- // 获取请求分发器
- RequestDispatcher rd = getRequestDispatcher(request, getUrl());
-
- // 如果是包含请求,则包含资源
- if (useInclude(request, response)) {
- response.setContentType(getContentType());
- rd.include(request, response);
- }
- // 否则转发请求
- else {
- rd.forward(request, response);
- }
- }
- protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
- // 将Model中的数据暴露为请求属性
- for (Map.Entry<String, Object> entry : model.entrySet()) {
- String modelName = entry.getKey();
- Object modelValue = entry.getValue();
- if (modelValue != null) {
- request.setAttribute(modelName, modelValue);
- }
- }
- }
复制代码
在Spring MVC中,我们可以通过返回特定的视图名称来实现重定向和转发:
重定向
- @RequestMapping("/save")
- public String saveUser(User user) {
- userService.saveUser(user);
- // 使用"redirect:"前缀实现重定向
- return "redirect:/user/list";
- }
复制代码
转发
- @RequestMapping("/process")
- public String processForm() {
- // 使用"forward:"前缀实现转发
- return "forward:/user/list";
- }
复制代码
重定向和转发的区别:
1. 重定向是客户端行为,浏览器地址栏会改变;转发是服务器端行为,浏览器地址栏不会改变
2. 重定向会丢失请求属性;转发会保留请求属性
3. 重定向可以重定向到任意URL;转发只能在同一应用内进行
实战技巧
1. 使用多个ViewResolver处理不同类型的视图
在实际项目中,我们可能需要同时使用多种视图技术,例如使用Thymeleaf处理HTML页面,使用JSP处理报表,使用JSON处理API响应等。这时,我们可以配置多个ViewResolver,并设置它们的优先级:
- @Configuration
- @EnableWebMvc
- public class WebConfig implements WebMvcConfigurer {
-
- @Bean
- public ContentNegotiatingViewResolver contentViewResolver() {
- ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
-
- // 设置内容协商策略
- ContentNegotiationManager manager = new ContentNegotiationManager(
- new HeaderContentNegotiationStrategy(),
- new FixedContentNegotiationStrategy(MediaType.TEXT_HTML)
- );
- resolver.setContentNegotiationManager(manager);
-
- // 设置默认视图
- resolver.setDefaultViews(Arrays.asList(new MappingJackson2JsonView()));
-
- // 设置ViewResolver列表
- List<ViewResolver> resolvers = new ArrayList<>();
- resolvers.add(thymeleafViewResolver());
- resolvers.add(jspViewResolver());
- resolver.setViewResolvers(resolvers);
-
- return resolver;
- }
-
- @Bean
- public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
- ThymeleafViewResolver resolver = new ThymeleafViewResolver();
- resolver.setTemplateEngine(templateEngine);
- resolver.setCharacterEncoding("UTF-8");
- resolver.setOrder(1);
- return resolver;
- }
-
- @Bean
- public InternalResourceViewResolver jspViewResolver() {
- InternalResourceViewResolver resolver = new InternalResourceViewResolver();
- resolver.setPrefix("/WEB-INF/jsp/");
- resolver.setSuffix(".jsp");
- resolver.setViewClass(JstlView.class);
- resolver.setOrder(2);
- return resolver;
- }
-
- // 其他Bean定义...
- }
复制代码
2. 使用ContentNegotiatingViewResolver实现内容协商
ContentNegotiatingViewResolver是Spring MVC提供的一个特殊ViewResolver,它可以根据请求的Accept头、文件扩展名或请求参数来决定使用哪种视图技术:
- @Configuration
- @EnableWebMvc
- public class WebConfig implements WebMvcConfigurer {
-
- @Bean
- public ContentNegotiatingViewResolver contentViewResolver() {
- ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
-
- // 设置内容协商策略
- ContentNegotiationManager manager = new ContentNegotiationManager(
- new HeaderContentNegotiationStrategy(),
- new ParameterContentNegotiationStrategy(),
- new PathExtensionContentNegotiationStrategy()
- );
- resolver.setContentNegotiationManager(manager);
-
- // 设置默认内容类型
- resolver.setDefaultContentType(MediaType.APPLICATION_JSON);
-
- // 设置媒体类型映射
- Map<String, MediaType> mediaTypes = new HashMap<>();
- mediaTypes.put("html", MediaType.TEXT_HTML);
- mediaTypes.put("json", MediaType.APPLICATION_JSON);
- mediaTypes.put("xml", MediaType.APPLICATION_XML);
- manager.addMediaTypeMappings(mediaTypes);
-
- // 设置ViewResolver列表
- List<ViewResolver> resolvers = new ArrayList<>();
- resolvers.add(thymeleafViewResolver());
- resolvers.add(jsonViewResolver());
- resolvers.add(xmlViewResolver());
- resolver.setViewResolvers(resolvers);
-
- return resolver;
- }
-
- @Bean
- public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
- ThymeleafViewResolver resolver = new ThymeleafViewResolver();
- resolver.setTemplateEngine(templateEngine);
- resolver.setCharacterEncoding("UTF-8");
- resolver.setContentType(MediaType.TEXT_HTML_VALUE);
- return resolver;
- }
-
- @Bean
- public ViewResolver jsonViewResolver() {
- return new ViewResolver() {
- @Override
- public View resolveViewName(String viewName, Locale locale) throws Exception {
- MappingJackson2JsonView view = new MappingJackson2JsonView();
- view.setPrettyPrint(true);
- return view;
- }
- };
- }
-
- @Bean
- public ViewResolver xmlViewResolver() {
- return new ViewResolver() {
- @Override
- public View resolveViewName(String viewName, Locale locale) throws Exception {
- MarshallingView view = new MarshallingView(jaxb2Marshaller());
- return view;
- }
- };
- }
-
- @Bean
- public Jaxb2Marshaller jaxb2Marshaller() {
- Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
- marshaller.setPackagesToScan("com.example.model");
- return marshaller;
- }
- }
复制代码
3. 使用异常处理器自定义错误页面
在Spring MVC中,我们可以通过@ExceptionHandler和@ControllerAdvice来处理异常并返回自定义的错误页面:
- @ControllerAdvice
- public class GlobalExceptionHandler {
-
- // 处理特定异常
- @ExceptionHandler(UserNotFoundException.class)
- public ModelAndView handleUserNotFoundException(UserNotFoundException ex) {
- ModelAndView mav = new ModelAndView();
- mav.addObject("errorMessage", ex.getMessage());
- mav.addObject("errorCode", "404");
- mav.setViewName("error/404");
- return mav;
- }
-
- // 处理所有异常
- @ExceptionHandler(Exception.class)
- public ModelAndView handleAllExceptions(Exception ex) {
- ModelAndView mav = new ModelAndView();
- mav.addObject("errorMessage", ex.getMessage());
- mav.addObject("errorCode", "500");
- mav.setViewName("error/500");
- return mav;
- }
- }
复制代码
4. 使用拦截器预处理和后处理Model数据
Spring MVC的拦截器不仅可以用于权限验证、日志记录等,还可以用于预处理和后处理Model数据:
- public class ModelDataInterceptor implements HandlerInterceptor {
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- // 在控制器方法执行前执行
- return true;
- }
-
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav) throws Exception {
- // 在控制器方法执行后,视图渲染前执行
-
- if (mav != null) {
- // 添加全局数据
- mav.addObject("currentTime", new Date());
- mav.addObject("applicationName", "Spring MVC Demo");
-
- // 处理Model数据
- Map<String, Object> model = mav.getModel();
- for (Map.Entry<String, Object> entry : model.entrySet()) {
- // 对特定数据进行处理
- if (entry.getValue() instanceof String) {
- String value = (String) entry.getValue();
- if (value != null && value.length() > 100) {
- // 截断过长的字符串
- model.put(entry.getKey(), value.substring(0, 100) + "...");
- }
- }
- }
- }
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- // 在视图渲染后执行
- }
- }
复制代码
然后在配置类中注册拦截器:
- @Configuration
- @EnableWebMvc
- public class WebConfig implements WebMvcConfigurer {
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new ModelDataInterceptor())
- .addPathPatterns("/**")
- .excludePathPatterns("/static/**");
- }
-
- // 其他配置...
- }
复制代码
5. 使用Flash属性传递重定向数据
在Spring MVC中,重定向会导致请求属性丢失,但我们可以使用Flash属性在重定向之间传递数据:
- @Controller
- @RequestMapping("/user")
- public class UserController {
-
- @PostMapping("/save")
- public String saveUser(User user, RedirectAttributes redirectAttributes) {
- // 保存用户
- userService.saveUser(user);
-
- // 添加Flash属性
- redirectAttributes.addFlashAttribute("message", "User saved successfully!");
- redirectAttributes.addFlashAttribute("user", user);
-
- // 重定向到列表页面
- return "redirect:/user/list";
- }
-
- @GetMapping("/list")
- public String listUsers(Model model) {
- // Flash属性会自动添加到Model中
- // 不需要额外处理
-
- // 获取用户列表
- List<User> users = userService.getAllUsers();
- model.addAttribute("users", users);
-
- return "user/list";
- }
- }
复制代码
6. 使用自定义视图处理特殊需求
有时,标准的视图技术无法满足特殊需求,这时我们可以自定义视图:
- public class PdfView extends AbstractView {
-
- public PdfView() {
- setContentType("application/pdf");
- }
-
- @Override
- protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
- // 设置响应头
- response.setContentType(getContentType());
- response.setHeader("Content-Disposition", "attachment; filename="report.pdf"");
-
- // 获取数据
- List<User> users = (List<User>) model.get("users");
-
- // 创建PDF文档
- Document document = new Document();
- PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream());
-
- document.open();
-
- // 添加标题
- Font titleFont = new Font(Font.FontFamily.HELVETICA, 18, Font.BOLD);
- Paragraph title = new Paragraph("User Report", titleFont);
- title.setAlignment(Element.ALIGN_CENTER);
- document.add(title);
-
- // 添加表格
- PdfPTable table = new PdfPTable(3);
- table.setWidthPercentage(100);
-
- // 添加表头
- table.addCell(createCell("ID", true));
- table.addCell(createCell("Name", true));
- table.addCell(createCell("Email", true));
-
- // 添加数据行
- for (User user : users) {
- table.addCell(createCell(String.valueOf(user.getId()), false));
- table.addCell(createCell(user.getName(), false));
- table.addCell(createCell(user.getEmail(), false));
- }
-
- document.add(table);
- document.close();
- writer.close();
- }
-
- private PdfPCell createCell(String content, boolean isHeader) {
- Font font = isHeader ?
- new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD) :
- new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL);
-
- PdfPCell cell = new PdfPCell(new Phrase(content, font));
- cell.setHorizontalAlignment(Element.ALIGN_CENTER);
- cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
- cell.setPadding(5);
-
- if (isHeader) {
- cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
- }
-
- return cell;
- }
- }
复制代码
然后在控制器中使用自定义视图:
- @Controller
- @RequestMapping("/report")
- public class ReportController {
-
- @Autowired
- private UserService userService;
-
- @GetMapping("/users.pdf")
- public View generateUserReport() {
- // 创建自定义视图
- PdfView view = new PdfView();
-
- // 准备数据
- List<User> users = userService.getAllUsers();
-
- // 添加静态属性
- Map<String, Object> model = new HashMap<>();
- model.put("users", users);
- view.setAttributesMap(model);
-
- return view;
- }
- }
复制代码
7. 使用Tiles框架构建复合视图
Apache Tiles是一个模板框架,允许构建可重用的页面组件。在Spring MVC中集成Tiles:
首先,添加Tiles依赖:
- <dependency>
- <groupId>org.apache.tiles</groupId>
- <artifactId>tiles-jsp</artifactId>
- <version>3.0.8</version>
- </dependency>
复制代码
然后,配置Tiles:
- @Configuration
- @EnableWebMvc
- @ComponentScan("com.example.controller")
- public class WebConfig implements WebMvcConfigurer {
-
- @Bean
- public TilesConfigurer tilesConfigurer() {
- TilesConfigurer configurer = new TilesConfigurer();
- configurer.setDefinitions("/WEB-INF/tiles/tiles-defs.xml");
- configurer.setCheckRefresh(true);
- return configurer;
- }
-
- @Bean
- public TilesViewResolver tilesViewResolver() {
- TilesViewResolver resolver = new TilesViewResolver();
- resolver.setViewClass(TilesView.class);
- return resolver;
- }
-
- // 其他配置...
- }
复制代码
定义Tiles配置文件(tiles-defs.xml):
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE tiles-definitions PUBLIC
- "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
- "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
- <tiles-definitions>
-
- <!-- 基础模板 -->
- <definition name="base" template="/WEB-INF/tiles/layout.jsp">
- <put-attribute name="header" value="/WEB-INF/tiles/header.jsp" />
- <put-attribute name="menu" value="/WEB-INF/tiles/menu.jsp" />
- <put-attribute name="body" value="" />
- <put-attribute name="footer" value="/WEB-INF/tiles/footer.jsp" />
- </definition>
-
- <!-- 首页 -->
- <definition name="home" extends="base">
- <put-attribute name="body" value="/WEB-INF/views/home.jsp" />
- <put-attribute name="title" value="Home Page" />
- </definition>
-
- <!-- 用户列表 -->
- <definition name="user.list" extends="base">
- <put-attribute name="body" value="/WEB-INF/views/user/list.jsp" />
- <put-attribute name="title" value="User List" />
- </definition>
-
- <!-- 用户详情 -->
- <definition name="user.detail" extends="base">
- <put-attribute name="body" value="/WEB-INF/views/user/detail.jsp" />
- <put-attribute name="title" value="User Detail" />
- </definition>
-
- </tiles-definitions>
复制代码
创建基础模板(layout.jsp):
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
- <!DOCTYPE html>
- <html>
- <head>
- <title><tiles:getAsString name="title"/></title>
- <meta charset="UTF-8"/>
- </head>
- <body>
- <div id="header">
- <tiles:insertAttribute name="header"/>
- </div>
-
- <div id="menu">
- <tiles:insertAttribute name="menu"/>
- </div>
-
- <div id="body">
- <tiles:insertAttribute name="body"/>
- </div>
-
- <div id="footer">
- <tiles:insertAttribute name="footer"/>
- </div>
- </body>
- </html>
复制代码
最后,在控制器中返回Tiles视图名称:
- @Controller
- @RequestMapping("/user")
- public class UserController {
-
- @Autowired
- private UserService userService;
-
- @RequestMapping("/list")
- public String listUsers(Model model) {
- List<User> users = userService.getAllUsers();
- model.addAttribute("users", users);
- return "user.list"; // 返回Tiles视图名称
- }
-
- @RequestMapping("/detail/{id}")
- public String getUserDetail(@PathVariable Long id, Model model) {
- User user = userService.getUserById(id);
- model.addAttribute("user", user);
- return "user.detail"; // 返回Tiles视图名称
- }
- }
复制代码
总结
本文全面解析了Spring MVC中的页面输出技术,从ModelAndView到ViewResolver,详细介绍了数据渲染与页面展示的完整流程及实战技巧。
我们首先了解了ModelAndView的基本概念和使用方式,掌握了如何在控制器中准备数据和指定视图。然后,我们深入探讨了ViewResolver的工作机制,学习了Spring MVC提供的多种ViewResolver实现,以及如何配置和使用它们。
接着,我们详细分析了Spring MVC中的数据渲染与页面展示流程,包括完整的请求处理流程、数据传递与渲染机制,以及视图解析与渲染的细节。最后,我们分享了一些实用的实战技巧,如使用多个ViewResolver处理不同类型的视图、使用ContentNegotiatingViewResolver实现内容协商、使用异常处理器自定义错误页面、使用拦截器预处理和后处理Model数据、使用Flash属性传递重定向数据、使用自定义视图处理特殊需求,以及使用Tiles框架构建复合视图。
通过本文的学习,读者应该能够深入理解Spring MVC的页面输出技术,并能够灵活运用这些技术解决实际开发中的问题。希望本文能够帮助读者在Spring MVC开发中更加得心应手,构建出更加优秀的Web应用。 |
|