SSM从入门到实战:3.4 SpringMVC视图解析与模板引擎

👋 大家好,我是 阿问学长!专注于分享优质开源项目解析、毕业设计项目指导支持、幼小初高教辅资料推荐等,欢迎关注交流!🚀

18-SpringMVC视图解析与模板引擎

📖 本文概述

本文是SSM框架系列SpringMVC进阶篇的第一篇,将深入探讨SpringMVC的视图解析机制和各种模板引擎的集成使用。通过详细的配置示例和最佳实践,帮助读者掌握视图层技术的选择和使用。

🎯 学习目标

  • 深入理解SpringMVC的视图解析原理
  • 掌握各种视图解析器的配置和使用
  • 学会集成和使用不同的模板引擎
  • 了解视图技术的选择标准
  • 掌握视图层的最佳实践

1. 视图解析原理

1.1 视图解析流程

/**
 * SpringMVC视图解析流程演示
 */
public class ViewResolutionProcess {
    
    /**
     * 视图解析的完整流程
     */
    public void viewResolutionFlow() {
        /*
         * 1. Controller返回视图名称
         * @GetMapping("/user/list")
         * public String userList() {
         *     return "user/list";  // 返回逻辑视图名
         * }
         */
        
        /*
         * 2. DispatcherServlet获取视图名称
         * String viewName = "user/list";
         */
        
        /*
         * 3. ViewResolver解析视图名称
         * ViewResolver根据配置将逻辑视图名转换为具体的View对象
         * 例如:InternalResourceViewResolver
         * "user/list" -> "/WEB-INF/views/user/list.jsp"
         */
        
        /*
         * 4. 创建View对象
         * View view = new JstlView("/WEB-INF/views/user/list.jsp");
         */
        
        /*
         * 5. View渲染
         * view.render(model, request, response);
         * - 将Model数据设置为request属性
         * - 转发到JSP页面进行渲染
         * - 生成HTML响应
         */
        
        /*
         * 6. 返回响应给客户端
         * 浏览器接收到渲染后的HTML页面
         */
    }
}

1.2 视图解析器架构

/**
 * 视图解析器架构演示
 */
public class ViewResolverArchitecture {
    
    /**
     * ViewResolver接口
     */
    public interface ViewResolver {
        /**
         * 根据视图名称和Locale解析视图
         */
        View resolveViewName(String viewName, Locale locale) throws Exception;
    }
    
    /**
     * View接口
     */
    public interface View {
        /**
         * 获取内容类型
         */
        String getContentType();
        
        /**
         * 渲染视图
         */
        void render(Map<String, ?> model, 
                   HttpServletRequest request, 
                   HttpServletResponse response) throws Exception;
    }
    
    /**
     * 视图解析器链
     */
    public void viewResolverChain() {
        /*
         * SpringMVC支持多个ViewResolver形成解析链:
         * 
         * 1. BeanNameViewResolver (优先级最高)
         *    - 将视图名称作为Bean名称查找View Bean
         * 
         * 2. ContentNegotiatingViewResolver
         *    - 根据请求的内容类型选择合适的视图
         * 
         * 3. InternalResourceViewResolver
         *    - 解析JSP视图
         * 
         * 4. FreeMarkerViewResolver
         *    - 解析FreeMarker模板
         * 
         * 5. ThymeleafViewResolver
         *    - 解析Thymeleaf模板
         */
    }
}

2. 常用视图解析器

2.1 InternalResourceViewResolver

<!-- JSP视图解析器配置 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="order" value="1"/>
    <property name="contentType" value="text/html;charset=UTF-8"/>
</bean>
/**
 * JSP视图解析器Java配置
 */
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Bean
    public ViewResolver jspViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        resolver.setOrder(1);
        resolver.setContentType("text/html;charset=UTF-8");
        
        // 设置暴露的属性
        resolver.setExposeContextBeansAsAttributes(true);
        resolver.setExposedContextBeanNames("userService", "roleService");
        
        return resolver;
    }
}

/**
 * JSP视图使用示例
 */
@Controller
@RequestMapping("/jsp")
public class JspViewController {
    
    @GetMapping("/user/list")
    public String userList(Model model) {
        List<User> users = userService.findAll();
        model.addAttribute("users", users);
        model.addAttribute("title", "用户列表");
        
        // 返回逻辑视图名,解析为:/WEB-INF/views/user/list.jsp
        return "user/list";
    }
    
