Spring MVC视图解析案例

wen java案例 2

本文目录导读:

Spring MVC视图解析案例

  1. 项目结构
  2. Maven依赖配置 (pom.xml)
  3. Web配置类
  4. 控制器类
  5. 视图模板
  6. 测试用例

我将为您创建一个完整的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,以及它们的配置和使用方法,您可以根据需要选择合适的视图解析器。

抱歉,评论功能暂时关闭!