Skip to content

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 返回后,会进入到模板的解析处理流程。

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

画一下执行的流程图:

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