    @GetMapping("/user/detail/{id}")
    public ModelAndView userDetail(@PathVariable Long id) {
        User user = userService.findById(id);
        
        ModelAndView mav = new ModelAndView("user/detail");
        mav.addObject("user", user);
        mav.addObject("title", "用户详情");
        
        return mav;
    }
}

2.2 BeanNameViewResolver

/**
 * Bean名称视图解析器
 */
@Configuration
public class BeanNameViewConfig {
    
    @Bean
    public ViewResolver beanNameViewResolver() {
        BeanNameViewResolver resolver = new BeanNameViewResolver();
        resolver.setOrder(0); // 最高优先级
        return resolver;
    }
    
    /**
     * 自定义JSON视图
     */
    @Bean("jsonView")
    public View jsonView() {
        return new AbstractView() {
            @Override
            protected void renderMergedOutputModel(Map<String, Object> model,
                                                 HttpServletRequest request,
                                                 HttpServletResponse response) throws Exception {
                response.setContentType("application/json;charset=UTF-8");
                
                ObjectMapper mapper = new ObjectMapper();
                String json = mapper.writeValueAsString(model);
                
                response.getWriter().write(json);
            }
        };
    }
    
    /**
     * 自定义XML视图
     */
    @Bean("xmlView")
    public View xmlView() {
        return new AbstractView() {
            @Override
            protected void renderMergedOutputModel(Map<String, Object> model,
                                                 HttpServletRequest request,
                                                 HttpServletResponse response) throws Exception {
                response.setContentType("application/xml;charset=UTF-8");
                
                StringBuilder xml = new StringBuilder();
                xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
                xml.append("<data>\n");
                
                for (Map.Entry<String, Object> entry : model.entrySet()) {
                    xml.append("  <").append(entry.getKey()).append(">")
                       .append(entry.getValue())
                       .append("</").append(entry.getKey()).append(">\n");
                }
                
                xml.append("</data>");
                
                response.getWriter().write(xml.toString());
            }
        };
    }
}

/**
 * 使用Bean名称视图
 */
@Controller
@RequestMapping("/api")
public class ApiViewController {
    
    @GetMapping("/user/{id}")
    public String getUserJson(@PathVariable Long id, Model model) {
        User user = userService.findById(id);
        model.addAttribute("user", user);
        
        // 返回Bean名称,使用jsonView Bean
        return "jsonView";
    }
    
    @GetMapping("/user/{id}/xml")
    public String getUserXml(@PathVariable Long id, Model model) {
        User user = userService.findById(id);
        model.addAttribute("user", user);
        
        // 返回Bean名称,使用xmlView Bean
        return "xmlView";
    }
}

2.3 ContentNegotiatingViewResolver

/**
 * 内容协商视图解析器
 */
@Configuration
public class ContentNegotiationConfig {
    
    @Bean
    public ViewResolver contentNegotiatingViewResolver() {
        ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
        resolver.setOrder(0);
        
        // 设置内容协商管理器
        resolver.setContentNegotiationManager(contentNegotiationManager());
        
        // 设置默认视图
        List<View> defaultViews = new ArrayList<>();
        defaultViews.add(jsonView());
        defaultViews.add(xmlView());
        resolver.setDefaultViews(defaultViews);
        
        return resolver;
    }
    
    @Bean
    public ContentNegotiationManager contentNegotiationManager() {
        ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
        
        // 设置媒体类型映射
        Map<String, MediaType> mediaTypes = new HashMap<>();
        mediaTypes.put("json", MediaType.APPLICATION_JSON);
        mediaTypes.put("xml", MediaType.APPLICATION_XML);
        mediaTypes.put("html", MediaType.TEXT_HTML);
        factory.setMediaTypes(mediaTypes);
        
        // 设置协商策略
        factory.setFavorPathExtension(true);  // 支持路径扩展名
        factory.setFavorParameter(true);      // 支持请求参数
        factory.setParameterName("format");   // 参数名称
        factory.setIgnoreAcceptHeader(false); // 不忽略Accept头
        factory.setDefaultContentType(MediaType.TEXT_HTML);
        
        factory.afterPropertiesSet();
        return factory.getObject();
    }
    
    @Bean
    public View jsonView() {
        MappingJackson2JsonView view = new MappingJackson2JsonView();
        view.setPrettyPrint(true);
        view.setContentType("application/json;charset=UTF-8");
        return view;
    }
    
