本文目录导读:

我将为您创建一个完整的Spring MVC视图解析案例,涵盖多种视图解析策略。
项目结构
spring-mvc-view-resolver/
├── pom.xml
├── src/main/java/
│ ├── config/
│ │ ├── WebConfig.java
│ │ └── WebInitializer.java
│ ├── controller/
│ │ └── ViewController.java
│ └── model/
│ └── User.java
└── src/main/resources/
│ └── application.properties
└── src/main/webapp/
├── WEB-INF/
│ └── views/
│ ├── jsp/
│ │ ├── home.jsp
│ │ └── user/
│ │ └── profile.jsp
│ ├── freemarker/
│ │ └── welcome.ftl
│ └── thymeleaf/
│ └── index.html
Maven依赖配置 (pom.xml)
<dependencies>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.29</version>
</dependency>
<!-- JSP 视图解析 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Thymeleaf 视图解析 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>
<!-- Freemarker 视图解析 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.29</version>
</dependency>
</dependencies>
Web配置类
WebInitializer.java
package config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// Customize if needed
registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");
}
}
WebConfig.java
package config;
import org.springframework.context.annotation.*;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.*;
import org.springframework.web.servlet.view.freemarker.*;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.springframework.web.servlet.view.JstlView;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "controller")
public class WebConfig implements WebMvcConfigurer {
// ============ JSP 视图解析器 ============
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/views/jsp/");
resolver.setSuffix(".jsp");
resolver.setOrder(3); // 最后解析
resolver.setViewNames("*.jsp"); // 仅处理以.jsp结尾的视图名
// 配置JSP属性
Map<String, Object> attributes = new HashMap<>();
attributes.put("active", true);
resolver.setAttributes(attributes);
// 设置内容类型
resolver.setContentType("text/html;charset=UTF-8");
resolver.setCharacterEncoding("UTF-8");
resolver.setCache(true);
return resolver;
}
// ============ Thymeleaf 视图解析器 ============
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("/WEB-INF/views/thymeleaf/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(true);
templateResolver.setOrder(2);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
engine.setEnableSpringELCompiler(true);
return engine;
}
@Bean
public ViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(2); // 第二优先级
resolver.setViewNames(new String[] {"th_*", "thymeleaf_*"}); // 仅处理特定前缀的视图名
return resolver;
}
// ============ Freemarker 视图解析器 ============
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/views/freemarker/");
configurer.setDefaultEncoding("UTF-8");
Map<String, Object> variables = new HashMap<>();
variables.put("app", "Spring MVC Freemarker");
configurer.setFreemarkerVariables(variables);
// Freemarker 设置
Properties settings = new Properties();
settings.setProperty("number_format", "0.##");
settings.setProperty("datetime_format", "yyyy-MM-dd HH:mm:ss");
settings.setProperty("date_format", "yyyy-MM-dd");
settings.setProperty("time_format", "HH:mm:ss");
configurer.setFreemarkerSettings(settings);
return configurer;
}
@Bean
public ViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setConfiguration(freeMarkerConfigurer().getConfiguration());
resolver.setPrefix("");
resolver.setSuffix(".ftl");
resolver.setContentType("text/html;charset=UTF-8");
resolver.setOrder(1); // 第二优先级
resolver.setViewNames(new String[] {"ftl_*", "freemarker_*"}); // 仅处理特定前缀的视图名
return resolver;
}
// ============ XML/普通视图解析器(可选) ============
@Bean
public ViewResolver xmlViewResolver() {
XmlViewResolver resolver = new XmlViewResolver();
resolver.setLocation(new ClassPathResource("/WEB-INF/views/") );
resolver.setOrder(4);
resolver.setViewNames(new String[] {"xml_*"});
return resolver;
}
// ============ BeanName 视图解析器(可选) ============
@Bean
public ViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(0); // 最高优先级
return resolver;
}
// ============ 静态资源处理 ============
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/")
.setCachePeriod(3600);
}
// ============ 跨域配置 ============
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
控制器类
ViewController.java
package controller;
import model.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.Arrays;
import java.util.List;
@Controller
@RequestMapping("/view")
public class ViewController {
// ============ JSP 视图示例 ============
@GetMapping("/jsp/home")
public String jspHome(Model model) {
model.addAttribute("title", "Spring MVC JSP View");
model.addAttribute("message", "This view is rendered by JSP");
model.addAttribute("users", getSampleUsers());
return "home"; // 会被 JSP视图解析器处理
}
@GetMapping("/jsp/profile/{id}")
public String jspProfile(@PathVariable Long id, Model model) {
User user = getUserById(id);
model.addAttribute("user", user);
return "user/profile"; // JSP 视图名称
}
// ============ Thymeleaf 视图示例 ============
@GetMapping("/thymeleaf")
public String thymeleafView(Model model) {
model.addAttribute("title", "Spring MVC Thymeleaf View");
model.addAttribute("message", "This view is rendered by Thymeleaf");
model.addAttribute("users", getSampleUsers());
return "thymeleaf_index"; // 前缀匹配 "th_"
}
// ============ Freemarker 视图示例 ============
@GetMapping("/freemarker")
public ModelAndView freemarkerView() {
ModelAndView mv = new ModelAndView("freemarker_welcome");
mv.addObject("title", "Spring MVC Freemarker View");
mv.addObject("message", "This view is rendered by Freemarker");
mv.addObject("users", getSampleUsers());
return mv;
}
// ============ 使用ModelAndView返回多个数据 ============
@GetMapping("/modelandview")
public ModelAndView modelAndViewExample() {
ModelAndView mv = new ModelAndView("jsp/home");
mv.addObject("title", "ModelAndView Example");
mv.addObject("message", "Using ModelAndView to render view");
mv.addObject("users", getSampleUsers());
return mv;
}
// ============ JSON 返回示例 ============
@GetMapping("/json/users")
@ResponseBody
public List<User> getUsersJson() {
return getSampleUsers();
}
// ============ 重定向视图 ============
@GetMapping("/redirect")
public String redirectView() {
return "redirect:/view/freemarker";
}
// ============ 转发视图 ============
@GetMapping("/forward")
public String forwardView() {
return "forward:/view/jsp/home";
}
// ============ 辅助方法 ============
private List<User> getSampleUsers() {
return Arrays.asList(
new User(1L, "张三", "zhangsan@example.com"),
new User(2L, "李四", "lisi@example.com"),
new User(3L, "王五", "wangwu@example.com")
);
}
private User getUserById(Long id) {
return getSampleUsers().stream()
.filter(u -> u.getId().equals(id))
.findFirst()
.orElse(null);
}
}
视图模板
home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>${title}</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: auto; }
.message { background: #e0f7fa; padding: 20px; border-radius: 5px; }
.user-list { margin-top: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #4CAF50; color: white; }
</style>
</head>
<body>
<div class="container">
<h1>${title}</h1>
<div class="message">${message}</div>
<div class="user-list">
<h2>用户列表</h2>
<c:if test="${not empty users}">
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
</tr>
</thead>
<tbody>
<c:forEach var="user" items="${users}">
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:if>
</div>
</div>
</body>
</html>
welcome.ftl (Freemarker)
<!DOCTYPE html>
<html>
<head>${title}</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: auto; }
.highlight { color: #4CAF50; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { padding: 8px; border: 1px solid #ddd; }
</style>
</head>
<body>
<div class="container">
<h1>${title}</h1>
<p class="highlight">${message}</p>
<h2>用户列表 (Freemarker)</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
</tr>
</thead>
<tbody>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
</tr>
</#list>
</tbody>
</table>
</div>
</body>
</html>
index.th.html (Thymeleaf)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>th:text="${title}">Title</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: auto; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { padding: 8px; border: 1px solid #ddd; }
</style>
</head>
<body>
<div class="container">
<h1 th:text="${title}">Title</h1>
<p th:text="${message}">Message</p>
<h2>用户列表 (Thymeleaf)</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}">ID</td>
<td th:text="${user.name}">Name</td>
<td th:text="${user.email}">Email</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
user/profile.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>用户资料</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.profile { max-width: 400px; margin: auto; border: 1px solid #ddd;
padding: 20px; border-radius: 5px; }
.profile-item { margin-bottom: 10px; }
.label { font-weight: bold; color: #666; }
</style>
</head>
<body>
<div class="profile">
<h2>用户资料</h2>
<div class="profile-item">
<span class="label">ID:</span> ${user.id}
</div>
<div class="profile-item">
<span class="label">姓名:</span> ${user.name}
</div>
<div class="profile-item">
<span class="label">邮箱:</span> ${user.email}
</div>
<a href="/view/jsp/home">返回主页</a>
</div>
</body>
</html>
测试用例
ViewControllerTest.java
package controller;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
class ViewControllerTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(new ViewController()).build();
}
@Test
void testJSPView() throws Exception {
mockMvc.perform(get("/view/jsp/home"))
.andExpect(status().isOk())
.andExpect(view().name("home"))
.andExpect(model().attributeExists("users"));
}
@Test
void testThymeleafView() throws Exception {
mockMvc.perform(get("/view/thymeleaf"))
.andExpect(status().isOk())
.andExpect(view().name("thymeleaf_index"));
}
@Test
void testFreemarkerView() throws Exception {
mockMvc.perform(get("/view/freemarker"))
.andExpect(status().isOk())
.andExpect(view().name("freemarker_welcome"));
}
@Test
void testJSONResponse() throws Exception {
mockMvc.perform(get("/view/json/users"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$[0].name").value("张三"));
}
}
这个案例展示了Spring MVC的多种视图解析策略,包括JSP、Thymeleaf和Freemarker,以及它们的配置和使用方法,您可以根据需要选择合适的视图解析器。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播