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)。

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>
相关推荐
许彰午19 小时前
# 手写一个迷你Tomcat——三步理解Servlet容器的核心原理
java·servlet·tomcat
何中应1 天前
记录一次Jenkins构建任务的坑
java·servlet·jenkins
2401_873479402 天前
物联网设备管理中,IP查询工具怎么评估IP安全性?
物联网·tcp/ip·servlet
kaico20182 天前
Jenkins Master 停机与备份
运维·servlet·jenkins
老友@3 天前
Jenkins 中 Node 版本异常排查:Alpine + musl 导致的兼容问题(lts-alpine-jdk17)
java·servlet·jenkins
低客的黑调4 天前
MyBatis-Plus-从 CRUD 到高级特性
java·servlet·tomcat
我登哥MVP4 天前
【SpringMVC笔记】 - 10 - 拦截器(Interceptor)
java·spring boot·spring·servlet·tomcat·maven
我登哥MVP6 天前
【SpringMVC笔记】 - 9 - 异常处理器
java·spring boot·spring·servlet·tomcat·maven
DianSan_ERP6 天前
淘宝订单接口集成中如何正确处理消费者敏感信息的安全与合规问题?
大数据·运维·网络·人工智能·安全·servlet