    @Bean
    public View xmlView() {
        MappingJackson2XmlView view = new MappingJackson2XmlView();
        view.setPrettyPrint(true);
        view.setContentType("application/xml;charset=UTF-8");
        return view;
    }
}

/**
 * 内容协商控制器
 */
@Controller
@RequestMapping("/content")
public class ContentNegotiationController {
    
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id, Model model) {
        User user = userService.findById(id);
        model.addAttribute("user", user);
        
        // 根据请求的内容类型自动选择视图:
        // /content/user/1.json -> JSON视图
        // /content/user/1.xml -> XML视图
        // /content/user/1.html -> HTML视图
        // /content/user/1?format=json -> JSON视图
        return "user/detail";
    }
}

3. 模板引擎集成

3.1 Thymeleaf集成

<!-- Thymeleaf依赖 -->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.15.RELEASE</version>
</dependency>
/**
 * Thymeleaf配置
 */
@Configuration
public class ThymeleafConfig {
    
    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        templateEngine.setEnableSpringELCompiler(true);
        
        // 添加方言
        templateEngine.addDialect(new SpringSecurityDialect());
        templateEngine.addDialect(new LayoutDialect());
        
        return templateEngine;
    }
    
    @Bean
    public SpringResourceTemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(applicationContext);
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCacheable(false); // 开发环境设为false
        templateResolver.setCharacterEncoding("UTF-8");
        
        return templateResolver;
    }
    
    @Bean
    public ThymeleafViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setOrder(1);
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setContentType("text/html;charset=UTF-8");
        
        return viewResolver;
    }
    
    @Autowired
    private ApplicationContext applicationContext;
}

/**
 * Thymeleaf控制器
 */
@Controller
@RequestMapping("/thymeleaf")
public class ThymeleafController {
    
    @GetMapping("/user/list")
    public String userList(Model model) {
        List<User> users = userService.findAll();
        model.addAttribute("users", users);
        model.addAttribute("title", "用户列表");
        model.addAttribute("currentTime", new Date());
        
        return "user/list"; // 解析为:/WEB-INF/templates/user/list.html
    }
    
    @GetMapping("/user/form")
    public String userForm(Model model) {
        model.addAttribute("user", new User());
        model.addAttribute("roles", roleService.findAll());
        
        return "user/form";
    }
}
<!-- Thymeleaf模板示例:/WEB-INF/templates/user/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="${title}">用户列表</title>
    <link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <h1 th:text="${title}">用户列表</h1>
        
        <p>当前时间:<span th:text="${#dates.format(currentTime, 'yyyy-MM-dd HH:mm:ss')}"></span></p>
        
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>用户名</th>
                    <th>邮箱</th>
                    <th>状态</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="user : ${users}">
                    <td th:text="${user.id}">1</td>
                    <td th:text="${user.username}">admin</td>
                    <td th:text="${user.email}">admin@example.com</td>
                    <td>
                        <span th:if="${user.status == 1}" class="badge badge-success">正常</span>
                        <span th:if="${user.status == 0}" class="badge badge-danger">禁用</span>
                    </td>
                    <td>
                        <a th:href="@{/thymeleaf/user/detail/{id}(id=${user.id})}" class="btn btn-sm btn-info">查看</a>
                        <a th:href="@{/thymeleaf/user/edit/{id}(id=${user.id})}" class="btn btn-sm btn-warning">编辑</a>
                        <a th:href="@{/thymeleaf/user/delete/{id}(id=${user.id})}" 
                           class="btn btn-sm btn-danger"
                           onclick="return confirm('确定要删除吗?')">删除</a>
                    </td>
                </tr>
            </tbody>
        </table>
        
        <div th:if="${#lists.isEmpty(users)}">
            <p class="text-muted">暂无用户数据</p>
        </div>
    </div>
</body>
</html>

3.2 FreeMarker集成

<!-- 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.21</version>
</dependency>
/**
 * FreeMarker配置
 */
@Configuration
public class FreeMarkerConfig {
    
    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/WEB-INF/templates/");
        configurer.setDefaultEncoding("UTF-8");
        
        // FreeMarker设置
        Properties settings = new Properties();
        settings.setProperty("template_update_delay", "0");
        settings.setProperty("default_encoding", "UTF-8");
        settings.setProperty("number_format", "0.##########");
        settings.setProperty("datetime_format", "yyyy-MM-dd HH:mm:ss");
        settings.setProperty("classic_compatible", "true");
        settings.setProperty("template_exception_handler", "ignore");
        
