从Struts1/Tiles1迁移到Struts2/Tiles3触发StackOverflowError
Struts 1.3/Tiles 1.x 迁移至 Struts 2/Tiles 3 菜单改造后出现StackOverflowError
背景
从Struts 1.3/Tiles 1.x组合迁移至Struts 2/Tiles 3,由于Tiles 3不再支持在定义中声明bean实现菜单项功能,因此对相关配置及页面进行改造,改造后运行触发java.lang.StackOverflowError,寻求解决方案。
原实现代码
原Tiles定义
<definition name=".selectPatient.menu" path="/WEB-INF/templates/menu.jsp"> <putList name="items"> <bean classtype="org.a.m.s.jsp.MenuItem"> <set-property property="id" value="menubar_register"/> <set-property property="name" value="menubar_register"/> <set-property property="title" value="Register"/> <set-property property="onclick" value="registerPatient()"/> <set-property property="text" value="Register"/> <set-property property="roles" value="BASIC,LEAD,SUPER"/> </bean> <bean classtype="org.a.m.s.jsp.MenuItem"> <set-property property="id" value="menubar_multitransaction"/> <set-property property="name" value="menubar_multitransaction"/> <set-property property="title" value="Post Multiple Transactions"/> <set-property property="onclick" value="postMultiTransactions()"/> <set-property property="text" value="Post Multiple Transactions"/> <set-property property="roles" value="BASIC,LEAD,SUPER"/> </bean> </putList> </definition>
原menu.jsp
<logic:iterate id="item" name="items" type="org.a.m.s.jsp.MenuItem"> <%if (UserRoles.userHasRole(request,item.getRoles())){%><td><table id="button_<bean:write name="item" property="id"/>" cellpadding="0" cellspacing="0" border="0"><tr><td><img src="images/menu_button_round_edge_left2.gif"></td> <td style="border-top:1px white solid; border-bottom:1px white solid;"> <a name="<bean:write name="item" property="name"/>" id="<bean:write name="item" property="id"/>" accesskey="M" title="<bean:write name="item" property="title"/>" class="menuItem" href="#" isDisabled="false" onclick="if (this.isDisabled != 'true'&&isPageReady()) <bean:write name="item" property="onclick"/>;" onmouseover="if (this.isDisabled != 'true'&&isPageReady())document.getElementById('button_<bean:write name="item" property="id"/>').style.backgroundColor='#ffff99';" onmouseout="if (this.isDisabled != 'true'&&isPageReady())document.getElementById('button_<bean:write name="item" property="id"/>').style.backgroundColor='#000066';">"<bean:write name="item" property="text"/></a></td> <td><img src="images/menu_button_round_edge_right2.gif" width="9" height="17"></td> </tr></table></td> <%}%> </logic:iterate>
改造后代码
新Tiles定义
<definition name="defaultMenuComponent" template="/WEB-INF/templates/barbutton.jsp"> </definition> <definition name="menubar_register" extends="defaultMenuComponent"> <put-attribute name="id" value="menubar_register" cascade="true"/> <put-attribute name="name" value="menubar_register" cascade="true"/> <put-attribute name="title" value="Register" cascade="true"/> <put-attribute name="onclick" value="registerPatient()" cascade="true"/> <put-attribute name="text" value="Register" cascade="true"/> <put-attribute name="roles" value="BASIC,LEAD,SUPER" cascade="true"/> </definition> <definition name="menubar_multitransaction" extends="defaultMenuComponent"> <put-attribute name="id" value="menubar_multitransaction" cascade="true"/> <put-attribute name="name" value="menubar_multitransaction" cascade="true"/> <put-attribute name="title" value="Post Multiple Transactions" cascade="true"/> <put-attribute name="onclick" value="postMultiTransactions()" cascade="true"/> <put-attribute name="text" value="Post Multiple Transactions" cascade="true"/> <put-attribute name="roles" value="BASIC,LEAD,SUPER" cascade="true"/> </definition> <definition name=".selectPatient.menu" template="/WEB-INF/templates/menu.jsp"> <put-list-attribute name="items"> <add-attribute value="menubar_register" type="definition"/> <add-attribute value="menubar_multitransaction" type="definition"/> </put-list-attribute> </definition>
barbutton.jsp
<table id="button_<tiles:insertAttribute name="id" flush="true"/>" cellpadding="0" cellspacing="0" border="0"> <tr> <td><img src="images/menu_button_round_edge_left2.gif"></td> <td style="border-top:1px white solid; border-bottom:1px white solid;"> <a name="<tiles:insertAttribute name="name" flush="true"/>" id="<tiles:insertAttribute name="id" flush="true"/>" accesskey="M" title="<tiles:insertAttribute name="title" flush="true"/>" class="menuItem" href="#" isDisabled="false" onclick="if (this.isDisabled != 'true'&&isPageReady()) <tiles:insertAttribute name="onclick" flush="true"/>;" onmouseover="if (this.isDisabled != 'true'&&isPageReady())document.getElementById('button_<tiles:insertAttribute name="id" flush="true"/>').style.backgroundColor='#ffff99';" onmouseout="if (this.isDisabled != 'true'&&isPageReady())document.getElementById('button_<tiles:insertAttribute name="id" flush="true"/>').style.backgroundColor='#000066';">"<tiles:insertAttribute name="text" flush="true"/> </a> </td> <td><img src="images/menu_button_round_edge_right2.gif" width="9" height="17"></td> </tr> </table>
修改后的menu.jsp
<tilesx:useAttribute id="items" name="items" classname="java.util.List"/> <table border=0 cellpadding=0 cellspacing=1> <tr> <c:forEach items="${items}" var="listItem"> <td> <tiles:insertDefinition name="${listItem}" flush="true"/> </td> </c:forEach> </tr> </table>
问题分析
出现StackOverflowError的核心原因是循环引用:
- 在
.selectPatient.menu定义中,将menubar_register等定义添加到items列表中 - 当
menu.jsp通过<tiles:insertDefinition>渲染这些子定义时,若子定义存在间接/直接引用父定义的配置,或Tiles解析定义时出现递归逻辑,就会触发无限递归调用,最终导致栈溢出。
另外,改造后丢失了原代码中的角色校验逻辑,这部分功能也需要补回。
解决方案
1. 修复循环引用,调整Tiles定义结构
将子定义从独立可插入的Tiles定义改为属性组,避免递归解析:
<definition name=".selectPatient.menu" template="/WEB-INF/templates/menu.jsp"> <put-list-attribute name="items"> <add-attribute> <put-attribute name="id" value="menubar_register"/> <put-attribute name="name" value="menubar_register"/> <put-attribute name="title" value="Register"/> <put-attribute name="onclick" value="registerPatient()"/> <put-attribute name="text" value="Register"/> <put-attribute name="roles" value="BASIC,LEAD,SUPER"/> </add-attribute> <add-attribute> <put-attribute name="id" value="menubar_multitransaction"/> <put-attribute name="name" value="menubar_multitransaction"/> <put-attribute name="title" value="Post Multiple Transactions"/> <put-attribute name="onclick" value="postMultiTransactions()"/> <put-attribute name="text" value="Post Multiple Transactions"/> <put-attribute name="roles" value="BASIC,LEAD,SUPER"/> </add-attribute> </put-list-attribute> </definition>
2. 修复menu.jsp,补全角色校验逻辑
修改后的menu.jsp直接渲染属性,无需调用insertDefinition:
<tilesx:useAttribute id="items" name="items" classname="java.util.List"/> <table border=0 cellpadding=0 cellspacing=1> <tr> <c:forEach items="${items}" var="menuItem"> <% String roles = (String) ((Map)pageContext.getAttribute("menuItem")).get("roles"); if (UserRoles.userHasRole(request, roles)) { %> <td> <table id="button_${menuItem.id}" cellpadding="0" cellspacing="0" border="0"> <tr> <td><img src="images/menu_button_round_edge_left2.gif"></td> <td style="border-top:1px white solid; border-bottom:1px white solid;"> <a name="${menuItem.name}" id="${menuItem.id}" accesskey="M" title="${menuItem.title}" class="menuItem" href="#" isDisabled="false" onclick="if (this.isDisabled != 'true'&&isPageReady()) ${menuItem.onclick};" onmouseover="if (this.isDisabled != 'true'&&isPageReady())document.getElementById('button_${menuItem.id}').style.backgroundColor='#ffff99';" onmouseout="if (this.isDisabled != 'true'&&isPageReady())document.getElementById('button_${menuItem.id}').style.backgroundColor='#000066';">${menuItem.text} </a> </td> <td><img src="images/menu_button_round_edge_right2.gif" width="9" height="17"></td> </tr> </table> </td> <% } %> </c:forEach> </tr> </table>
3. 清理冗余配置
删除defaultMenuComponent、menubar_register等独立定义,避免配置冗余和潜在的循环风险。
4. 排查递归根源(可选)
若保留原定义结构,需查看栈溢出的详细堆栈信息,定位具体哪个定义引发递归;同时检查cascade="true"属性是否导致属性反向传递,触发循环引用。
内容的提问来源于stack exchange,提问作者Arbuz Melon
相关产品推荐
相关产品推荐

