XHTML中JSTL标签嵌套引发div未闭合错误的解决咨询
问题分析与解决方案
你遇到的这个错误本质是XHTML的严格语法要求和JSTL标签动态生成内容之间的冲突。XHTML解析器会在编译阶段检查所有标签是否正确闭合,而你的JSTL逻辑把<div>的开始标签放在一个<c:when>块里,结束标签放在另一个<c:when>块里,这在静态解析时就会被判定为标签未闭合——哪怕服务器端运行时能正确生成完整的HTML结构。
你的原始报错代码
<c:forEach var="item" items="${menu}"> <c:choose> <c:when test="${fn:startsWith(item.key, 'startDropdown.')}"> <c:set var="dropdown" scope ="session" value="true" /> <c:set var="dropdownnaam" scope ="session" value="${fn:substringAfter(item.key, 'startDropdown.')}" /> <li class="nav-item dropdown"> <a id="${dropdownnaam}" href="#" role="button" class="nav-link dropdown-toggle" data-toggle="dropdown"> #{i18nTeksten.tekst(item.value)}<b class="caret"></b> </a> <div class="dropdown-menu dropdown-menu-center" aria-labelledby="${dropdownnaam}"> </c:when> <c:when test="${item.key.equals('endDropdown')}"> <c:set var="dropdown" scope ="session" value="false"/> </div> </li> </c:when> <c:when test="${dropdown.equals('true')}"> <a class="dropdown-item" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a> </c:when> <c:otherwise> <li class="nav-item"> <a class="nav-link" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a> </li> </c:otherwise> </c:choose> </c:forEach>
报错信息
Error Traced[line: 29] The element type "div" must be terminated by the matching end-tag "".
解决方法:三种方案按需选择
方案1:快速规避——改用JSP页面
如果项目允许,把页面后缀从.xhtml改成.jsp。JSP的解析器对标签闭合的要求更宽松,它会在服务器端处理完所有JSTL标签后再生成最终HTML,不会在静态编译阶段检查标签是否跨块闭合。这是改动最小的解决方法,几乎不需要修改现有逻辑。
方案2:调整JSTL逻辑——保证静态标签完整闭合
如果你必须保留XHTML格式,需要重构逻辑,让每个HTML标签的开始和结束都在同一个JSTL代码块内。可以把下拉菜单的容器逻辑调整为:
<c:forEach var="item" items="${menu}"> <c:choose> <!-- 初始化下拉菜单容器 --> <c:when test="${fn:startsWith(item.key, 'startDropdown.')}"> <c:set var="dropdown" scope="session" value="true" /> <c:set var="dropdownnaam" scope="session" value="${fn:substringAfter(item.key, 'startDropdown.')}" /> <!-- 提前写出完整的下拉容器结构,后续子项用条件判断插入 --> <li class="nav-item dropdown"> <a id="${dropdownnaam}" href="#" role="button" class="nav-link dropdown-toggle" data-toggle="dropdown"> #{i18nTeksten.tekst(item.value)}<b class="caret"></b> </a> <div class="dropdown-menu dropdown-menu-center" aria-labelledby="${dropdownnaam}"> <!-- 这里留空,后续子项会通过<c:if>插入 --> </div> </li> </c:when> <!-- 插入下拉子项 --> <c:when test="${dropdown}"> <!-- 通过JSTL动态修改已生成的div内容?不,换个思路:用<c:if>判断当前是否在下拉模式,直接输出子项到页面 --> <!-- 注意:这里需要确保子项被正确包含在下拉容器内,所以需要把容器的生成和子项输出结合起来 --> <c:if test="${!item.key.equals('endDropdown')}"> <a class="dropdown-item" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a> </c:if> </c:when> <!-- 关闭下拉模式 --> <c:when test="${item.key.equals('endDropdown')}"> <c:set var="dropdown" scope="session" value="false"/> </c:when> <c:otherwise> <li class="nav-item"> <a class="nav-link" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a> </li> </c:otherwise> </c:choose> </c:forEach>
这种方法能解决语法问题,但逻辑相对绕,后期维护成本较高。
方案3:推荐——将菜单逻辑移到服务端Java类
这是最规范、可维护的方案:把菜单的构建逻辑移到后端,生成结构化的菜单对象(而非扁平的键值对列表),再传到页面遍历生成。
后端示例代码
// 定义菜单实体类 public class MenuItem { private String key; private String value; private boolean isDropdown; private List<MenuItem> subItems; // 省略getter/setter } // 在Controller中构建菜单数据 List<MenuItem> menu = new ArrayList<>(); // 添加普通菜单项 MenuItem homeItem = new MenuItem(); homeItem.setKey("/home"); homeItem.setValue("home.label"); homeItem.setDropdown(false); menu.add(homeItem); // 添加下拉菜单 MenuItem productDropdown = new MenuItem(); productDropdown.setKey("products"); productDropdown.setValue("products.label"); productDropdown.setDropdown(true); // 添加下拉子项 MenuItem product1 = new MenuItem(); product1.setKey("/product1"); product1.setValue("product1.label"); product1.setDropdown(false); productDropdown.setSubItems(Arrays.asList(product1)); menu.add(productDropdown); // 将菜单数据放入请求域 request.setAttribute("menu", menu);
页面代码(完全符合XHTML语法)
<c:forEach var="item" items="${menu}"> <c:choose> <c:when test="${item.dropdown}"> <li class="nav-item dropdown"> <a id="${item.key}" href="#" role="button" class="nav-link dropdown-toggle" data-toggle="dropdown"> #{i18nTeksten.tekst(item.value)}<b class="caret"></b> </a> <div class="dropdown-menu dropdown-menu-center" aria-labelledby="${item.key}"> <c:forEach var="subItem" items="${item.subItems}"> <a class="dropdown-item" href="#{subItem.key}">#{i18nTeksten.tekst(subItem.value)}</a> </c:forEach> </div> </li> </c:when> <c:otherwise> <li class="nav-item"> <a class="nav-link" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a> </li> </c:otherwise> </c:choose> </c:forEach>
这种写法完全符合XHTML的严格语法,逻辑清晰,后续新增菜单类型或修改结构也更方便。
总结
- 追求最快解决:改用JSP页面;
- 必须保留XHTML:调整JSTL逻辑保证标签闭合;
- 追求长期可维护:将菜单逻辑移到服务端,使用结构化数据生成菜单(推荐)。
内容的提问来源于stack exchange,提问作者Marco