        configurer.setFreemarkerSettings(settings);
        
        return configurer;
    }
    
    @Bean
    public FreeMarkerViewResolver freeMarkerViewResolver() {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
        resolver.setCache(false);
        resolver.setPrefix("");
        resolver.setSuffix(".ftl");
        resolver.setContentType("text/html;charset=UTF-8");
        resolver.setRequestContextAttribute("request");
        resolver.setExposeRequestAttributes(true);
        resolver.setExposeSessionAttributes(true);
        resolver.setExposeSpringMacroHelpers(true);
        resolver.setOrder(2);
        
        return resolver;
    }
}

/**
 * FreeMarker控制器
 */
@Controller
@RequestMapping("/freemarker")
public class FreeMarkerController {
    
    @GetMapping("/user/list")
    public String userList(Model model) {
        List<User> users = userService.findAll();
        model.addAttribute("users", users);
        model.addAttribute("title", "用户列表");
        
        return "user/list"; // 解析为:/WEB-INF/templates/user/list.ftl
    }
}
<!-- FreeMarker模板示例:/WEB-INF/templates/user/list.ftl -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>${title}</title>
    <link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <h1>${title}</h1>
        
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>用户名</th>
                    <th>邮箱</th>
                    <th>状态</th>
                    <th>创建时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <#list users as user>
                <tr>
                    <td>${user.id}</td>
                    <td>${user.username}</td>
                    <td>${user.email}</td>
                    <td>
                        <#if user.status == 1>
                            <span class="badge badge-success">正常</span>
                        <#else>
                            <span class="badge badge-danger">禁用</span>
                        </#if>
                    </td>
                    <td>${user.createTime?string("yyyy-MM-dd HH:mm:ss")}</td>
                    <td>
                        <a href="/freemarker/user/detail/${user.id}" class="btn btn-sm btn-info">查看</a>
                        <a href="/freemarker/user/edit/${user.id}" class="btn btn-sm btn-warning">编辑</a>
                        <a href="/freemarker/user/delete/${user.id}" 
                           class="btn btn-sm btn-danger"
                           onclick="return confirm('确定要删除吗?')">删除</a>
                    </td>
                </tr>
                <#else>
                <tr>
                    <td colspan="6" class="text-center text-muted">暂无用户数据</td>
                </tr>
                </#list>
            </tbody>
        </table>
    </div>
</body>
</html>

3.3 Velocity集成(已废弃,仅作了解)

/**
 * Velocity配置(Spring 5.x已不支持)
 */
@Configuration
public class VelocityConfig {
    
    // 注意:Spring 5.x已移除对Velocity的支持
    // 此配置仅适用于Spring 4.x及以下版本
    
    @Bean
    public VelocityConfigurer velocityConfigurer() {
        VelocityConfigurer configurer = new VelocityConfigurer();
        configurer.setResourceLoaderPath("/WEB-INF/templates/");
        
        Properties velocityProperties = new Properties();
        velocityProperties.setProperty("input.encoding", "UTF-8");
        velocityProperties.setProperty("output.encoding", "UTF-8");
        
        configurer.setVelocityProperties(velocityProperties);
        
        return configurer;
    }
    
    @Bean
    public VelocityViewResolver velocityViewResolver() {
        VelocityViewResolver resolver = new VelocityViewResolver();
        resolver.setSuffix(".vm");
        resolver.setContentType("text/html;charset=UTF-8");
        resolver.setOrder(3);
        
        return resolver;
    }
}

4. 视图技术选择

4.1 技术对比

/**
 * 视图技术对比分析
 */
public class ViewTechnologyComparison {
    
    /**
     * JSP技术特点
     */
    public void jspFeatures() {
        /*
         * 优点:
         * 1. Java EE标准,广泛支持
         * 2. 与Java代码紧密集成
         * 3. 丰富的标签库(JSTL)
         * 4. IDE支持良好
         * 
         * 缺点:
         * 1. 编译慢,开发效率低
         * 2. 不支持模板继承
         * 3. 语法相对复杂
         * 4. 不适合前后端分离
         * 
         * 适用场景:
         * - 传统的MVC架构
         * - 需要与Java代码紧密集成
         * - 团队熟悉JSP技术
         */
    }
    
