【SpringBoot】21-Spring Boot中Web页面抽取公共页面的完整实践

发布于:2025-09-03 ⋅ 阅读:(16) ⋅ 点赞:(0)


前言

在使用Spring Boot结合Thymeleaf等模板引擎开发Web应用时,我们经常会遇到多个页面需要包含相同内容的情况,比如页头、页脚、导航栏等。为了提高代码的复用性和可维护性,我们需要将这些公共部分抽取出来。本文将详细介绍如何在Spring Boot项目中抽取和使用公共页面。

一、项目准备

1、创建Spring Boot项目

首先,我们创建一个标准的Spring Boot项目。确保pom.xml中包含了必要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo-common-page</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo-common-page</name>

    <properties>
        <java.version>8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2、项目结构

创建完成后,项目的基本结构如下:

src/
├── main/
│   ├── java/
│   │   └── com/example/demo/
│   │       ├── controller/
│   │       │   └── PageController.java
│   │       └── DemoApplication.java
│   └── resources/
│       ├── static/
│       └── templates/
│           ├── common/
│           │   ├── header.html
│           │   ├── footer.html
│           │   └── navbar.html
│           ├── home.html
│           └── about.html
└── test/
    └── java/

二、创建公共页面片段

src/main/resources/templates/common/目录下创建我们需要的公共页面。

