5.2 JSTL核心标签实战


5.2 JSTL核心标签实战

本节摘要:JSTL 是视图控制流的标准标签库:c:if 与 c:choose 接管条件渲染,c:forEach 接管循环并自动管理循环状态,fmt 标签库管格式化,fn 函数库补齐 EL 的字符串能力。上一站解决了"取值",本站解决"判断与循环"——两者清零之后,视图去脚本化即宣告完工。本节以图书列表页为完整样本,给出改造前后的对照代码。

把循环和判断交还给标签

判断是最先该换掉的脚本。改造前的写法谁都见过:

<% if (user != null && "admin".equals(user.getRole())) { %> <li>库存管理入口</li> <% } %>

判空、类型、比较全挤在百分号里,HTML 结构被 Java 语句切得七零八落。换成 c:if 后:

<c:if test="${requestScope.user.role eq 'admin'}"> <li>库存管理入口</li> </c:if>

判空被 EL 的宽容语义吸收了——user 为空时整个条件为假,不需要显式判空;比较用 eq 而不是双等号,也避开了一些解析歧义。多分支场景用 c:choose 三件套,语义就是"多选一",比串联多个 c:if 清晰得多:

<c:choose> <c:when test="${book.stock gt 10}"><span>现货充足</span></c:when> <c:when test="${book.stock gt 0}"><span>仅剩少量</span></c:when> <c:otherwise><span>暂时售罄</span></c:otherwise> </c:choose>

循环是 JSTL 最能打的地方。c:forEach 不只迭代,还通过 varStatus 暴露循环状态——索引、计数、是否首个、是否末个,老代码里手工维护的循环变量全部免了:

<c:forEach items="${requestScope.books}" var="book" varStatus="st"> <tr class="${st.index % 2 eq 0 ? 'even' : 'odd'}"> <td>${st.count}</td> <td>${book.title}</td> <td>${book.price}</td> <td><c:if test="${st.last}">末行标记</c:if></td> </tr> </c:forEach>

三行属性记牢就够用大半场景:index 是零起点下标,count 是从一开始的序号,last 标记末次迭代。偶数行样式、行序号、末行特殊处理这些设计稿常客,全靠它们。

格式化与函数库:两个专职副手

核心标签之外,两个副手库在实战里出场率极高。fmt 库管"人看的格式":日期、数字、货币。fn 库管"字符串加工",以函数形态嵌进 EL 表达式:

<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %> <%@ taglib prefix="fn" uri="jakarta.tags.functions" %> <fmt:formatDate value="${order.createdAt}" pattern="yyyy-MM-dd HH:mm"/> <fmt:formatNumber value="${book.price}" type="currency"/> <p>简介:${fn:substring(book.intro, 0, 60)}……</p> <p>${fn:length(requestScope.books)} 本图书</p> <c:if test="${fn:contains(book.title, '典藏')}">典藏版</c:if>

fmt 的价值在"格式与数据分离":日期对象只有一种内存形态,展示格式却随场景变——列表页要短格式、详情页要长格式,改需求时只动 pattern 属性,不碰任何 Java 代码。fn 的价值在给 EL 续命:EL 本身不提供方法调用,取长度、截子串、判断包含这些高频小活,全由 fn 以纯函数形态补上。两者共用同一个机制——都是标签库,都靠 3.2 节讲的 taglib 指令引入页面。

案例:图书列表页去脚本化全程

背景:列表页是青梧书肆脚本密度最高的页面:取九百多个字符的介绍、循环画书行、奇偶行样式、库存角标判断,百分号出现四十多次。前端同学一直投诉改不了这个页面。它也是全站页面的改造模板,做好了其他页面照方抓药。

操作:三步走。第一步换取值,所有 <%= %> 表达式按上一站的规则改成带 requestScope 前缀的 EL;第二步换控制流,if 换 c:if、多分支换 c:choose、for 循环换 c:forEach 并把手工计数改为 varStatus;第三步补格式化,价格与日期换成 fmt 标签,截断与长度判断换成 fn 函数。改造用第 1 章搭好的本地环境逐页验证,渲染结果与老版本逐像素比对。

结果:页面百分号归零,代码行数从两百一十行降到一百四十行,其中真正属于"页面"的 HTML 结构占比首次超过八成。前端同学当天就在这个页面上完成了视觉改版——这是改造收益最直观的一次确认。

解读:全程没有遇到架构级困难,难点都在细节:奇偶行样式要用 index 而不是 count(前者零起点才能配合取余判断);售罄角标原代码在循环里查了数据库(每行一查),顺势把批量取库存的活儿挪回了控制器——视图改造常常顺手揪出这类藏在循环里的性能炸弹,第 8 章体检时会再见到它们。

变式:如果页面里有标准标签实在覆盖不了的逻辑(比如把订单号按业务规则加校验位),有两条正路:一是把加工逻辑放进控制器或服务层,视图只拿成品数据——永远优先;二是铸造成自定义标签或 EL 函数,供全站复用——这正是下一章的主题。

⚠️ 常见坑:改造时最容易把"页面里没脚本"做成"页面里没逻辑做不了的事全塞进控制器"——控制器随即被展示细节淹死。记住分工:数据加工归服务层,视图编排归控制器,页面只负责画。塞给控制器的那部分,本质还是视图逻辑,应继续下沉。

标签选型的两条优先级

实战里标签选型总在打架:同样一段逻辑,用标准标签、自定义标签还是干脆让控制器算好?两条优先级可以把选择变成机械判断。第一优先级:能算的不如能装的——凡是纯计算(合计、统计、状态文案),优先在控制器或服务层算好放进托盘,视图只负责展示,标签越少越好。第二优先级:能复用标准的就不自铸——标准标签经过千锤百炼,自铸标签的维护成本永远被低估;只有当同样的结构模式在五处以上出现、且标准标签表达不了时,才轮到第 6 章的车间开炉。两条优先级合用,视图里的标签总量会自然收敛,页面可读性随之上升——青梧书肆改造完成后,全站自定义标签只用了三组,标准标签密度也比改造前低了两成,这正是收敛的痕迹。

本节要点回顾

  • 判断三件套:c:if 管单条件,c:choose 管多选一,test 属性里写 EL——宽容语义让显式判空大多省掉;
  • forEach 自带状态机:index、count、first、last 覆盖奇偶行、序号、末行判断,手工循环变量全部退役;
  • fmt 管格式 fn 管加工:格式与数据分离、纯函数嵌进表达式,两个副手覆盖视图层最后一批杂活;
  • 改造三步法:先取值、再控制流、后格式化,逐页验证、逐像素比对,风险极低;
  • 顺手揪出循环里的查询:视图改造是发现"每行一查"这类性能病灶的最佳时机,发现即上报。

大扫除完成,页面终于干净了。但总有一天你会遇到标准标签覆盖不了的需求——下一章开自定义标签车间,亲手铸造你自己的标签。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U