你了解jsp中的c:forEach吗?

1,870 阅读3分钟

       最近由于业务需求,帮同事把jsp页面上的select控件进行了改动。但是在改完代码之后,在某几个拥有大量select组件的页面下出现了报错信息:_jspService超过了65535字节的限制。

  // 旧的代码
  <form:options items="${opts}" itemLabel="label" itemValue="value" htmlEscape="false"/>
// 新的代码
<c:forEach items="${opts}" var="d">
    <option value="d.value" label="d.label" title="d.label"></option>
</c:forEach>

       虽然这个问题通过其他方式解决了。但是这个错误还是一直困扰着我。正好今天没什么事情,就决定来看看这个的问题到底出在什么地方。 因为问题页面的其他代码都没有改动过,所以我们可以很轻易的定位到我们的问题代码,也就是从optionsc:forEach的改动。

       我们先来了解一下jsp的编译过程:首先,客户端发送请求给web容器;然后,web容器将jsp文件编译成servlet源码,再将servlet源码编译成class文件;web容器执行class文件并将响应返回给客户端。而我们的遇到的错误信息是“unable to compile class for JSP”,而compile失败的原因呢就是_jspService太大了。

根据报错信息,我们得先找到_jspService这个方法。先从最简单的空页面开始调试,编译运行后在浏览器里访问该页面以生成java文件和class文件。下图就是编译后的class文件,我们成功的找到了_jspService方法,同时我们可以看到,就是这个方法输出了我们的页面。


     接下来,我同时创建了两个jsp页面,里面的内容就是最上面的代码示例,一个页面用options标签,另一个页面用了c:forEach。下面贴上两个jsp的编译出来的class文件里面的out.write()部分代码:


      第一个jsp用的是options标签,从图中可以看到options标签没有做任何的处理就编译成了class文件。


      第二个文件是用的c:forEach,我们发现class文件里面已经没有c:forEach标签了,取而代之的是一个if语句,而它的判断条件是一个看起来与forEach有关的方法的返回值。来到这一步,答案其实已经呼之欲出了。我们在这个文件中继续查找,果然在下面出现了这个方法。


       这个方法内部有一个do{}while()循环,所有的option都在这个循环中输出。由于这个方法太长了就不贴整个方法了,这个方法最终会返回false,然后继续执行_jspService方法后续的out.write()

总结             

options元素在jsp编译成class的过程中会被保留,到后面由spring MVC来处理,而每一个c:forEach标签在编译阶段就已经被编译成一个函数来输出目标元素。如果form表单中需要大量的select控件,最好能用options来实现。当然,最好的方法还是不要把太多的元素放到一个jsp中,可以灵活的运用<jsp:include page="" flush="true" />等方法来分解文件。