01基于 Jakarta EE开发 : Servlet + Thymeleaf图书管理系统
一、前提准备 创建工程 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)。
<!-- 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模板引擎类
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类
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页面
<!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>