运行《Struts2 权威指南》 s-if.jsp 的异常引出对 EL 和 OGNL 的思考

我用 MyEclipse 建了一个 Web 项目,配置了能支持 Struts2。在验证标签应用时,把 《Struts2权威指南--基于WebWork核心的MVC开发》一书的例子 10.3 controlTag 中的 s-if.jsp 拷入当前应用。该 jsp 文件的内容是:想像一下我访问它时得到了什么样的结果?

然后把该应用 TestStruts2 部署到 Tomcat 5.0.28 下,启动 Tomcat,地址栏输入 http://localhost:8080/TestStruts2/s-if.jsp,确定,执行结果让我傻眼了,任凭我百般刷新,也都是赫然显示着:

少年

按理应显示 "青年人" 才对啊,代码上看不出什么问题啊!难道是原本的 controlTag 就有问题?于是把 controlTag 复制到 Tomcat 的 webapps 目录,再访问 http://localhost:8080/controlTag/s-if.jsp 时,页面显示为:

青年人

这更让我迷惑。这两个应用到底有何差异?先是采用一种十分愚笨的方法,先把 controlTag 精简出一个最小可用的目录,然后看 TestStruts2 中那块有疑问的地方就改成与 controlTag 一样的配置,strtus.xml 改一样了,lib 下的 jar 也替换掉了,web.xml 中的 <filter.../> 和 <filter-mapping.../> 配置也改一样了,结果发现仍旧为 "少年"。但要是把 controlTag 的整个 WEB-INF 覆盖过去,又是可以的,继而又做了不少尝试,Tomcat 不停的重启,还是未找到问题所在。看来真的似乎是没辙了。

好吧,什么叫做君子性非异也,善假于物也!不得已安装上 BeyondCompare,两个应用一对比就发现原来还是 web.xml 内容不同。 有人要问了,前面不是说已经把 web.xml 的内容改成一样的吗?是的,但单就犯了一个错误,web.xml 文件的声明处没注意:

controlTag 的 web.xml 的声明处是:

TestStruts2 的 web.xml 的声明处是:

看到这一差别才轰然醒悟过来,原来是在 Servlet 2.4 与 Servlet 2.3 的差别。Tomcat 5.0.28 是能支持 Servlet2.4/JSP 2.0 的,JSP 2.0 是可以用 EL 表达式,而之前版本是不行的,正式这一声明指示着容器应如何解析和编译 JSP 的,而产生未曾意料到的结果。

原本用 MyEclipse 新建的 Web 应用是采用的 Servlet 2.4 的声明,只是我有一次测试 Struts2 在 Servlet 2.3 下的表现时忘了改回来。喜或悲,着实浪费了不少时间,却也让我真实见证到了一个问题,也为在 Servlet2.3/JSP 1.2 下使用 Struts2 的企图撤了一道卡。见我的另一篇日志,一直在追寻着这个问题的答案:在仅实现到 Servlet 2.3/JSP 1.2 规范、JDK为1.4 的容器中用 Struts 2 会有什么问题?

如果硬是要在 Servlet 2.3 下使用 Struts2 运行这个 JSP 页面,那么就要把 EL 改成 OGNL 表达式,把原来 s-if.jsp 中的三个测试表达式行分别改为:

<s:if test="${age > 60}">                             改成  <s:if test="#age > 60">
<s:elseif test="${age > 35}">                       改成  <s:elseif test="#age > 35">
<s:elseif test="${age > 15}" id="wawa">      改成 <s:elseif test="#age > 15" id="wawa">

再次浏览 http://localhost:8080/TestStruts2/s-if.jsp 就保证你看到的是 "青年人" 了,因为 OGNL 是不依赖于 Servlet 容器的,就像 Struts 1.x 的 struts-el 和 JSTL 的 EL。

既然误入了藕花深处,何不来惊起一滩鸥鹭呢?我们深入对比一下,web.xml 中不同的 Servlet 版本声明时编译出的 jsp 源文件的差异。

