ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Jakarta EE与Thymeleaf构建现代化Web应用实践

Jakarta EE与Thymeleaf构建现代化Web应用实践 1. 项目概述最近在IDEA2025环境下用Jakarta EE开发了一个ServletThymeleaf的项目踩了不少坑也积累了一些经验。Jakarta EE 9版本开始全面采用新的命名空间从javax.变为jakarta.这让很多老项目的迁移变得复杂但同时也带来了更好的性能和更现代的API设计。Servlet 6.0作为Jakarta EE的核心组件配合Thymeleaf 3.1.2模板引擎可以构建出既保持前后端分离优势又能享受服务端渲染好处的Web应用。这种组合特别适合需要SEO优化但又不想完全放弃前端框架的项目。注意从Servlet 5.0(javax)升级到6.0(jakarta)时所有import语句都需要从javax.servlet改为jakarta.servlet这是最大的迁移成本点。2. 环境准备与项目创建2.1 JDK与Tomcat版本选择Servlet 6.0要求JDK 17和Tomcat 10.1这是硬性要求。我实测过JDK 11 Tomcat 10.1 → 启动报错JDK 17 Tomcat 9 → 不兼容jakarta包JDK 17 Tomcat 10.1.7 → 完美运行推荐使用Amazon Corretto 17作为JDK它的长期支持策略比OpenJDK更明确。Tomcat则建议直接用10.1.x的最新版避免早期版本的bug。2.2 Maven依赖配置完整的pom.xml关键依赖如下properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target thymeleaf.version3.1.2.RELEASE/thymeleaf.version /properties dependencies !-- Jakarta EE Web API -- dependency groupIdjakarta.platform/groupId artifactIdjakarta.jakartaee-web-api/artifactId version10.0.0/version scopeprovided/scope /dependency !-- Thymeleaf核心 -- dependency groupIdorg.thymeleaf/groupId artifactIdthymeleaf/artifactId version${thymeleaf.version}/version /dependency !-- 其他实用工具 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.32/version scopeprovided/scope /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.16/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies这里有几个关键点jakarta.jakartaee-web-api的scope必须是provided因为Tomcat本身已经提供了这些实现Thymeleaf版本锁定在3.1.2这是目前最稳定的版本MySQL驱动建议用8.0.33修复了许多连接池相关的问题2.3 项目结构规划标准的Maven项目结构如下src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── config/ # 配置类 │ │ ├── controller/ # Servlet控制器 │ │ ├── service/ # 业务逻辑 │ │ └── util/ # 工具类 │ ├── resources/ │ │ ├── static/ # 静态资源 │ │ │ ├── css/ │ │ │ ├── js/ │ │ │ └── images/ │ │ └── application.properties # 配置文件 │ └── webapp/ │ ├── WEB-INF/ │ │ ├── templates/ # Thymeleaf模板 │ │ └── web.xml # 可选配置 │ └── index.jsp # 欢迎页(可选) └── test/ # 测试代码3. 核心实现细节3.1 Thymeleaf配置类创建一个单例的TemplateEngine管理类public class ThymeleafConfig { private static TemplateEngine templateEngine; public static synchronized TemplateEngine getTemplateEngine(ServletContext servletContext) { if (templateEngine null) { ServletContextTemplateResolver resolver new ServletContextTemplateResolver(servletContext); resolver.setPrefix(/WEB-INF/templates/); resolver.setSuffix(.html); resolver.setTemplateMode(TemplateMode.HTML); resolver.setCharacterEncoding(UTF-8); resolver.setCacheable(true); // 生产环境设为true templateEngine new TemplateEngine(); templateEngine.setTemplateResolver(resolver); } return templateEngine; } }关键配置参数说明setCacheable(true)生产环境必须开启模板缓存性能提升10倍以上字符集统一用UTF-8避免中文乱码TemplateMode.HTML会启用HTML严格检查有助于提前发现模板问题3.2 Servlet最佳实践现代Servlet开发推荐使用注解而非web.xml。一个完整的Servlet示例WebServlet( name userServlet, value {/user, /user/*}, initParams { WebInitParam(name defaultPageSize, value 10) }, loadOnStartup 1 ) public class UserServlet extends HttpServlet { private int defaultPageSize; Override public void init() throws ServletException { this.defaultPageSize Integer.parseInt( getServletConfig().getInitParameter(defaultPageSize) ); } Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String pathInfo req.getPathInfo(); // 获取/user/后面的路径 TemplateEngine engine ThymeleafConfig.getTemplateEngine(req.getServletContext()); if (pathInfo null || pathInfo.equals(/)) { // 列表页逻辑 ListUser users userService.listUsers(defaultPageSize); req.setAttribute(users, users); engine.process(user/list, req, resp); } else if (pathInfo.matches(/\\d)) { // 详情页逻辑 long userId Long.parseLong(pathInfo.substring(1)); User user userService.getUserById(userId); req.setAttribute(user, user); engine.process(user/detail, req, resp); } else { resp.sendError(HttpServletResponse.SC_NOT_FOUND); } } }这个示例展示了几个重要技巧使用WebServlet注解配置多个URL模式通过initParams传递初始化参数路径参数的正则验证根据不同路径分发到不同模板3.3 模板开发技巧Thymeleaf模板示例 (user/list.html):!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title用户列表/title link th:href{/static/css/bootstrap.min.css} relstylesheet /head body div classcontainer h1用户列表/h1 !-- 条件判断 -- div th:if${not #lists.isEmpty(users)} classalert alert-info 共 span th:text${users.size()}0/span 个用户 /div !-- 循环渲染 -- table classtable thead tr thID/th th用户名/th th注册时间/th /tr /thead tbody tr th:eachuser : ${users} td th:text${user.id}1/td td a th:href{|/user/${user.id}|} th:text${user.username}admin/a /td td th:text${#dates.format(user.createTime, yyyy-MM-dd HH:mm)} 2023-01-01 /td /tr /tbody /table !-- 片段引入 -- div th:replace~{fragments/pagination :: pagination}/div /div script th:src{/static/js/jquery.min.js}/script /body /html实用Thymeleaf特性th:href{/static/...}自动处理上下文路径th:if#lists工具类的组合判断#dates.format日期格式化th:replace引入公共片段4. 高级配置与优化4.1 静态资源处理现代最佳实践是将静态资源放在/static/目录下并通过配置缓存策略WebServlet(/static/*) public class StaticResourceServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String pathInfo req.getPathInfo(); if (pathInfo null || pathInfo.isEmpty()) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // 安全校验防止路径穿越 if (pathInfo.contains(..)) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String mimeType getServletContext().getMimeType(pathInfo); if (mimeType ! null) { resp.setContentType(mimeType); } // 生产环境设置缓存头 if (!development.equals(System.getenv(APP_ENV))) { resp.setHeader(Cache-Control, public, max-age31536000); } try (InputStream in getServletContext().getResourceAsStream(/static pathInfo); OutputStream out resp.getOutputStream()) { if (in null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } in.transferTo(out); } } }这个自定义StaticResourceServlet提供了路径安全校验自动MIME类型识别生产环境强缓存高效的流式传输4.2 异常处理机制统一的错误处理方案WebServlet(/error-handler) public class ErrorHandlerServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Integer statusCode (Integer) req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); Throwable exception (Throwable) req.getAttribute(RequestDispatcher.ERROR_EXCEPTION); TemplateEngine engine ThymeleafConfig.getTemplateEngine(req.getServletContext()); Context ctx new Context(); if (statusCode ! null) { ctx.setVariable(statusCode, statusCode); if (statusCode HttpServletResponse.SC_NOT_FOUND) { ctx.setVariable(message, 请求的资源不存在); } else if (statusCode HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { ctx.setVariable(message, 服务器内部错误); } } if (exception ! null) { ctx.setVariable(exception, exception); } resp.setStatus(statusCode ! null ? statusCode : HttpServletResponse.SC_INTERNAL_SERVER_ERROR); engine.process(error, ctx, resp.getWriter()); } }在web.xml中配置error-page error-code404/error-code location/error-handler/location /error-page error-page error-code500/error-code location/error-handler/location /error-page error-page exception-typejava.lang.Exception/exception-type location/error-handler/location /error-page5. 部署与性能调优5.1 Tomcat生产配置在conf/server.xml中优化连接器配置Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 maxThreads200 minSpareThreads10 acceptCount100 maxConnections10000 compressionon compressionMinSize1024 compressableMimeTypetext/html,text/xml,text/css,application/javascript,text/javascript serverUnknown /关键参数说明maxThreads根据CPU核心数设置通常为核心数*200compression启用Gzip压缩显著减少传输体积server隐藏Tomcat版本信息提高安全性5.2 JVM参数优化在bin/setenv.sh中设置export JAVA_OPTS-server -Xms2g -Xmx2g -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m -XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:ParallelGCThreads4 -Djava.awt.headlesstrue这些参数特别适合Servlet容器G1垃圾收集器减少Full GC停顿固定堆大小避免动态调整开销Headless模式节省GUI相关资源6. 常见问题解决方案6.1 中文乱码问题全方位编码设置检查清单Servlet中设置响应编码resp.setContentType(text/html;charsetUTF-8); resp.setCharacterEncoding(UTF-8);Thymeleaf模板解析器配置resolver.setCharacterEncoding(UTF-8);JSP页面(如果有)头部声明% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Tomcat server.xml连接器添加Connector ... URIEncodingUTF-8 /数据库连接字符串指定编码jdbc.urljdbc:mysql://localhost:3306/db?useUnicodetruecharacterEncodingUTF-86.2 静态资源404问题完整排查步骤确认资源文件位置必须在/src/main/webapp/static/目录下检查URL拼写Thymeleaf中应该用{/static/...}语法验证部署包内容解压target/xxx.war文件检查WEB-INF/classes/static/是否存在查看Tomcat日志确认请求是否到达服务器检查是否有权限错误测试直接访问在浏览器直接输入http://localhost:8080/context/static/css/style.css如果可访问说明是Thymeleaf路径问题6.3 模板缓存问题开发与生产环境的不同策略开发环境配置在ThymeleafConfig中resolver.setCacheable(false); // 关闭缓存 templateEngine.setCacheManager(null); // 禁用缓存管理器生产环境配置resolver.setCacheable(true); resolver.setCacheTTLMs(3600000L); // 1小时缓存 templateEngine.setCacheManager(new StandardCacheManager()); // 启用缓存强制清除缓存的方法开发时有用// 在Servlet中调用 templateEngine.clearTemplateCache();7. 监控与诊断7.1 内置监控端点添加以下Servlet提供基础监控WebServlet(/admin/monitor) public class MonitorServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Runtime runtime Runtime.getRuntime(); long maxMemory runtime.maxMemory(); long allocatedMemory runtime.totalMemory(); long freeMemory runtime.freeMemory(); MapString, Object stats new LinkedHashMap(); stats.put(jvm.uptime, ManagementFactory.getRuntimeMXBean().getUptime() ms); stats.put(jvm.memory, String.format( %.1fMB/%.1fMB (%.1f%%), (allocatedMemory - freeMemory) / 1024.0 / 1024, maxMemory / 1024.0 / 1024, (allocatedMemory - freeMemory) * 100.0 / maxMemory )); // 添加Thymeleaf缓存统计 if (templateEngine.getCacheManager() ! null) { stats.put(thymeleaf.cache.size, templateEngine.getCacheManager().getAllCaches().size()); } // 输出JSON响应 resp.setContentType(application/json); new ObjectMapper().writeValue(resp.getWriter(), stats); } }7.2 连接池监控Druid连接池的监控配置WebServlet(/admin/druid/*) public class DruidStatServlet extends StatViewServlet { Override public void init() throws ServletException { super.init(); // 添加认证逻辑 this.username admin; this.password admin123; } }在web.xml中配置servlet servlet-nameDruidStatView/servlet-name servlet-classcom.your.package.DruidStatServlet/servlet-class /servlet servlet-mapping servlet-nameDruidStatView/servlet-name url-pattern/admin/druid/*/url-pattern /servlet-mapping访问/admin/druid可以看到完整的SQL监控、URI监控等功能。
返回列表