    /**
     * Thymeleaf技术特点
     */
    public void thymeleafFeatures() {
        /*
         * 优点:
         * 1. 自然模板,可直接在浏览器打开
         * 2. 强大的表达式语言
         * 3. 良好的HTML5支持
         * 4. 支持模板继承和片段
         * 5. 国际化支持良好
         * 
         * 缺点:
         * 1. 学习成本相对较高
         * 2. 性能不如JSP
         * 3. 调试相对困难
         * 
         * 适用场景:
         * - 现代Web应用
         * - 需要良好的HTML5支持
         * - 前后端协作开发
         */
    }
    
    /**
     * FreeMarker技术特点
     */
    public void freeMarkerFeatures() {
        /*
         * 优点:
         * 1. 功能强大,语法简洁
         * 2. 性能优秀
         * 3. 支持宏定义和模板继承
         * 4. 良好的错误提示
         * 
         * 缺点:
         * 1. 学习成本较高
         * 2. 模板不是有效的HTML
         * 3. IDE支持一般
         * 
         * 适用场景:
         * - 复杂的模板逻辑
         * - 性能要求较高
         * - 代码生成工具
         */
    }
}

4.2 选择建议

/**
 * 视图技术选择建议
 */
public class ViewTechnologySelection {
    
    /**
     * 基于项目特点选择
     */
    public void selectionCriteria() {
        /*
         * 1. 新项目建议:
         * - 优先选择Thymeleaf
         * - 考虑前后端分离架构
         * 
         * 2. 遗留项目:
         * - 继续使用JSP
         * - 逐步迁移到现代模板引擎
         * 
         * 3. 性能敏感项目:
         * - 考虑FreeMarker
         * - 或者前后端分离+REST API
         * 
         * 4. 团队技能:
         * - 根据团队熟悉程度选择
         * - 考虑学习成本和维护成本
         */
    }
    
    /**
     * 混合使用策略
     */
    public void hybridStrategy() {
        /*
         * 可以在同一个项目中使用多种视图技术:
         * 
         * 1. 管理后台:使用Thymeleaf
         * 2. API接口:返回JSON/XML
         * 3. 报表页面:使用FreeMarker
         * 4. 错误页面:使用JSP
         */
    }
}

5. 视图层最佳实践

5.1 模板组织结构

/WEB-INF/templates/
├── layout/                     # 布局模板
│   ├── base.html              # 基础布局
│   ├── admin.html             # 管理后台布局
│   └── mobile.html            # 移动端布局
├── fragments/                  # 模板片段
│   ├── header.html            # 页头
│   ├── footer.html            # 页脚
│   ├── sidebar.html           # 侧边栏
│   └── pagination.html        # 分页组件
├── user/                      # 用户模块
│   ├── list.html
│   ├── detail.html
│   ├── form.html
│   └── profile.html
├── role/                      # 角色模块
│   ├── list.html
│   ├── form.html
│   └── permissions.html
├── common/                    # 通用页面
│   ├── login.html
│   ├── register.html
│   └── dashboard.html
└── error/                     # 错误页面
    ├── 404.html
    ├── 500.html
    └── error.html

5.2 模板继承和片段

<!-- 基础布局模板:layout/base.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title th:text="${title}">默认标题</title>
    
    <!-- 通用CSS -->
    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <link rel="stylesheet" href="/css/common.css">
    
    <!-- 页面特定CSS -->
    <th:block th:fragment="css"></th:block>
</head>
<body>
    <!-- 页头 -->
    <div th:replace="fragments/header :: header"></div>
    
    <!-- 主要内容 -->
    <main class="container-fluid">
        <div th:replace="${content} :: content"></div>
    </main>
    
    <!-- 页脚 -->
    <div th:replace="fragments/footer :: footer"></div>
    
    <!-- 通用JS -->
    <script src="/js/jquery.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    
    <!-- 页面特定JS -->
    <th:block th:fragment="scripts"></th:block>
</body>
</html>

<!-- 页头片段:fragments/header.html -->
<header th:fragment="header" class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
        <a class="navbar-brand" href="/">SSM Demo</a>
        
        <div class="navbar-nav ml-auto">
            <a class="nav-link" href="/user/list">用户管理</a>
            <a class="nav-link" href="/role/list">角色管理</a>
            <div class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown">
                    <span th:text="${session.currentUser?.username}">用户</span>
                </a>
                <div class="dropdown-menu">
                    <a class="dropdown-item" href="/user/profile">个人资料</a>
                    <a class="dropdown-item" href="/logout">退出登录</a>
                </div>
            </div>
        </div>
    </div>
