多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

JSP日期处理全攻略:格式化、时区与LocalDateTime实战方案

JSP日期处理全攻略:格式化、时区与LocalDateTime实战方案 在实际的Java Web项目里JSP页面上的日期处理是我见过翻车最多的基础功能之一。明明只是一个日期格式化愣是能搞出时区错乱、年份少一年、英文月份、性能慢到页面崩溃等各种事故。这个标题看起来简单背后牵扯的东西却不少后端Java代码里怎么取时间、传参格式怎么统一、JSP页面上怎么显示、遇到null怎么处理、日志里怎么排查。这篇文章我先把JSP日期处理的完整技术链路拆开再给出一套能直接抄作业的方案适合刚入门的Java Web开发者和正在做传统JSP项目毕设的同学参考。1. JSP日期处理的整体思路拆解先搞清楚日期在哪里被处理1.1 一个日期在JSP项目中要经过的三层处理很多新手拿到“JSP日期处理”这个需求第一反应就是在JSP页面里写死一个日期格式或者翻出SimpleDateFormat直接在页面里new一个对象。这种做法能跑但通常撑不过第二个需求。一个日期从数据库到用户浏览器至少要经过三个位置后端Java代码里取数、赋值到实体/封装对象、JSP页面上渲染输出。严格来说还可能经过前端JavaScript二次格式化以及浏览器最终显示这一步。任何一个环节的格式约定不一致页面上的日期就会“看起来不对劲”。我的习惯是这样的后端负责标准化统一存时间戳或标准格式字符串JSP页面负责展示格式化用JSTL标签或EL表达式来做前端交互比如日期选择器、倒计时用JavaScript单独处理。这样一层归一层排查问题的时候只需要按链路定位。比如说用户注册时间数据库里存的是DATETIME类型MyBatis查出来映射成java.util.Date传给JSP。如果JSP页面直接用${user.regTime}输出出来的是一串“Sat Jun 10 10:00:00 CST 2023”这种英文星期的格式丑到没法看。这时候就需要在页面层做格式化或者在实体类里提前格式化好。1.2 为什么不用页面内嵌Java代码来处理日期JSP页面内嵌% %Java代码是很多老教程的写法比如在页面里写一个% SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd); %这样的片段。我不推荐这么干原因有三个。第一JSP本质上是视图层职责是展示塞入大量Java代码会让页面变得极其难以维护。第二SimpleDateFormat本身有线程安全问题在多线程访问同一实例时会出数据错乱而JSP页面普遍被多个请求共享很容易踩中这个雷。第三页面里写Java代码无法利用JSTL标签和EL表达式的便捷性代码复用率低。正确做法是JavaBean在后台准备数据JSP页面用EL表达式${}取值用JSTL的fmt:formatDate标签做格式化。EL表达式处理null值比较友好空值输出是一个空串而不是“null”字符串这一点对用户体验影响很大。1.3 JSP项目里日期处理的五种常见场景结合我在实际项目中遇到的情况JSP日期处理大致分成以下五类场景每类的处理方案略有不同场景类型典型需求推荐处理位置列表页展示订单时间、文章发布时间精度到分钟或日期即可JSP页面用fmt:formatDate格式化为“yyyy-MM-dd HH:mm”详情页展示用户详情、商品详情可能要“yyyy年MM月dd日”后端在实体类提供格式化的getter方法或者JSP格式化表单提交用户填的生日、预约日期需要把字符串解析成日期入库后端接收String用DateTimeFormatter或SimpleDateFormat解析日期范围查询按开始日期和结束日期过滤列表后端接收字符串后转成时间范围注意“结束日期要加一天”的坑动态显示倒计时、实时时间前端JavaScript处理JSP只负责输出初始值我在做“JSP个人信息展示页面”这个热门需求时就是按照这五类场景逐一处理的。个人信息页往往同时涉及生日显示、注册时间显示、最近登录时间显示等正好涵盖了列表页展示和详情页展示两类格式化要求还不一样是练习日期处理的好素材。2. 后端Java日期处理的核心代码与原理2.1 实体类日期字段的最佳类型选择与理由很多JSP项目的实体类里日期字段还在用java.util.Date这倒不是说不能用但在新写的代码里我更推荐用java.time.LocalDate、LocalDateTime或者Instant这是JDK 8及以后版本提供的现代时间API。用传统Date类型的问题在于它不代表一个具体的日期而是一个时间戳的毫秒数打印出来是“Sat Jun 10 10:00:00 CST 2023”这种格式不具备任何自解释性。而且Date的很多方法比如getDay()、getYear()已经废弃命名混乱内部还包含时区信息容易在序列化和国际化时搞出幺蛾子。LocalDate只代表年月日适用于生日、节假日这类不需要时分秒的数据LocalDateTime代表年月日时分秒适用于订单时间、操作日志等Instant是时间线上的一个点适用于需要跨时区比较的时间。用这个组合类型本身就说明了语义代码的可读性大幅提升。比如用户实体类里 public class User { private LocalDate birthday; // 生日只需要年月日 private LocalDateTime regTime; // 注册时间需要精确到秒 private LocalDateTime lastLoginTime; // 最近登录时间 }2.2 SimpleDateFormat的线程安全问题详解如果你还在用SimpleDateFormat一定要知道它有一个经典问题线程不安全。SimpleDateFormat内部维护了一个Calendar对象format和parse操作都会修改这个Calendar的状态。当多个线程共享同一个SimpleDateFormat实例时一个线程正在解析日期另一个线程把Calendar改成别的值了前面那个线程就会拿到错误的结果。我见过一个生产事故一个JSP列表页每5秒自动刷新一次部署在多线程容器里页面上的时间偶尔会变成“2023年6月30日星期六”或者干脆解析异常抛到页面上。排查了一上午最后定位到是两个请求共用了Servlet中静态的SimpleDateFormat实例。解决方案有两个方向。一是每次用时new一个实例这种最简单适合并发量不高的场景二是用ThreadLocal包装一下每个线程持有自己的一份实例适合高并发的场景。在JSP项目里我通常建议直接换成DateTimeFormatter它本身就是线程安全的不需要额外的包装。2.3 基于JDK 8的日期格式化与解析实操JDK 8之后推荐用DateTimeFormatter来处理日期格式化和解析。它线程安全API语义清晰和LocalDateTime配合非常顺手。格式化输出当前时间 LocalDateTime now LocalDateTime.now(); DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss); String text now.format(formatter); // 输出2025-01-15 14:30:22解析字符串为日期对象 String birthdayStr 1998-08-20; DateTimeFormatter pattern DateTimeFormatter.ofPattern(yyyy-MM-dd); LocalDate birthday LocalDate.parse(birthdayStr, pattern); // 得到 LocalDate 对象可以直接存库解析时有一个容易忽略的点如果字符串自带时分秒但你的格式串里没写会出现DateTimeParseException。反过来也一样。所以格式串和实际字符串必须严格对应。处理用户表单输入时这种问题尤其常见因为用户可能输入“2023/06/10”、“2023年6月10日”、“2023-6-10”等各种奇奇怪怪的格式。我一般会做一个统一的日期解析工具类内部支持多种格式匹配 public static LocalDate parseFlexibleDate(String dateStr) { List patterns Arrays.asList(yyyy-MM-dd, yyyy/MM/dd, yyyy年MM月dd日, yyyy-M-d); for (String p : patterns) { try { DateTimeFormatter f DateTimeFormatter.ofPattern(p); return LocalDate.parse(dateStr, f); } catch (DateTimeParseException ignored) { } } throw new IllegalArgumentException(无法解析的日期格式: dateStr); }这样遇到用户输入不规范的情况不至于直接400报错给用户而是能兜住大多数常见写法。2.4 常用日期操作比较、计算与前后端传输日期处理不只是格式化还包括比较和计算。比如判断用户注册满一年了吗计算合同到期还有多少天这些场景在JSP后台逻辑中也很常见。LocalDate和LocalDateTime都实现了Comparable接口直接用isBefore、isAfter、isEqual比较即可 LocalDate deadline LocalDate.of(2025, 6, 1); if (LocalDate.now().isAfter(deadline)) { // 已过期 }日期计算用plusDays、plusMonths、minusDays等方法返回新对象原对象不变 LocalDate today LocalDate.now(); LocalDate oneMonthLater today.plusMonths(1); long daysBetween ChronoUnit.DAYS.between(today, oneMonthLater);这个方法很实用比如做会员到期提醒时判断“距离到期还有几天”直接拿到期日和今天做差值ChronoUnit.DAYS.between会返回一个long类型的相差天数。前后端传输日期时我建议后端统一输出两种格式一种是人看的YYYY-MM-DD HH:mm:ss用于页面展示一种是时间戳数字epoch millis用于JavaScript做日期计算。这样前端拿到时间戳想怎么格式化都行不受时区干扰。3. JSP页面上的日期显示与格式化技巧3.1 JSTL fmt:formatDate标签的使用与属性详解如果项目还在用JSP且引入了JSTL页面上的日期格式化推荐直接用fmt:formatDate标签这是最标准、最省事的方案。首先要在页面顶部引入JSTL核心库和格式化库 % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %然后在需要显示日期的地方 fmt:formatDate value${user.regTime} patternyyyy-MM-dd HH:mm:ss /fmt:formatDate的属性主要有这么几个value是必填的类型是java.util.Datepattern用来指定输出格式比如“yyyy-MM-dd”或“yyyy年MM月dd日”dateStyle和timeStyle可以指定预定义样式比如short、medium、long但这里有个坑如果dateStyle和timeStyle同时指定或者一个指定一个不指定组合逻辑很容易混乱我建议直接指定pattern虽然代码多几个字符但输出结果完全可控。有同学会问一个问题后端如果用的是LocalDateTimefmt:formatDate的value属性认不认答案是不认严格来说Tomcat 9以上的EL实现支持LocalDateTime但fmt:formatDate的value被限制为java.util.Date类型传入LocalDateTime会报错或者格式化失败。如果后端用了LocalDateTime通常有以下三个处理方案。第一个方案是在实体类里多提供一个返回Date类型的方法 public Date getRegTimeAsDate() { return Date.from(regTime.atZone(ZoneId.systemDefault()).toInstant()); }第二个方案是后端在进入页面之前手动转为Date或者在Controller/model里把LocalDateTime转成对应格式的字符串。JSP里的JavaBean和Servlet/Spring MVC的Model完全可以容纳字符串字段。第三个方案是不用fmt:formatDate标签直接用EL表达式配合自定义函数。EL表达式虽然不能直接格式化日期但可以通过自定义EL函数来调用静态方法 ${my:formatDateTime(user.regTime)}这个方法维护稍微麻烦一点需要写一个Tag EL函数类但灵活性最高。我个人最推荐的是方案一在实体类里增加一个额外的getter方法不改动原有结构对JSP页面的侵入最小。这种“实体类携带展示格式”的做法在传统JSP项目里很常见不算优雅但足够务实。3.2 EL表达式直接输出日期的结果异常原因新手常犯一个错误直接用${user.regTime}在页面上输出日期然后惊讶地发现显示出来的是“Wed Apr 10 10:30:00 CST 2024”这种格式。这是因为Object.toString()方法输出的结果就是这样。java.util.Date重写了toString()默认用英文星期的格式来表达时间。EL表达式不会自动帮你做格式化它只会调用对象的toString()方法。解决方案一种是改用fmt:formatDate一种是提前在实体类或工具类中把日期转成字符串。我在做实名认证展示页时用户身份证有效期是“2030.12.31”这种格式页面直接${user.certExpireDate}输出结果变成一大串英文和数字。后来在实体类里加了一个getCertExpireStr()方法返回“2030年12月31日”页面一律显示这个字段整个世界清净了。3.3 空值日期在JSP页面显示的三种处理方式日期为空是JSP页面上经常遇到的情况。用户没有填写生日、商品没有下架时间、管理员没有设置最后登录时间这些字段可能是null。如果直接使用fmt:formatDate它对于null的value会输出空串不会报错这是比较友好的一点。但如果你用自定义EL函数或者自己在页面里拼接字符串就要格外小心null导致NPENullPointerException。我常用的三种方式如下。第一种页面级兜底 fmt:formatDate value${user.birthday} patternyyyy-MM-dd /第二种默认值替代 c:choose c:when test${not empty user.birthday} fmt:formatDate value${user.birthday} patternyyyy-MM-dd / /c:when c:otherwise未设置/c:otherwise /c:choose第三种后端封装默认值。在实体类或VO类的getter方法里判空返回默认值 public String getBirthdayDisplay() { return birthday null ? 未设置 : birthday.format(DateTimeFormatter.ofPattern(yyyy-MM-dd)); }这三种方式各有适用场景页面级兜底适合所有时间字段都无所谓是否为空的情况默认值替代适合列表页让用户知道这一项没有填后端封装默认值适合需要在多个页面上复用的展示逻辑。3.4 基于标签库的JSP页面日期格式化完整代码示例这一节给出一个可直接参考的列表页日期展示片段。场景是展示用户列表需要显示注册时间和最后登录时间。假设后端在Model/request中放了一个List User实体类包含LocalDateTime regTime和lastLoginTime字段及对应的getter。JSP页面关键代码用户名注册时间最近登录${user.username}未知从未登录注意这里的value用的是${user.regTimeAsDate}对应实体类中的getRegTimeAsDate()方法。如果实体类里没有这个方法你可以在Servlet或Controller中遍历列表把时间对象转换为Date对象传给页面或者干脆在实体类里再加一个格式化后的字符串字段。这种组合方式同时兼顾了三个需求空值处理、格式定制、列表循环。fmt:formatDate配合c:forEach是JSP页面处理日期列表最常见的一套组合拳。4. 实战案例JSP个人信息展示页面中的日期字段全流程处理4.1 功能拆解与页面设计以热搜词“jsp个人信息展示页面”为例做一个完整的个人中心页面。这个页面通常包含以下和日期相关的字段生日用户自己填写格式为“1995年8月20日”注册时间系统自动记录格式为“2023年6月10日 14:23:05”最近登录时间系统自动更新格式为“半小时前”或具体时间会员到期时间如果有会员体系需要显示“2026年12月31日”实名认证有效期可能显示“2030.05.20”这种格式这里面就涉及了“详情页展示”和“动态时间显示”两类场景正好能覆盖绝大多数日期处理的细节。我需要先梳理一下数据库字段类型再设计实体类然后是Controller/Servlet层的处理逻辑最后是JSP页面的展示。对于这个页面我的实现思路是把“存储格式”和“展示格式”分离。数据库统一存标准时间实体类里提供多个面向不同展示场景的getter方法页面按需取用。4.2 数据库表结构设计与实体类POJO实现数据库表这里以MySQL为例日期字段统一使用DATETIME类型生日用DATE类型。DATETIME比TIMESTAMP的好处是范围更大而且不受2038年问题限制适合长期存储。CREATE TABLEuser(idint(11) NOT NULL AUTO_INCREMENT,usernamevarchar(50) NOT NULL,birthdaydate DEFAULT NULL,reg_timedatetime DEFAULT NULL,last_login_timedatetime DEFAULT NULL,vip_expire_timedatetime DEFAULT NULL,cert_expire_datedate DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;实体类里我推荐用LocalDate对应DATELocalDateTime对应DATETIME。配合MyBatis 3.4.5以上版本或者较新的JDBC驱动可以自动完成类型映射不需要额外写TypeHandler。public class User { private Integer id; private String username; private LocalDate birthday; private LocalDateTime regTime; private LocalDateTime lastLoginTime; private LocalDateTime vipExpireTime; private LocalDate certExpireDate; // 省略 getter/setter }这里有个细节如果项目使用的是老版本MyBatis plus或Hibernate日期类型映射可能不支持LocalDate/LocalDateTime会报“无法识别Java 8日期类型”之类的错。这种情况下要么升级依赖版本要么在配置里注册JSR310支持要么退回到java.util.Date字段。我遇到过项目用了MyBatis 3.4.2升级到3.4.6才解决。4.3 Servlet/Controller层的日期数据准备与转换逻辑在Servlet或Spring MVC的Controller里要做三件事查询用户基本信息把注册时间和最近登录时间装入User对象计算最近登录时间的相对显示比如“刚刚”“5分钟前”“昨天”给JSP页面返回数据。注册时间这里我做一个简单的抽闲计算逻辑写一个工具方法public static String getRelativeTime(LocalDateTime targetTime) { LocalDateTime now LocalDateTime.now(); Duration duration Duration.between(targetTime, now); long minutes duration.toMinutes(); if (minutes 1) { return 刚刚; } else if (minutes 60) { return minutes 分钟前; } else if (minutes 24 * 60) { return (minutes / 60) 小时前; } else { return targetTime.format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm)); } }然后在获取用户信息的接口里public void getUserInfo(HttpServletRequest req, HttpServletResponse resp) { User user userService.getById(1); // 页面中显示的额外字段 user.setLastLoginDisplay(getRelativeTime(user.getLastLoginTime())); req.setAttribute(user, user); req.getRequestDispatcher(/userInfo.jsp).forward(req, resp); }这里我把“相对时间”字符串算好后直接放到实体类的冗余字段里页面拿着这个字段展示即可。实体类增加一个lastLoginDisplay字符串字段这就是传统JSP项目的典型做法。另外如果页面需要展示“会员是否已过期”也建议在后端提前算好。避免在JSP里写复杂的比较逻辑。4.4 个人信息JSP页面的完整代码与效果预期个人信息页面userInfo.jsp的关键代码% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %个人信息个人信息用户名${user.username}生日未填写注册时间最近登录${user.lastLoginDisplay}会员到期无会员这里用到了user.birthdayAsDate、user.regTimeAsDate、user.vipExpireTimeAsDate这些额外的getter方法它们负责把LocalDate/LocalDateTime转换为java.util.Date以便fmt:formatDate渲染。实体类里的转换方法写法如下public Date getBirthdayAsDate() { return birthday ! null ? Date.from(birthday.atStartOfDay(ZoneId.systemDefault()).toInstant()) : null; }public Date getRegTimeAsDate() { return regTime ! null ? Date.from(regTime.atZone(ZoneId.systemDefault()).toInstant()) : null; }public Date getVipExpireTimeAsDate() { return vipExpireTime ! null ? Date.from(vipExpireTime.atZone(ZoneId.systemDefault()).toInstant()) : null; }页面上最终的渲染效果是生日显示为“1995年08月20日”注册时间显示为“2023-06-10 14:23:05”最近登录显示为“5分钟前”会员到期显示为“2026年12月31日”视觉效果干净且统一没有英文星期没有默认的CST标识空值也不会刺眼。4.5 表单提交场景中的日期解析与格式规整个人信息页面除了展示还有编辑功能用户提交生日时通常是一个“1995-08-20”的字符串。在Servlet/Controller里解析String birthdayParam req.getParameter(birthday); if (birthdayParam ! null !birthdayParam.trim().isEmpty()) { DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd); LocalDate birthday LocalDate.parse(birthdayParam.trim(), formatter); user.setBirthday(birthday); }前端表单里我一般用typedate的input这样浏览器会强制按“YYYY-MM-DD”的格式提交后端解析就省心多了。不过这有一个兼容性问题某些旧版浏览器把typedate当成typetext来渲染用户输入“2023年6月10日”这种格式后端就会被解析报错。我的兜底方案是在后端写一个宽松解析方法格式匹配不上多个候选格式时抛出友好的业务异常返回给用户“日期格式不正确请使用YYYY-MM-DD格式”的提示而不是500页面。表单里如果用日期选择器比如laydate、flatpickr注意和JSP的结合方式JSP渲染初始值JavaScript日期选择器负责交互提交的格式由JS统一成“YYYY-MM-DD”。这个分工对用户体验和开发体验都比较友好。5. 常见日期处理问题与排查技巧实录5.1 日期显示少了几个月或一天的原因与解法有同学遇到过这样的问题数据库里存的是“2023-06-10”页面上显示成“2023-05-10”或者“2023-06-09”。这种问题十有八九是时区问题。MySQL连接串里如果没有指定serverTimezone默认可能按服务器时区或UTC来处理。比如你的MySQL服务器在时区Asia/Shanghai但JDBC连接串用了UTC那从库里查出的DATETIME会被JDBC自动转换一次转换后显示出来的时间就少了8小时。排查步骤是先检查MySQL连接串有没有加serverTimezoneAsia%2FShanghaiUTC8对应Asia/Shanghai。再检查JVM默认时区在代码里打印一下TimeZone.getDefault().getID()看看是不是Asia/Shanghai。最后检查数据库服务器的time_zone变量执行SELECT global.time_zone, session.time_zone;查看。推荐统一的做法是MySQL连接串显式指定 jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai这样JDBC、JVM、数据库三者的时区就能一致对齐。5.2 fmt:formatDate格式化出错时怎么快速定位fmt:formatDate报错通常集中在两个点。第一是类型不支持。刚才提到过如果value传入的是LocalDateTime或LocalDate格式化会失败或者输出不对。快速判断方法看页面上是否有“Cannot convert”或“No value”相关的错误堆栈或者在后端临时打印一下传到页面的数据的class类型。第二是pattern写法错误。pattern里如果写了“YYYY”大写它会表示“Week Year”而不是“Year”小写yyyy才是年份。Week Year在跨年周的时候会和真实年份差一年。比如2024年1月1日如果是上一年的最后一周按YYYY格式化会输出2023。这绝对是经典坑我的建议是年份永远使用小写yyyy。顺带提醒一下月份符号大写的MM是月份小写的mm是分钟。如果你写了“yyyy-mm-dd”分号前面的mm会被当成分钟解析时会直接报错或者显示异常数据。5.3 页面显示英文/乱码日期时的整链路检查顺序如果页面上日期变成“Wed Jun 10 14:23:05 CST 2023”或者“Jun 10, 2023”这种英文格式说明页面显示的实际上是Date对象的默认toString输出。检查链路按照这个顺序来第一步看JSP代码用的是不是${user.regTime}原样输出。如果用了fmt:formatDate标签还出现英文就检查标签库的URI引用是否写对JSTL库是否成功引入。第二步看实体类里是不是同时存在getRegTime()和getRegTimeAsDate()方法而JSP里无意中把RegTimeAsDate写成了RegTime。第三步看后端返回的字段是不是已经是字符串并在Controller中做了格式化。如果后端的格式化用的还是默认Locale英文也会显示英文月份。可以用标签的locale属性指定中文环境。实际上显示英文最多的情况就是压根没用格式化标签直接原样输出了这一点从代码上就能一眼看出。5.4 日期范围内的查询边界问题结束日期总是查不到当天做日期范围查询的时候有个高频问题用户选择“2025-01-01 到 2025-01-15”结果1月15日的数据查不出来。原因是数据库里存的时间是“2025-01-15 14:23:05”这种完整datetime而后端查询条件只用了“2025-01-15”。正确做法是结束日期加一天用开区间排除边界 WHERE create_time 2025-01-01 00:00:00 AND create_time 2025-01-16 00:00:00用小于结束日期的下一天而不是小于等于结束日期当天。这样避免了23:59:59.999的精度问题这也是MySQL DATETIME(3)等毫秒精度字段经常会遇到的问题。在Java端计算结束区间的下一天 LocalDate startDate LocalDate.parse(2025-01-01); LocalDate endDate LocalDate.parse(2025-01-15); LocalDateTime startTime startDate.atStartOfDay(); LocalDateTime endTime endDate.plusDays(1).atStartOfDay();然后用startTime和endTime作为查询参数传入Mapper。5.5 传统JSP项目的日期处理性能注意事项最后聊一下性能。传统JSP项目通常是同步渲染的每一个页面上的日期格式化都会消耗少量CPU。绝大多数情况下这不是瓶颈但有几个细节值得注意。第一避免在JSP里反复new SimpleDateFormat。如果非要用SimpleDateFormat尽量在工具类里用ThreadLocal包一个但最推荐的还是DateTimeFormatter线程安全。这一点在并发高的JSP页面上尤其明显。第二不要把大列表里的日期字段全部放在实体类getter里做复杂计算。比如在每一行的getter里调用“相对时间”算法处理逻辑不复杂还好如果里面涉及数据库查询或者远程调用那就把性能拖垮了。相对时间等计算应该在Service层提前算好放在冗余字段里JSP只负责取。第三JSP页面输出日期尽量使用fmt:formatDate标签它经过JSTL实现优化比在Scriptlet里写Java格式化逻辑更可靠。而且JSTL的格式化标签会缓存已解析的pattern同一页面大量使用也不会频繁触发内部解析。写在最后的几点实操心得做了不少Java Web项目之后我对JSP日期处理的最大体会是它在技术上不复杂但对代码规范的要求特别高。很多问题的根源都是混用类型、混用时区、混用Java代码和标签库职责。现在新项目大概率已经拥抱前后端分离了但校园实训、老系统维护、传统JSP毕设依旧大量存在这套方案依然实用。如果你正在做“基于JSP的毕设选题”我建议在处理日期时尽量统一做到下面几点数据库字段用DATETIME/DATE表达语义实体类用LocalDate/LocalDateTime对应页面展示用JSTL的fmt:formatDate表单解析用DateTimeFormatter查询边界用前闭后开。这些习惯一旦养成不管以后写JSP还是Spring Boot都少踩一半日期相关的坑。最后再分享一个小技巧如果项目里大量页面都需要同样的日期格式不要每个页面都写一遍pattern。把格式化串统一放在一个常量类里比如DateFormatConst.FULL_TIME等JSP里直接引用这些常量字符串。这样等业务方某一天忽然要求“时间显示改成24小时制带秒”你只需要改一个地方而不是全局搜索替换所有JSP页面。
返回列表