背景:
项目需要在web.xml中配置的过滤器不仅能够拦截浏览器向服务器直接发出的request请求,同时也应该能够拦截转发请求,即通过调用request.getRequestDispatcher(forwardURI).forward(request, response)由服务器内部通过转发的方式直接产生的请求,但是这在servlet2.3规范中是做不到的。
解决方案:
Servlet2.4规范通过给<filter-mapping>增加子元素<dispatcher>解决了这个问题,<dispatcher>元素有四种可能的取值:REQUEST、FORWARD、INCLUDE和ERROR,分别代表不同的请求类型,其中forward即代表转发。
所以我首先将web.xml从2.3版本升级到2.4,升级过程如下:
升级前的2.3版本:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> …… </web-app>
升级后的2.4版本(2.4版本的部署描述符是基于XML Schema定义的):
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> …… </web-app>
接下来为要过滤转发请求的<filter-mapping>增加<dispatcher>元素,关键代码如下:
<filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> </filter-mapping>
然后启动应用程序服务器(Tomcat5.5.30),接下来发生了不可思议的“灵异”事件,本来通过使用displaytag标签能够正常显示表格数据的页面突然之间显示“没有数据可供显示”,附页面使用displaytag(标签库是用的EL版本:<%@ taglib uri="http://displaytag.sf.net/el " prefix="display" %>)的主要代码:
<display:table id="listID" name="${acctList}" partialList="true" size="${actionPagination.recordTotal}" pagesize="${actionPagination.pagesize}" export="true" requestURI="familyAccountAction.do" class="list"> <display:column title="选择" media="html" property="familyAccountId" decorator="radio"></display:column> <display:column title="发生日期" property="exeDate" sortable="true" headerClass="sortable" decorator="nyrDateColumnDecorator"/> </display:table>
接下来就是我解决这个问题的漫漫征途,因为我在这之前并未改动过项目的其它任何文件,所以问题的根源显然在升级web.xml上,但是究竟是为什么呢?
我首先对出现问题的JSP页面在升级前后tomcat将其编译的servlet源码进行了对比,结果确实发现了有价值的线索。
使用Servlet2.3版本时tomcat对其编译产生的servlet文件的源码片段:
org.displaytag.tags.el.ELTableTag _jspx_th_display_005ftable_005f0 = (org.displaytag.tags.el.ELTableTag) _005fjspx_005ftagPool_005fdisplay_005ftable_0026_005fsize_005frequestURI_005fpartialList_005fpagesize_005fname_005fid_005fexport_005fclass.get(org.displaytag.tags.el.ELTableTag.class); _jspx_th_display_005ftable_005f0.setPageContext(_jspx_page_context); _jspx_th_display_005ftable_005f0.setParent(null); _jspx_th_display_005ftable_005f0.setUid("listID"); _jspx_th_display_005ftable_005f0.setName("${acctList}"); _jspx_th_display_005ftable_005f0.setPartialList(true); _jspx_th_display_005ftable_005f0.setSize("${actionPagination.recordTotal}"); _jspx_th_display_005ftable_005f0.setPagesize("${actionPagination.pagesize}");
使用Servlet2.4版本时tomcat对其编译产生的servlet文件的源码片段:
org.displaytag.tags.el.ELTableTag _jspx_th_display_005ftable_005f0 = (org.displaytag.tags.el.ELTableTag) _005fjspx_005ftagPool_005fdisplay_005ftable_0026_005fsize_005frequestURI_005fpartialList_005fpagesize_005fname_005fid_005fexport_005fclass.get(org.displaytag.tags.el.ELTableTag.class); _jspx_th_display_005ftable_005f0.setPageContext(_jspx_page_context); _jspx_th_display_005ftable_005f0.setParent(null); _jspx_th_display_005ftable_005f0.setUid("listID"); _jspx_th_display_005ftable_005f0.setName((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${acctList}", java.lang.String.class, (PageContext)_jspx_page_context, null, false)); _jspx_th_display_005ftable_005f0.setPartialList(true); _jspx_th_display_005ftable_005f0.setSize((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${actionPagination.recordTotal}", java.lang.String.class, (PageContext)_jspx_page_context, null, false)); _jspx_th_display_005ftable_005f0.setPagesize((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${actionPagination.pagesize}", java.lang.String.class, (PageContext)_jspx_page_context, null, false));
由此可以看出,在使用Servlet2.4规范之后,tomcat在jsp的编译过程中自动对EL表达式进行解释(注:servlet2.3规范对应jsp1.2而servlet2.4规范对应jsp2.0),因为从jsp2.0规范开始引入了 EL表达式 。如下表述译自jsp2.0规范第22页:
Jsp2.0规范的第22页:
JSP容器使用web.xml的版本号去检测应该使用jsp1.2规范还是jsp2.0规范,故而一些特性会因为web.xml的版本号不同而表现出不同的行为。下面列举了一个清单,当开发者把web.xml从servelet2.3更新到servlet2.4时应该特别注意它们,其中第一条就提到:
EL表达式在JSP1.2中默认是被忽略的,当把web应用程序更新到jsp2.0时,EL表达式会被自动进行解释。转义序列\$可以被用于禁用EL表达式,这样容器就不会对它进行解释。另外,也可以使用page指令的isELIgnored属性或者<el-ignored>配置元素来禁用EL表达式。使用JSTL1.0的用户有两种选择,一是更新标签库导入jstl1.1的URIS,二是使用这些标签库的RT(请求时属性)版本而不是EL版本(例如使用c_rt代替c、使用fmt_rt代替fmt)。
由此我们就不难解释上面遇到的问题并给出解决方案:
方案一:由使用Displaytag的EL版本改为使用非EL版本,因为其el版本是为不支持el表达式的实现jsp1.2规范的web容器所定制的,支持jsp2.0规范的web容器同时也会自动支持el,所以就无需再使用专门为jsp1.2规范所定制的el版本的标签库了。
方案二:升级到jsp2.0之后,仍然使用displaytag的el版本,但是需要在page指令中设置忽略el表达式标志为true,<%@page isELIgnored="true"% >,以便使web容器忽略el表达式。
方案三:升级到jsp2.0之后,仍然使用displaytag的el版本,但是页面中相应的代码需要改成下面的样子:
<display:table id="listID" name="acctList" partialList="true" size="actionPagination.recordTotal" pagesize="${actionPagination.pagesize}" export="true" requestURI="familyAccountAction.do" class="list"> <display:column title="选择" media="html" property="familyAccountId" decorator="radio"></display:column> <display:column title="发生日期" property="exeDate" sortable="true" headerClass="sortable" decorator="nyrDateColumnDecorator"/> </display:table>也就是在name和size属性上不再使用el表达式,displaytag标签会调用表达式管理器去进行解释,这与web容器是无关的。