</header>

<!-- 具体页面:user/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      th:replace="layout/base :: layout(~{::content}, ~{::css}, ~{::scripts})">
<head>
    <!-- 页面特定CSS -->
    <th:block th:fragment="css">
        <link rel="stylesheet" href="/css/user.css">
    </th:block>
</head>
<body>
    <!-- 页面内容 -->
    <div th:fragment="content">
        <div class="row">
            <div class="col-12">
                <h1>用户列表</h1>
                
                <!-- 搜索表单 -->
                <form class="mb-3" method="get">
                    <div class="row">
                        <div class="col-md-3">
                            <input type="text" name="username" class="form-control" 
                                   placeholder="用户名" th:value="${param.username}">
                        </div>
                        <div class="col-md-3">
                            <select name="status" class="form-control">
                                <option value="">全部状态</option>
                                <option value="1" th:selected="${param.status == '1'}">正常</option>
                                <option value="0" th:selected="${param.status == '0'}">禁用</option>
                            </select>
                        </div>
                        <div class="col-md-3">
                            <button type="submit" class="btn btn-primary">搜索</button>
                            <a href="/user/create" class="btn btn-success">添加用户</a>
                        </div>
                    </div>
                </form>
                
                <!-- 用户表格 -->
                <div class="table-responsive">
                    <table class="table table-striped">
                        <thead>
                            <tr>
                                <th>ID</th>
                                <th>用户名</th>
                                <th>邮箱</th>
                                <th>状态</th>
                                <th>创建时间</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr th:each="user : ${users}">
                                <td th:text="${user.id}"></td>
                                <td th:text="${user.username}"></td>
                                <td th:text="${user.email}"></td>
                                <td>
                                    <span th:if="${user.status == 1}" class="badge badge-success">正常</span>
                                    <span th:if="${user.status == 0}" class="badge badge-danger">禁用</span>
                                </td>
                                <td th:text="${#dates.format(user.createTime, 'yyyy-MM-dd HH:mm')}"></td>
                                <td>
                                    <a th:href="@{/user/detail/{id}(id=${user.id})}" class="btn btn-sm btn-info">查看</a>
                                    <a th:href="@{/user/edit/{id}(id=${user.id})}" class="btn btn-sm btn-warning">编辑</a>
                                    <button type="button" class="btn btn-sm btn-danger" 
                                            th:onclick="'deleteUser(' + ${user.id} + ')'">删除</button>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
                
                <!-- 分页组件 -->
                <div th:replace="fragments/pagination :: pagination(${page})"></div>
            </div>
        </div>
    </div>
    
    <!-- 页面特定JS -->
    <th:block th:fragment="scripts">
        <script>
            function deleteUser(userId) {
                if (confirm('确定要删除这个用户吗?')) {
                    $.post('/user/delete/' + userId, function(result) {
                        if (result.success) {
                            location.reload();
                        } else {
                            alert('删除失败:' + result.message);
                        }
                    });
                }
            }
        </script>
    </th:block>
</body>
</html>

6. 小结

本文深入介绍了SpringMVC的视图解析和模板引擎:

  1. 视图解析原理:从逻辑视图名到具体视图的解析过程
  2. 视图解析器:各种解析器的配置和使用场景
  3. 模板引擎集成:Thymeleaf、FreeMarker等的集成配置
  4. 技术选择:不同视图技术的特点和选择建议
  5. 最佳实践:模板组织、继承和片段化设计

掌握视图解析的关键点:

  • 理解视图解析的完整流程
  • 选择合适的视图技术
  • 合理组织模板结构
  • 使用模板继承和片段
  • 考虑性能和维护性

🔗 下一篇预告

下一篇文章将介绍SpringMVC拦截器与异常处理,学习如何实现横切关注点和统一异常处理。


相关文章:

标题SpringBoot基于Web的图书借阅管理信息系统设计实现AI更换标题第1章引言介绍图书借阅管理信息系统的研究背景、意义、现状以及论文的研究方法和创新点。1.1研究背景意义分析当前图书借阅管理的需求和SpringBoot技术的应用背景。1.2国内外研究现状概述国内外在图书借阅管理信息系统方面的研究进展。1.3研究方法创新点介绍本文采用的研究方法和系统设计的创新之处。第2章相关理论技术阐述SpringBoot框架、Web技术和数据库相关理论。2.1SpringBoot框架概述介绍SpringBoot框架的基本概念、特点和核心组件。2.2Web技术基础概述Web技术的发展历程、基本原理和关键技术。2.3数据库技术应用讨论数据库在图书借阅管理信息系统中的作用和选型依据。第3章系统需求分析对图书借阅管理信息系统的功能需求、非功能需求进行详细分析。3.1功能需求分析列举系统应具备的各项功能,如用户登录、图书查询、借阅管理等。3.2非功能需求分析阐述系统应满足的性能、安全性、易用性等方面的要求。第4章系统设计详细介绍图书借阅管理信息系统的设计方案和实现过程。4.1系统架构设计给出系统的整体架构,包括前后端分离、数据库设计等关键部分。4.2功能模块设计具体阐述各个功能模块的设计思路和实现方法,如用户管理模块、图书管理模块等。4.3数据库设计详细介绍数据库的设计过程,包括表结构、字段类型、索引等关键信息。第5章系统实现测试对图书借阅管理信息系统进行编码实现,并进行详细的测试验证。5.1系统实现介绍系统的具体实现过程,包括关键代码片段、技术难点解决方法等。5.2系统测试给出系统的测试方案、测试用例和测试结果,验证系统的正确性和稳定性。第6章结论展望总结本文的研究成果,指出存在的问题和未来的研究方向。6.1研究结论概括性地总结本文的研究内容和取得的成果。6.2展望对图书借阅管理
摘 要 基于SpringBoot的电影院售票系统为用户提供了便捷的在线购票体验,覆盖了从注册登录到观影后的评价反馈等各个环节。用户能够通过系统快速浏览和搜索电影信息,包括正在热映及即将上映的作品,并利用选座功能选择心仪的座位进行预订。系统支持多种支付方式如微信、支付宝以及银行卡支付,同时提供积分兑换和优惠券领取等功能,增强了用户的购票体验。个人中心允许用户管理订单、收藏喜爱的影片以及查看和使用优惠券,极大地提升了使用的便利性和互动性。客服聊天功能则确保用户在遇到问题时可以即时获得帮助。 后台管理人员,系统同样提供了全面而细致的管理工具来维护日常运营。管理员可以通过后台首页直观地查看销售额统计图,了解票房情况并据此调整策略。电影信息管理模块支持新增、删除及修改电影资料,确保信息的准确及时更新。用户管理功能使得管理员可以方便地处理用户账号,包括导入导出数据以供分析。订单管理模块简化了对不同状态订单的处理流程,提高了工作效率。优惠券管理和弹窗提醒管理功能有助于策划促销活动,吸引更多观众。通过这样的集成化平台,SpringBoot的电影院售票系统不仅优化了用户的购票体验,也加强了影院内部的管理能力,促进了业务的发展和服务质量的提升。 关键词:电影院售票系统;SpringBoot框架;Java技术
内容概要:本文介绍了2025年中国网络安全的十大创新方向,涵盖可信数据空间、AI赋能数据安全、ADR(应用检测响应)、供应链安全、深度伪造检测、大模型安全评估、合规管理安全运营深度融合、AI应用防火墙、安全运营智能体、安全威胁检测智能体等。每个创新方向不仅提供了推荐的落地方案和典型厂商,还详细阐述了其核心能力、应用场景、关键挑战及其用户价值。文中特别强调了AI技术在网络安全领域的广泛应用,如AI赋能数据安全、智能体驱动的安全运营等,旨在应对日益复杂的网络威胁,提升企业和政府机构的安全防护能力。 适合人群:从事网络安全、信息技术、数据管理等相关工作的专业人士,尤其是负责企业信息安全、技术架构设计、合规管理的中高层管理人员和技术人员。 使用场景及目标:①帮助企业理解和应对最新的网络安全威胁和技术趋势;②指导企业选择合适的网络安全产品和服务,提升整体安全防护水平;③协助企业构建和完善自身的网络安全管理体系,确保合规运营;④为技术研发人员提供参考,推动技术创新和发展。 其他说明:文章内容详尽,涉及多个技术领域和应用场景,建议读者根据自身需求重点关注相关章节,并结合实际情况进行深入研究和实践。文中提到的多个技术和解决方案已在实际应用中得到了验证,具有较高的参考价值。此外,随着技术的不断发展,文中提及的部分技术和方案可能会有所更新或改进,因此建议读者保持关注最新的行业动态和技术进展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿问学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值