文章目录
前言
在使用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>© 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>
六、运行和测试
- 启动Spring Boot应用
- 访问
http://localhost:8080/
查看首页 - 访问
http://localhost:8080/about
查看关于我们页面 - 验证导航栏、页头、页脚是否正确显示
七、最佳实践
- 合理组织目录结构:将公共片段放在
templates/common/
目录下 - 命名规范:为片段使用有意义的名称
- 避免过度嵌套:公共片段不宜过于复杂
- 考虑性能:对于不经常变化的公共内容,可以考虑使用缓存
通过以上步骤,我们成功地在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>© 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() {
}
}