一、SpringBoot对静态资源的映射规则
SpringBoot 对静态资源的映射核心是 自动配置 + 约定优于配置 ,默认情况下,它会把以下目录下的静态资源(js/css/img/html 等)映射到 /** 路径(即访问 http://localhost:8080/xxx.xx 就能直接访问这些目录里的文件)。
1. 默认静态资源目录(优先级从高到低)
SpringBoot 会按以下顺序扫描静态资源目录,优先级高的目录中的文件会覆盖低优先级的同名文件:
目录路径(classpath 指项目的 resources 目录) |
说明 |
|---|---|
classpath:/META-INF/resources/ |
优先级最高(通常用于第三方 jar 包的静态资源) |
classpath:/resources/ |
项目自定义静态资源(推荐) |
classpath:/static/ |
默认生成的静态资源目录(最常用) |
classpath:/public/ |
优先级最低 |
示例:
- 在
resources/static/下放一个test.jpg,访问http://localhost:8080/test.jpg就能直接打开; - 在
resources/public/下放同名test.jpg,会被static目录的覆盖,访问时显示static里的文件。
2. 特殊映射:webjars
SpringBoot 支持将 webjars(前端库的 jar 包形式,如 jQuery、Vue)映射到 /webjars/** 路径。
- 依赖示例(pom.xml):
XML
<!-- 引入jQuery的webjars -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
- 访问路径:
http://localhost:8080/webjars/jquery/3.6.0/jquery.min.js(对应 jar 包内的路径)。
3. 默认欢迎页(index.html)
SpringBoot 会自动在上述 4 个静态资源目录中查找 index.html,访问 http://localhost:8080/ 时会默认加载这个文件(优先级同静态目录)。
示例 :在 resources/static/ 下放 index.html,直接访问根路径就能打开。
4. 图标映射(favicon.ico)
默认会在静态资源目录中查找 favicon.ico,作为网站的标签页图标,无需额外配置。
二、模版引擎
1.什么是模板引擎
模板引擎(Template Engine)是连接「数据」和「静态页面」的中间件,可以理解为:
- 你有一个「静态页面模板」(比如
success.html),里面留了一些「占位符」(比如${hello}); - 你有一些动态数据(比如 Controller 里的
"zhangsan"); - 模板引擎会把「动态数据」填充到「占位符」里,最终生成一个完整的、可直接展示的 HTML 页面返回给浏览器。
常用的模板引擎:JSP、Velocity、Freemarker、Thymeleaf
我们使用Thymeleaf
用 Thymeleaf 模板引擎,你只需要关注:
- 页面结构写在
html里(前端语法); - 数据逻辑写在 Controller 里(后端逻辑);
- 模板引擎自动完成「数据 + 模板」的融合。
2.Thymeleaf语法
2.1前置知识
- Thymeleaf 语法都以
th:开头(称为「Thymeleaf 指令」),写在 HTML 标签属性中; - 所有动态数据都通过「表达式」获取 ,核心表达式是
${}(OGNL 表达式,和 EL 表达式类似); - 模板文件必须放在
resources/templates/下,且引入命名空间:
html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2.2取值与文本渲染
这是最常用的语法,用于将后端数据填充到页面中。
| 指令 | 作用 | |
|---|---|---|
th:text |
渲染文本(转义 HTML 标签,转换为纯文本) | |
th:utext |
渲染文本(不转义 HTML 标签,而是带样式) | |
th:value |
渲染 input 框的值 |
实战示例:
Controller 准备数据:
java
@Controller
public class ThymeleafController {
@RequestMapping("/basic")
public String basic(Model model) {
model.addAttribute("name", "张三");
model.addAttribute("htmlContent", "<h2>这是带标签的内容</h2>");
return "basic";
}
}
basic.html 模板:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<!-- 转义文本:输出 张三 -->
<div th:text="${name}"></div>
<!-- 不转义文本:显示带 h2 样式的内容 -->
<div th:utext="${htmlContent}"></div>
<!-- input 框赋值:value 为 张三 -->
<input type="text" th:value="${name}">
</body>
</html>
2.3表达式
Thymeleaf 有 5 类核心表达式,覆盖所有动态场景:
| 表达式类型 | 语法 | 作用 | 示例 |
|---|---|---|---|
| 变量表达式 | ${} |
获取后端传递的变量 | ${name}、${user.age} |
| 选择变量表达式 | *{} |
基于 th:object 取对象属性 | 见下方「对象取值」示例 |
| 链接表达式 | @{} |
生成 URL(自动拼接上下文) | @{/success}、@{/user/{id}(id=1)} |
| 片段表达式 | ~{} |
引用公共页面片段(如页眉) | ~{common :: header} |
| 字面量表达式 | '字符串'/ 数字 |
直接写常量 | th:text="'Hello ' + ${name}" |
| 文字国际化表达式 | #{ } | 从一个外部文件获取区域文字信息 | #{main.title} |
(1)链接表达式(最常用):
html
<!-- 生成绝对路径:http://localhost:8084/success -->
<a th:href="@{/success}">访问success页面</a>
<!-- 带参数的URL:/user/1?name=张三 -->
<a th:href="@{/user/{id}(id=1, name=${name})}">访问用户1</a>
完整的 URL 转换规则(分步骤)
Thymeleaf 会按以下固定顺序处理这个表达式,最终生成合法的 URL:
步骤 1:替换「路径占位符」(优先处理 {} 中的变量)
-
规则:参数列表中键名和路径占位符名一致 的键值对,会替换掉路径中的
{占位符}; -
示例:
/user/{id}+id=1→ 路径变为/user/1。
步骤 2:处理「查询参数」(剩余的键值对)
-
规则:参数列表中未用于替换路径占位符 的键值对,会以
key=value的形式拼接在 URL 后,用?分隔,多个参数用&分隔; -
示例:剩余参数
name=${name}(假设后端传递的name值是「张三」)→ 查询参数为name=张三。
步骤 3:拼接上下文路径(自动补全项目根路径)
-
规则:如果你的 SpringBoot 项目配置了
server.servlet.context-path(比如/myapp),Thymeleaf 会自动把上下文路径拼在 URL 最前面;如果没配置,这一步省略; -
示例:若上下文路径为
/myapp,则路径会变成/myapp/user/1。
步骤 4:URL 编码(自动处理特殊字符)
-
规则:Thymeleaf 会自动对 URL 中的特殊字符(如中文、空格、& 等)进行 URL 编码(UTF-8),避免 URL 非法;
-
示例:
name=张三→ 编码为name=%E5%BC%A0%E4%B8%89。
(2)对象取值(选择变量表达式):
Controller 传递对象:
java
// 定义User类(含id、name、age属性)
model.addAttribute("user", new User(1, "李四", 20));
模板取值:
html
<!-- 方式1:${} 取对象属性 -->
<div th:text="${user.name}"></div>
<!-- 方式2:*{} 简化取值(需先指定 th:object) -->
<div th:object="${user}">
<div th:text="*{name}"></div>
<div th:text="*{age}"></div>
</div>
(3)文字国际化表达式
步骤 1:先明确国际化配置文件的规范
国际化文件必须放在 resources 目录下,命名规则是:
XML
messages_语言代码_国家代码.properties # 特定语言(如中文、英文)
messages.properties # 默认语言(找不到对应语言时用)
示例:
- 中文配置文件:
messages_zh_CN.properties - 英文配置文件:
messages_en_US.properties - 默认配置文件:
messages.properties
步骤 2:配置文件中定义 Key-Value
在配置文件里,main.title 就是「键」,等号后是「对应语言的文本值」:
XML
# messages_zh_CN.properties(中文)
main.title = 我的网站首页
user.name = 用户名
# messages_en_US.properties(英文)
main.title = My Website Home
user.name = User Name
# messages.properties(默认,兜底)
main.title = Home Page
user.name = Name
步骤 3:#{main.title} 的取值规则
Thymeleaf 渲染 #{main.title} 时,会按以下优先级找值:
- 先判断当前请求的「语言环境」(比如浏览器设置的是中文 / 英文);
- 优先从对应语言的配置文件中找
main.title的值(比如中文环境找messages_zh_CN.properties); - 如果找不到(比如没有配置日文文件,但请求是日文环境),就用
messages.properties里的默认值; - 把找到的值渲染到页面上。
2.4表达式支持的语法
字面(Literals)
- 文本文字(Text literals): 'one text', 'Another one!',...
- 数字文本(Number literals): 0, 34, 3.0, 12.3,...
- 布尔文本(Boolean literals): true, false
- 空(Null literal): null
- 文字标记(Literal tokens): one, sometext, main,...
文本操作(Text operations)
- 字符串连接(String concatenation): +
- 文本替换(Literal substitutions): |The name is ${name}|,字符串拼接
算术运算(Arithmetic operations)
- 二元运算符(Binary operators): +, -, *, /, %
- 减号(单目运算符)Minus sign (unary operator): -
布尔操作(Boolean operations)
- 二元运算符(Binary operators):and, or
- 布尔否定(一元运算符)Boolean negation (unary operator):!, not
比较和等价(Comparisons and equality)
- 比较(Comparators): >, <, >=, <= (gt, lt, ge, le)
- 等值运算符(Equality operators):==, != (eq, ne)
条件运算符(Conditional operators)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
2.5常用的标签
| 关键字 | 功能介绍 | 案例 |
|---|---|---|
| th:id | 替换id | <input th:id="'xxx' + ${collect.id}"/> |
| th:text | 文本替换 | <p th:text="${collect.description}">description</p> |
| th:utext | 支持html的文本替换 | <p th:utext="${htmlcontent}">conten</p> |
| th:object | 替换对象 | <div th:object="${session.user}"> |
| th:value | 属性赋值 | <input th:value="${user.name}" /> |
| th:onclick | 点击事件 | th:οnclick="'getCollect()'" |
| th:each | 属性赋值 | tr th:each="user,userStat:{users}"\> * user:循环的「当前元素变量」→ 代表 `{uList}` 中当前遍历到的那个 User 对象,可自定义名称 * userStat:循环的「状态变量」→ 记录循环的索引、序号、奇偶行等状态,可选省略 |
| th:if | 判断条件 | <a th:if="${userId == collect.userId}" > |
| th:unless | 和th:if判断相反 | <a th:href="@{/login}" rel="external nofollow" rel="external nofollow" rel="external nofollow" th:unless=${session.user != null}>Login</a> |
| th:href | 链接地址 | <a th:href="@{/login}" rel="external nofollow" rel="external nofollow" rel="external nofollow" th:unless=${session.user != null}>Login</a> /> |
| th:switch | 多路选择 配合th:case 使用 | <div th:switch="${user.role}"> |
| th:case | th:switch的一个分支 | <p th:case="'admin'">User is an administrator</p> |
| th:fragment | 布局标签,定义一个代码片段,方便其它地方引用 | <div th:fragment="alert"> |
| th:include | 布局标签,替换内容到引入的文件 | <head th:include="layout :: htmlhead" th:with="title='xx'"></head> /> |
| th:replace | 布局标签,替换整个标签到引入的文件 | <div th:replace="fragments/header :: title"></div> |
| th:selected | selected选择框 选中 | th:selected="({xxx.id} == {configObj.dd})" |
| th:src | 图片类地址引入 | <img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" /> |
| th:action | 表单提交的地址 | <form action="subscribe.html" th:action="@{/subscribe}"> |
userStat的常用属性(必记)
| 属性 | 含义 | 示例(遍历 3 个用户) |
|---|---|---|
userStat.index |
循环索引(从 0 开始) | 0、1、2 |
userStat.count |
循环序号(从 1 开始) | 1、2、3 |
userStat.size |
列表总长度(固定值) | 3(始终不变) |
userStat.even |
是否为偶数行(布尔) | false、true、false |
userStat.odd |
是否为奇数行(布尔) | true、false、true |
userStat.first |
是否为第一个元素 | true、false、false |
userStat.last |
是否为最后一个元素 | false、false、true |
3.标签测试
html:
html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Title</title>
</head>
<body>
<div th:text="${hello}" th:id="${hello.toUpperCase()}">xxxx</div>
<input th:value="${user.getUsername()}">
<hr>
<div th:object="${user}">
<span th:text="*{username}"></span>
</div>
<!--只有当 user.getAge() 返回值等于 2 时,整个 <a> 标签才会被渲染到页面上,表示链接无跳转地址(点击后不会跳转)-->
<a th:href="@{/index}" th:if="${user.getAge() == 2}" >首页</a>
<a th:class="${user.getAge() > 2}?'class1':'class2'" >年龄</a>
<p th:if="${user.score >= 60 and user.score < 85}">B</p>
<p th:if="${user.score < 60}">C</p>
<p th:if="${user.score > 85}">优秀</p>
<span th:switch="${user.gender}">
<p th:case="1">男</p>
<p th:case="2">女</p>
</span>
<!--a 就是当前这一轮循环对应的 User 对象。-->
<!--aState 也是你自定义的变量名,它是 Thymeleaf 自动生成的「循环状态对象」,用来记录当前循环的各种状态信息(比如索引、序号、是否是第一行 / 最后一行等),是循环的「辅助工具」。-->
<table>
<tr th:each="a,aState:${uList}">
<td th:text="${a.username}"></td>
<td th:text="${a.password}"></td>
<td th:text="${aState.index}"></td>
</tr>
</table>
</body>
</html>
<style>
.class1 { color: red; /* 红色文字 */ }
.class2 { color: blue; /* 蓝色文字 */ }
</style>
Controller中给数据:
java
@RequestMapping("/success")
public String hello(HttpServletRequest req, HttpSession httpSession, Model model){
model.addAttribute("hello","<h1>renliang</h1>");
User user = new User();
user.setPassword("111");
user.setUsername("renliang");
user.setAge(2);
user.setScore(78);
user.setGender(2);
List<User> uList = new ArrayList<>();
for (int i = 0; i < 10; i++){
User u = new User();
u.setUsername("renliang"+i);
u.setPassword("111"+i);
uList.add(u);
}
// httpSession.setAttribute("user", user);
model.addAttribute("user", user);
model.addAttribute("uList", uList);
return "success";
}
@RequestMapping("/index")
public String index() {
// 返回模板名称:对应 resources/templates/index.html
return "index";
}