Appearance
SpringMVC教程 - 6 SpringMVC与Thymeleaf
在现在的 Web 开发中,前后端分离架构 已经成为主流。在这种架构下,后端只负责提供数据接口(通常返回 JSON 格式的数据),而页面的渲染、交互逻辑和样式展示则完全由前端框架(如 Vue、React、Angular)来实现。这种方式的优势是职责清晰、易于协作、前端体验更灵活。
而在早期的 Web 开发模式中(例如传统的 SpringMVC 项目),页面是由 后端直接渲染生成HTML 并返回给浏览器。用户访问某个 URL 时,SpringMVC 控制器会从数据库获取数据,然后将数据绑定到模板文件中(例如 JSP、Freemarker、Thymeleaf),最终生成完整的 HTML 页面输出给用户。
如果项目比较简单,只有几个页面,那么依然可以采用这种方式。它不需要前端工程化构建,也不用维护两个独立的项目结构,开发和部署都更为轻量、快速。
现在 JSP 已经基本不用了,如果要使用视图的话, 推荐使用 Thymeleaf ,下面就基于 Thymeleaf 视图来讲解。
6.1 Thymeleaf简介
Thymeleaf 是一个用于生成 HTML、XML 等内容的 Java 模板引擎。
简单来说,就是我们先准备一个模板文件(Thymeleaf模板文件),在其中编写了一些表达式或特殊的标签,然后程序运行的时候,将数据与模板绑定,根据模板中的表达式或特殊的标签,生成最终的完整HTML页面返回给浏览器。
与 JSP 相比,Thymeleaf 的语法更自然、更贴近 HTML 语义,而且模板文件本身就是合法的 HTML 文件,可以直接在浏览器中打开预览(即使没有服务器运行)。
而在 SpringMVC 中,当用户访问某个 URL 时,Controller 中方法会返回一个字符串,也就是 逻辑视图名(如 "student/list"),Thymeleaf 视图解析器根据配置找到对应的模板文件(例如 /WEB-INF/templates/student/list.html),然后将 Model 中的数据渲染到模板中,最终生成的 HTML 被返回给浏览器展示。
下面就来介绍在 SpringMVC 项目中,使用 Thymeleaf 模板实现一个 HelloWorld。
下面从0开始创建项目,你可以直接在前面的的基础上进行添加,是一样的。
6.2 项目搭建
1 创建项目
创建一个 Maven 项目,和前面 HelloWorld 是一样的,还是叫 hello-springmvc。
2 引入依赖
和之前的区别是需要引入 thymeleaf 依赖。
在项目的 pom.xml 中添加相关的依赖。
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>
<groupId>com.foooor</groupId>
<artifactId>hello-springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 打包为 war 包 -->
<packaging>war</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<!-- Tomcat容器会提供,这里主要是写代码时需要引入,否则会报错 -->
<scope>provided</scope>
</dependency>
<!-- Spring MVC 核心依赖,会包含 Spring 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.11</version>
</dependency>
<!-- 引入thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.3.RELEASE</version>
</dependency>
<!-- Jackson 用于 JSON 序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 日志实现,Logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.19</version>
</dependency>
<!-- Lombok 可以生成 getter/setter 等方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打包配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<warName>hello-springmvc</warName> <!-- 可选:自定义 WAR 文件名 -->
</configuration>
</plugin>
<!-- 编译配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg> <!-- 开启参数名保留,用于反射获取参数名 -->
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>- 在上面的代码中添加了 SpringMVC 的核心依赖、Servlet 的依赖、日志、 Thymeleaf依赖,因为传统项目也可能有支持 AJAX 请求的接口,也需要返回 JSON 格式的数据,所以这里也引入了 JSON 依赖。;
- 添加了 Lombok 依赖,通过注解会可以在编译期间自动帮我们生成类的 getter 和 setter,也可以帮我们注入日志对象(基于 SLF4J API),减少样板代码。Lombok 的使用可以参考 Lombok传送门 。
- 添加打包的依赖,可以通过 Maven 将项目打包成 war 包。
添加完成,右键 pom.xml 文件,Maven -> Reload project ,下载一下依赖。
3 添加Web支持
和之前一样。
在 src/main 下创建一个文件夹 webapp ,然后右键项目模块,Open Module Setting ,打开 Project Structure 。
然后按照下面的操作,添加项目发布描述文件:

操作完成,会在 webapp/WEB-INF 添加一个 web.xml 文件。
如果没生成,手动添加也可以,也就是在添加 src/main/webapp/WEB-INF/web.xml 文件,具体 XML 中的内容在下一个步骤有。
4 配置DispatcherServlet
和之前一样。
在SpringMVC中,所有的请求都会到达 DispatcherServlet,并由 DispatcherServlet 分发到指定的 Controller,所以需要在 web.xml 中配置 DispatcherServlet 。
编辑 web.xml 如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!-- DispatcherServlet 配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 表示在应用启动时加载Servlet,数字越小,加载优先级越高 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!-- 和上面的 servlet-name 对应 -->
<servlet-name>springmvc</servlet-name>
<!-- 拦截所有除了 JSP 页面的请求,如果是/*表示拦截所有请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>- 在
web.xml中,添加DispatcherServlet,并添加 servlet 映射,将路径/映射到DispatcherServlet,这样所有的请求都会到达DispatcherServlet,由DispatcherServlet来处理。
5 创建视图模板文件
在 webapp/WEB-INF/template (没有 template 就新建)目录下创建 index. html ,内容如下:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><!-- 引入Thymeleaf命名空间 -->
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<div th:text="${message}"></div>
</body>
</html>- 就是一个 html 页面,但是使用了 thymeleaf 的相关语法,Thymeleaf 并没有引入新的标签,而是通过 在原生 HTML 标签上添加自定义属性(以
th:开头) 来实现动态渲染(语法,下一章再讲),这样模板依然是标准的 HTML 文件,浏览器可以直接打开预览。 - 待会在 Controller 中,就传递
message给这个视图模板,渲染为最终的 HTML 内容返回给浏览器。
6 创建Controller
创建包,并在包下创建一个 IndexController.java ,内容如下:
java
package com.foooor.hellospringmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index(Model model) { // model会自动注入
// 向模板传递参数
model.addAttribute("message", "Hello For技术栈");
return "index"; // 返回视图名称,待会去视图模板所在目录查找 index.html
}
}- 这里使用的是
@Controller注解,方法的返回值是 String,index表示的是视图名称,方法也没有添加@ResponseBody注解。 - 返回值为
index,表示会去模板所在目录下查找index.html模板文件,后面会指定模板文件所在目录为webapp/WEB-INF/template,那么模板的路径将是webapp/WEB-INF/template/index.html。
7 配置SpringMVC文件
首先在项目的 webapp/WEB-INF 目录下创建 springmvc-servlet.xml 配置文件,这个名称是和 web.xml 中的 Servlet 的名称 springmvc (<servlet-name>springmvc</servlet-name>)是对应的,也就是 名称-servlet.xml 这个格式。
然后编写内容如下:
和之前的区别是要添加视图解析器。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 启用注解扫描 -->
<context:component-scan base-package="com.foooor.hellospringmvc" />
<!-- 启用 Spring MVC 注解支持 -->
<mvc:annotation-driven />
<!-- 1.模板解析器 -->
<bean id="templateResolver" class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<!-- 模板文件所在路径 -->
<property name="prefix" value="/WEB-INF/template/"/>
<!-- 模板文件的后缀 -->
<property name="suffix" value=".html"/>
<!-- 模板模式:HTML -->
<property name="templateMode" value="HTML"/>
<!-- 文件编码,模板文件本身用什么编码写的 -->
<property name="characterEncoding" value="UTF-8"/>
<!-- 是否缓存模板 -->
<property name="cacheable" value="false"/>
</bean>
<!-- 2.模板引擎 -->
<bean id="templateEngine" class="org.thymeleaf.spring6.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver"/>
</bean>
<!-- 3.视图解析器 -->
<bean class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<!-- 模板引擎 -->
<property name="templateEngine" ref="templateEngine"/>
<!-- 字符集,返回给浏览器的响应内容用什么编码 -->
<property name="characterEncoding" value="UTF-8"/>
<!-- 优先级,数字越小,优先级越高,是可以配置多个视图解析器的,每个视图解析器都有自己的优先级 -->
<property name="order" value="1"/>
</bean>
</beans>- 在其中开启了组件扫描,并启用了 MVC注解处理器 ,这样可是使用 Spring 和 SpringMVC 的注解来进行开发。
- 还需要配置模板解析器。
8 添加日志配置
和之前一样,其实这个是非必须的。
添加一下日志配置,后面在项目中可以更方便的打印日志,这里使用 logback 日志框架实现。
在项目的 resources 目录下,创建 logback.xml,配置如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义日志文件存放路径,这里指定放在 Tomcat 根目录下的 logs 文件夹 -->
<property name="LOG_PATH" value="${catalina.base}/logs" />
<!-- 定义日志文件路径 -->
<property name="LOG_FILE" value="${LOG_PATH}/hello-springmvc.log" />
<!-- 定义控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 定义文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天生成新日志文件 -->
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留 30 天的日志 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 还可以单独指定某些包下的日志级别 -->
<logger name="com.foooor.hellospringmvc" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</logger>
<!-- root 日志级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>9 运行
像之前 HelloWorld 中一样配置 Tomcat ,并启动运行。
10 测试
打开浏览器,访问 http://localhost:8080/ 就可以访问到 Controller,并跳转到指定的页面了:

项目结构如下:

6.3 代码解析
下面来说说使用模板解析器后,SpringMVC处理请求的整个流程,前面的步骤和之前返回 JSON 数据是一样的,从 Controller 返回后,会进入到模板的解析处理流程。
- 在应用启动时,会扫描所有带
@Controller/@RequestMapping注解的 bean,将 Controller 和其中的方法等信息封装为HandlerMethod对象,HandlerMethod对象中封装了调用方法所需的全部信息,例如持有 Controller 实例和方法元数据,建立path和HandlerMethod对象的映射表(HashMap 形式),这个映射表由RequestMappingHandlerMapping管理。 - 用户通过浏览器发起请求,并到达服务器;
- 请求会被前端控制器
DispatcherServlet接收; DispatcherServlet会调用RequestMappingHandlerMapping,根据请求 URL、HTTP 方法等信息,找到匹配的HandlerMethod。并根据HandlerMethod找到合适的HandlerAdapter,因为不同类型的HandlerMethod(如普通 Controller、注解 Controller)需要不同的HandlerAdapter来执行,在注解驱动的 SpringMVC 中,会使用RequestMappingHandlerAdapter来执行HandlerMethod;- 通过
HandlerMethod封装的信息,利用反射机制来调用 Controller 中对应的方法; - Controller 方法会返回视图名称,进入视图解析流程;
ViewResolver会根据逻辑视图名解析出真正的物理视图(例如使用 Thymeleaf 时,将index解析为templates/index.html),并由具体的View对象渲染视图,结合动态数据生成最终的 HTML 页面; - 视图渲染完成后,
DispatcherServlet将生成的 HTML 内容写入 HTTP 响应体中;浏览器最终接收并显示页面或数据。
画一下执行的流程图:

所以整个流程简单的说就是:请求被 Controller 接收后,返回视图名称,视图解析器找到对应的视图模板,结合数据生成 HTML 页面内容返回给浏览器,浏览器渲染并显示最终的页面。