一、前提准备 创建工程 JDK 25、IDEA2025 、tomcat-11.0.20 项目依赖已正确引入(Thymeleaf 3.1.2 +Jakarta EE11 Servlet 6.0,lombok1.18.42,druid1.2.16,mysql-connector-java8.0.33)。
java
<!-- Thymeleaf依赖 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<!-- lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
二、核心配置步骤
1.配置 Thymeleaf 模板路径
Thymeleaf 的模板文件默认放在 src/main/webapp/WEB-INF/templates/ 下(需手动创建目录)。
2.CustomTemplateEngine模板引擎类
java
package com.hnjt.thymeleaf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.WebApplicationTemplateResolver;
import org.thymeleaf.web.servlet.IServletWebExchange;
import org.thymeleaf.web.servlet.JakartaServletWebApplication;
import java.io.IOException;
/**
* 对于一个JavaWeb应用而言,我们只需要配置一套模板引擎即可,所有的请求都通过该模板引擎来解析网页。
* 单例模式。本类中还提供了一个对请求进行解析的方法,方便我们使用。
*/
public class CustomTemplateEngine {
// 定义一个静态变量,用于保存模板引擎对象
private static CustomTemplateEngine webApplication;
// 模板引擎对象
private TemplateEngine templateEngine;
// 创建JakartaServletWebApplication对象
private JakartaServletWebApplication application;
/**
* 私有化构造方法,防止外部直接创建对象
* @param request
*/
private CustomTemplateEngine(HttpServletRequest request) {
System.out.println("设置Thymeleaf模板引擎");
// 创建Thymeleaf的JakartaServletWebApplication对象
application = JakartaServletWebApplication.buildApplication(request.getServletContext());
// 创建模板解析器对象
final WebApplicationTemplateResolver templateResolver = new WebApplicationTemplateResolver(application);
// 设置Thymeleaf的模板模式为HTML,除此之外Thymeleaf还支持处理其他5种模板,它们分别是XML、TEXT、JAVASCRIPT、CSS、RAW
templateResolver.setTemplateMode(TemplateMode.HTML);
// 设置模板文件的前缀(即路径)
templateResolver.setPrefix("/WEB-INF/templates/");
// 设置模板文件的文件后缀
templateResolver.setSuffix(".html");
// 设置缓存时间
templateResolver.setCacheTTLMs(Long.valueOf(3600000L));
// 设置缓存是否可用,开发阶段我们需要将缓存关闭,即设置为false
templateResolver.setCacheable(false);
// 创建模板引擎对象
templateEngine = new TemplateEngine();
// 为模板引擎设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
/**
* 获取WebApplication对象
* @param request 请求对象
* @return 返回CustomTemplateEngine对象
*/
public static CustomTemplateEngine getInstance(HttpServletRequest request) {
if (webApplication == null) {
webApplication = new CustomTemplateEngine(request);
}
return webApplication;
}
/**
* 处理模板文件
* @param templateName 模板文件的名称
* @param request 请求对象
* @param response 响应对象
* @throws IOException IO异常
*/
public void processTemplate(String templateName, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 创建IServletWebExchange对象
IServletWebExchange webExchange = application.buildExchange(request, response);
// 创建WebContext对象
WebContext context = new WebContext(webExchange, webExchange.getLocale());
// 设置响应体内容类型和字符集
response.setContentType("text/html;charset=UTF-8");
// 处理模板数据
templateEngine.process(templateName, context, response.getWriter());
}
}
3.LoginServlet类
java
package com.hnjt.servlet;
import com.hnjt.thymeleaf.CustomTemplateEngine;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "login", value = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取模板引擎实例
CustomTemplateEngine engine = CustomTemplateEngine.getInstance(req);
//保存数据
req.setAttribute("username", "zhangsan");
req.setAttribute("password", "123456");
// 处理模板文件,将登录页面渲染到响应中
engine.processTemplate("login", req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
package com.hnjt.servlet;
import com.hnjt.thymeleaf.CustomTemplateEngine;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "login", value = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取模板引擎实例
CustomTemplateEngine engine = CustomTemplateEngine.getInstance(req);
//保存数据
req.setAttribute("username", "zhangsan");
req.setAttribute("password", "123456");
// 处理模板文件,将登录页面渲染到响应中
engine.processTemplate("login", req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
4.login.html页面
java
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf/org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户登录</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.login-container {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.input-field:focus {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3);
}
.btn-hover:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(99, 102, 241, 0.4);
}
.form-animate {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex items-center justify-center p-4">
<div class="login-container bg-white/90 rounded-2xl p-8 w-full max-w-md form-animate">
<div class="text-center mb-10">
<div class="mx-auto bg-gray-200 border-2 border-dashed rounded-xl w-16 h-16 flex items-center justify-center mb-4">
<i class="fas fa-user-circle text-3xl text-indigo-600"></i>
</div>
<h1 class="text-3xl font-bold text-gray-800">欢迎回来</h1>
<p class="text-gray-600 mt-2">请登录您的账户</p>
</div>
<form id="loginForm" class="space-y-6" method="post">
<div>
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">用户名</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fas fa-user text-gray-400"></i>
</div>
<input
type="text"
id="username"
th:value="${username}"
class="input-field w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 transition duration-200"
placeholder="请输入用户名"
required
>
</div>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">密码</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fas fa-lock text-gray-400"></i>
</div>
<input
type="password"
id="password"
th:value="${password}"
class="input-field w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 transition duration-200"
placeholder="请输入密码"
required
>
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input
id="remember"
type="checkbox"
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
>
<label for="remember" class="ml-2 block text-sm text-gray-700">记住我</label>
</div>
<div class="text-sm">
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">忘记密码?</a>
</div>
</div>
<div>
<button
type="submit"
class="btn-hover w-full bg-indigo-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-300 ease-in-out"
>
登录
</button>
</div>
</form>
<div class="mt-8 text-center">
<p class="text-gray-600">
还没有账户?
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">立即注册</a>
</p>
</div>
<div class="mt-8">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white text-gray-500">其他登录方式</span>
</div>
</div>
<div class="mt-6 grid grid-cols-3 gap-3">
<button class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<i class="fab fa-wechat text-green-500"></i>
</button>
<button class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<i class="fab fa-qq text-blue-500"></i>
</button>
<button class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<i class="fab fa-weibo text-red-500"></i>
</button>
</div>
</div>
</div>
<script>
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
// 这里可以添加实际的登录逻辑
console.log('登录信息:', {username, password});
// 模拟登录成功效果
alert(`欢迎回来, ${username}!`);
});
</script>
</body>
</html>