1. 在 Servlet 2.3/JSP 1.2 (web.xml 声明使用 web-app_2_3.dtd) 下编译出的 JSP 对应源代码
    1)  EL 表达式 ${age > 60} 对应代码为:执行页面显示 少年 ×
          _jspx_th_s_if_0.setTest("${age > 60}");
    2) OGN 表达式 #age > 60 对应代码为:执行页面显示 青年人 √
          _jspx_th_s_if_0.setTest("#age > 60");

2. 在 Servlet 2.4/JSP 2.0 (web.xml 声明使用 web-app_2_4.xsd) 下编译出的 JSP 对应源代码
    1) EL 表达式 ${age > 60} 对应代码为:执行页面显示 青年人 √
          _jspx_th_s_elseif_0.setTest((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${age > 35}", java.lang.String.class, (PageContext)_jspx_page_context, null, false));
    2) OGN 表达式 #age > 60 对应代码为:执行页面显示 青年人 √
          _jspx_th_s_if_0.setTest("#age > 60");

由此对比,我们可以发现,使用 OGNL 表达式,在两种版本 web.xml 声明中编译出的 JSP 对应源代码都是一样的,其中的 OGNL 表达式后续由 ognl-2.x.x.jar 来处理。在 Servlet 2.3/JSP 1.2 下因为不支持 EL,所以这个 EL 表达式被忽略,只当是个普通字符串了,而在 Servlet 2.4/JSP 2.0 下的 EL 表达式会交给 PageContextImpl.proprietaryEvaluate 去处理。

在 JSP2.0 中有一个页面指令 <%@ page isELIgnored="true"%> ,如果加上这一指令,即使在 Servlet 2.4/JSP 2.0 中 EL 也被忽略,编译出的 JSP 对应源代码同 Servlet 2.3/JSP 1.2 也一样了。

所以要在仅支持 Servlet 2.3/JSP 1.2、JDK为1.4 的容器中应用 Struts2 的第二条准则是:EL 换成 OGNL 表达式。前面有遇到过第一条准则是解决在用 1.4 没有泛型的情况下 Action 中集合元素的类型识别问题 Unmi 的 Struts2 学习笔记(七),是要在 ActionName-conversion.properties 中用 Element_xxx 和 Key_xxx 来说明。

对于取pageContext、parameters、request、session、application 等处的属性值(假如有 name 属性)时我们用的 EL 表达式分别是:

${pageScope.name}、${param.name}、{$requestScope.name}、{$sessionScope.name}、{$applicationScope.name}

那么对应的 OGNL 的解决方案分别是:

<s:peroperty value="#attr.name"/> 
<s:property value="#parameters.name"/>
<s:property value="#request.name"/>
<s:property value="#session.name"/>
<s:property value="#application.name"/>
<s:textfield name="name"  value="%{#parameters.name}"/>

说明,attr 如果可以访问到,则访问 pageContext,否则将 依次搜索 pageContext、request、session、application 相应值,所以可用来访问 pageContext 中的值,可替代 EL 的 ${pageScope.name}。

在前面提到过,Servlet 2.3/JSP 1.2 的容器中可以借助于 struts-el (针对 struts 1.x) 和 JSTL 来支持 EL 表达式,那么对于 Struts 2 是否能有所借鉴呢?其实不太可行。

struts-el 是对 struts 1.x 的全套标签重新实现,并有少部分 EL 不支持,Struts2 能这么做吗?当然是可以做到,但 Struts2 本身因为引入了 OGNL 可替代方案,所以它觉得没这个必要性,如果你愿意的话可以仿照 struts-el 自己来实现,不过会不伦不类的,大于号 ">" 要写成 "gt" 种种。

JSTL 确提供了对 EL 很好的支持,但你必须用 JSTL 的标签 <c:out value="${requestScope.name}"/>,没办法应用到 Struts2 的标签当中,所以也是徒劳无益。所以还是直接用 OGNL 表达式吧。

参考:1. 在仅实现到 Servlet 2.3/JSP 1.2 规范、JDK为1.4 的容器中用 Struts 2 会有什么问题?
        2. Unmi 的 Struts2 学习笔记(七)
        3. 《struts2权威指南》的一个例子的问题

 
 

类别: Struts. 标签: , . 阅读(160). 订阅评论. TrackBack.

Leave a Reply

Be the First to Comment!

avatar