1、创建页头(header.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header(title)">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title th:text="${title} + ' | 我的网站'">默认标题</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body { padding-top: 70px; }
    </style>
</head>
</html>

2、创建导航栏(navbar.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <!-- 导航栏 -->
    <nav th:fragment="navbar" class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top">
        <div class="container">
            <a class="navbar-brand" href="/">我的网站</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav me-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="/">首页</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/about">关于我们</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/contact">联系我们</a>
                    </li>
                </ul>
                <form class="d-flex">
                    <input class="form-control me-2" type="search" placeholder="搜索" aria-label="Search">
                    <button class="btn btn-outline-light" type="submit">搜索</button>
                </form>
            </div>
        </div>
    </nav>
</body>
</html>

3、创建页脚(footer.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <!-- 页脚 -->
    <footer th:fragment="footer" class="bg-dark text-white text-center py-3 mt-5">
        <div class="container">
            <p>&copy; 2025 我的网站. 保留所有权利.</p>
            <div class="mt-2">
                <a href="#" class="text-white me-3">隐私政策</a>
                <a href="#" class="text-white me-3">服务条款</a>
                <a href="#" class="text-white">网站地图</a>
            </div>
        </div>
    </footer>
</body>
</html>

三、创建业务页面并引用公共片段

1、创建首页(home.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/header :: header('首页')"></head>
<body>
    <!-- 引入导航栏 -->
    <div th:insert="common/navbar :: navbar"></div>

    <!-- 主要内容 -->
    <div class="container">
        <div class="row">
            <div class="col-12">
                <div class="jumbotron bg-light p-5 rounded">
                    <h1 class="display-4">欢迎来到我的网站</h1>
                    <p class="lead">这是一个使用Spring Boot和Thymeleaf构建的示例网站。</p>
                    <hr class="my-4">
                    <p>这里展示了如何抽取和使用公共页面片段。</p>
                    <a class="btn btn-primary btn-lg" href="/about" role="button">了解更多</a>
                </div>
            </div>
        </div>
    </div>

    <!-- 引入页脚 -->
    <div th:insert="common/footer :: footer"></div>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

2、创建关于我们页面(about.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/header :: header('关于我们')"></head>
<body>
    <!-- 引入导航栏 -->
    <div th:insert="common/navbar :: navbar"></div>

    <!-- 主要内容 -->
    <div class="container">
        <div class="row">
            <div class="col-12">
                <h1 class="mt-4">关于我们</h1>
                <p class="lead">我们是一家致力于提供高质量Web解决方案的公司。</p>
                
                <div class="card mt-4">
                    <div class="card-body">
                        <h5 class="card-title">我们的使命</h5>
                        <p class="card-text">通过创新的技术解决方案,帮助客户实现数字化转型,提升业务效率和用户体验。</p>
                    </div>
                </div>

                <div class="card mt-3">
                    <div class="card-body">
                        <h5 class="card-title">我们的团队</h5>
                        <p class="card-text">我们拥有一支经验丰富、充满激情的技术团队,涵盖前端开发、后端开发、UI/UX设计等多个领域。</p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 引入页脚 -->
    <div th:insert="common/footer :: footer"></div>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

四、创建控制器

1、创建PageController

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PageController {

    @GetMapping("/")
    public String home() {
        return "home";
    }

    @GetMapping("/about")
    public String about() {
        return "about";
    }

    @GetMapping("/contact")
    public String contact() {
        return "contact"; // 假设我们也有一个contact.html页面
    }
}

五、Thymeleaf片段语法详解

在上面的代码中,我们使用了Thymeleaf的片段表达式,主要有两种方式:

1、th:insert vs th:replace vs th:include

  • th:insert: 将目标片段插入到宿主元素内
  • th:replace: 用目标片段替换整个宿主元素
  • th:include: (已弃用)只包含目标片段的内容
<!-- 示例 -->
<div th:insert="common/header :: header"></div>
<!-- 结果:div元素保留,内部插入header内容 -->

<div th:replace="common/header :: header"></div>
<!-- 结果:div元素被header内容完全替换 -->

2、传递参数

header.html中,我们定义了带参数的片段:

<head th:fragment="header(title)">
    <title th:text="${title} + ' | 我的网站'">默认标题</title>
</head>

在使用时传递参数:

<head th:replace="common/header :: header('首页')"></head>

六、运行和测试

  1. 启动Spring Boot应用
  2. 访问 http://localhost:8080/ 查看首页
  3. 访问 http://localhost:8080/about 查看关于我们页面
  4. 验证导航栏、页头、页脚是否正确显示

七、最佳实践

  1. 合理组织目录结构:将公共片段放在templates/common/目录下
  2. 命名规范:为片段使用有意义的名称
  3. 避免过度嵌套:公共片段不宜过于复杂
  4. 考虑性能:对于不经常变化的公共内容,可以考虑使用缓存

通过以上步骤,我们成功地在Spring Boot项目中抽取并使用了公共页面,大大提高了代码的复用性和可维护性。这种方法特别适用于多页面应用,能够有效减少重复代码,便于统一管理和样式维护。

八、完整代码

项目结构

src/
├── main/
│   ├── java/
│   │   └── com/example/demo/
│   │       ├── controller/
│   │       │   └── PageController.java
│   │       └── DemoApplication.java
│   └── resources/
│       ├── static/
│       └── templates/
│           ├── common/
│           │   ├── header.html
│           │   ├── footer.html
│           │   └── navbar.html
│           ├── home.html
│           ├── about.html
│           └── contact.html
└── test/
    └── java/
        └── com/example/demo/DemoApplicationTests.java

代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo-common-page</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo-common-page</name>

    <properties>
        <java.version>8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

DemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

PageController.java

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PageController {

    @GetMapping("/")
    public String home() {
        return "home";
    }

    @GetMapping("/about")
    public String about() {
        return "about";
    }

    @GetMapping("/contact")
    public String contact() {
        return "contact";
    }
}

common/header.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header(title)">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title th:text="${title} + ' | 我的网站'">默认标题</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body { padding-top: 70px; }
    </style>
</head>
</html>

common/navbar.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <!-- 导航栏 -->
    <nav th:fragment="navbar" class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top">
        <div class="container">
            <a class="navbar-brand" href="/">我的网站</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav me-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="/">首页</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/about">关于我们</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/contact">联系我们</a>
                    </li>
                </ul>
                <form class="d-flex">
                    <input class="form-control me-2" type="search" placeholder="搜索" aria-label="Search">
                    <button class="btn btn-outline-light" type="submit">搜索</button>
                </form>
            </div>
        </div>
    </nav>
</body>
</html>

common/footer.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
    <!-- 页脚 -->
    <footer th:fragment="footer" class="bg-dark text-white text-center py-3 mt-5">
        <div class="container">
            <p>&copy; 2025 我的网站. 保留所有权利.</p>
            <div class="mt-2">
                <a href="#" class="text-white me-3">隐私政策</a>
                <a href="#" class="text-white me-3">服务条款</a>
                <a href="#" class="text-white">网站地图</a>
            </div>
        </div>
    </footer>
</body>
</html>

home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/header :: header('首页')"></head>
<body>
    <!-- 引入导航栏 -->
    <div th:insert="common/navbar :: navbar"></div>

    <!-- 主要内容 -->
    <div class="container">
        <div class="row">
            <div class="col-12">
                <div class="jumbotron bg-light p-5 rounded">
                    <h1 class="display-4">欢迎来到我的网站</h1>
                    <p class="lead">这是一个使用Spring Boot和Thymeleaf构建的示例网站。</p>
                    <hr class="my-4">
                    <p>这里展示了如何抽取和使用公共页面片段。</p>
                    <a class="btn btn-primary btn-lg" href="/about" role="button">了解更多</a>
                </div>
            </div>
        </div>
    </div>

    <!-- 引入页脚 -->
    <div th:insert="common/footer :: footer"></div>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

about.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/header :: header('关于我们')"></head>
<body>
    <!-- 引入导航栏 -->
    <div th:insert="common/navbar :: navbar"></div>

    <!-- 主要内容 -->
    <div class="container">
        <div class="row">
            <div class="col-12">
                <h1 class="mt-4">关于我们</h1>
                <p class="lead">我们是一家致力于提供高质量Web解决方案的公司。</p>
                
                <div class="card mt-4">
                    <div class="card-body">
                        <h5 class="card-title">我们的使命</h5>
                        <p class="card-text">通过创新的技术解决方案,帮助客户实现数字化转型,提升业务效率和用户体验。</p>
                    </div>
                </div>

                <div class="card mt-3">
                    <div class="card-body">
                        <h5 class="card-title">我们的团队</h5>
                        <p class="card-text">我们拥有一支经验丰富、充满激情的技术团队,涵盖前端开发、后端开发、UI/UX设计等多个领域。</p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 引入页脚 -->
    <div th:insert="common/footer :: footer"></div>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

contact.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/header :: header('联系我们')"></head>
<body>
    <!-- 引入导航栏 -->
    <div th:insert="common/navbar :: navbar"></div>

    <!-- 主要内容 -->
    <div class="container">
        <div class="row">
            <div class="col-12">
                <h1 class="mt-4">联系我们</h1>
                <p class="lead">有任何问题或合作意向,请通过以下方式联系我们。</p>
                
                <div class="row mt-4">
                    <div class="col-md-6">
                        <div class="card">
                            <div class="card-body">
                                <h5 class="card-title">联系信息</h5>
                                <p><strong>电话:</strong> +86 123-4567-8900</p>
                                <p><strong>邮箱:</strong> contact@mywebsite.com</p>
                                <p><strong>地址:</strong> 北京市朝阳区某大厦123号</p>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="card">
                            <div class="card-body">
                                <h5 class="card-title">工作时间</h5>
                                <p>周一至周五:9:00 - 18:00</p>
                                <p>周六:10:00 - 16:00</p>
                                <p>周日:休息</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 引入页脚 -->
    <div th:insert="common/footer :: footer"></div>

    <!-- JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

DemoApplicationTests.java

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Test
    void contextLoads() {
    }

}