Struts2 使用 XSLTResult 输出页面内容详解

Struts2 内置提供了 xslt 结果类型,实现类为 org.apache.struts2.views.xslt.XSLTResult,它让你方便的把获得的 XML 数据内容,或者是用 OGNL 能访问到的某个属性(像 ContenxtMap、Request 等中的属性),通过一个 xslt 文件转换成你想要的格式。前面这句听来不怎么明白,后面慢慢道来。

在 Struts2 的 struts-default.xml 中定义了 chain、dispatcher、freemarker、httpheader、redirect、redirectAction、stream、velocity、xslt 和 plainText 10 种类型的 Result;而在 Struts2 初期版本中的 jasper、chart、jsf 和 tiles 结果类型已移到相应的插件去实现了。

freemarker、velocity 和 xslt 可以很自由的使用各自的模板语言,velocity 渐渐淡出了我们的视野,那还剩下 freemarker 和 xslt。freemarker 要求实合并的变量是实体类型,满足了多数时候的需求,不过现在要说的 xslt 结果类型,向 xslt 文件送去的数据可以是实体类型,也可以是原生的 org.w3c.dom.Document 类型,当然到了 xslt 文件这一层处理的都是 org.w3c.dom.Document 类型。注意不能是别的 Document,如 org.dom4j.Document。这对于像调用 WebService 返回的是个 XML 内容然后用 xslt 文件格式化输出是最直截了当的,这就免去了我们想使用 Transformer.transform() 进行 xslt 转换 xml 的过程。

我们先作个准备,来看下 org.apache.struts2.views.xslt.XSLTResult 有哪些可配置的属性:

adapterFactory      可定制自己的,告诉 XSLTResult 怎么把暴露的变量转换为 org.w3c.dom.Document 类型
exposedValue         送出给 xslt 模板要处理的数据,可用 OGNL 引用当前上下文中的变量,也可以是个集合
stylesheetLocation xslt 文件的位置,如 /WEB-INF/xslt/foo.xslt,基于当前应用的路径,struts2.1.1 前用 location
noCache                    是否缓存 xslt 模板,可通过 struts 的常量  struts.xslt.nocache 进行全局设置
parse                          是否启用 OGNL  表达式来解析 stylesheetLocation 获得实际的 xstl 文件位置,默认为 true

另有两个在 struts2.1.1 之前的配置属性是 exludingPattern 和 matchingPattern。

有了前面的了解,现在可以看个具体的实例了:

1. Action 中执行方法的代码:

public String execute(){

    Ticker user = new Ticker();
    user.setId(100);
    user.setName("Unmi");

    Map<String, Object> contextMap = ActionContext.getContext().getContextMap();
    contextMap.put("user", user);

    return SUCCESS;
 }

cc.unmi.model.User 类中有两属性 id 和 name

2. struts.xml 中对该 Action 的配置:

<action name="user" class="cc.unmi.action.UserAction">
    <result type="xslt">
        <param name="stylesheetLocation ">/xslt/user.xslt</param>
        <param name="exposedValue">user</param>
        <param name="noCache">true</param>
    </result>
</action>

上面的 exposedValue 是个 OGNL 表达式,这里 user 是取自 ContextMap 中的,可以是 UserAction 本身的属性,或者 Request、ServletContext 中的属性,或者是 ModelDriven  Action 的模型对象,总之是用 OGNL 能访问到的任何东西都可以在这里引用。如果每一个 exposedValue 都要申明为当前 Action 的属性就够呛的,个人觉得放 ContextMap 是个很好的选择。

比如 exposedValue 值可以用 user.name, 或用大括号包起来的多个值 {user1, user2},user1 和 user2 是当前 Action 的属性或是它的 model 中的属性是这么写。依据 OGNL 的规则,如果 user1 和 user2 不是 OGNL 上下文中根据对象的属性,则要写成 {#user1, #user2},比如它们作为 ActionContext.getContext().getContextMap() 中的值。

3. /xslt/user.xslt 文件,最简单较通用的内容

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml"/>
    <xsl:template match="/result">
        <xsl:copy-of select="."/>
    </xsl:template>
</xsl:stylesheet>

最后,HttpServletResponse 响应的类型是由上面的 <xsl:output method="xml"/> 决定的。

4. 访问该 Action 输出的内容:

<?xml version="1.0" encoding="UTF-8"?>
<result>
    <id>100</id>
    <name>Unmi</name>
</result>

这是一个较简单的 bean 的输出,更深层次的 javabean,甚至带有 Map、List 属性的 javabean 会输出成什么样子的 xml,可以自己慢慢去试。

前面的 root 节点为什么是 <result> 呢,这要进到 org.apache.struts2.views.xslt.AdapterFactory 来寻下底。在 org.apache.struts2.views.xslt.XSLTResult 的 execute() 方法中是用:

Object result = invocation.getAction();
if (exposedValue != null) {
    ValueStack stack = invocation.getStack();
    result = stack.findValue(exposedValue);
}

Source xmlSource = getDOMSourceForStack(result);

找到 exposedValue 值的,从 ValueStack 找,这是个 OGNL ValueStack,所以 exposedValue 就是个 OGNL 表达式。然后最后一行调用跑到当前类的:

protected Source getDOMSourceForStack(Object value)
    throws IllegalAccessException, InstantiationException {
    return new DOMSource(getAdapterFactory().adaptDocument("result", value) );
 }

这里可以看到设置了一个 "result" 字符串作为根节点的标签名。再深入一步,来到 org.apache.struts2.views.xslt.AdapterFactory 类:

public Document adaptDocument(String propertyName, Object propertyValue)
    throws IllegalAccessException, InstantiationException {
    //if ( propertyValue instanceof Document )
    //  return (Document)propertyValue;

     return new SimpleAdapterDocument(this, null, propertyName, propertyValue);
 }

方法返回的是一个 org.w3c.dom.Document 类型,再推送给 xslt 文件。发现到上面注释掉的代码,作者的本意还在,如果本身就是个 org.w3c.dom.Document 类型,那么直接给 xslt 就行,只是现在这一逻辑放到 SimpleAdapterDocument 类里去了而已。

也就是说,如果你给 exposedValue 就是一个 org.w3c.dom.Document 类型,那么会保持好原来的 XML 格式,无需再用 "result" 作为根节点名,同时在你的 xslt 文件中也不是用 <xsl:template match="/result"> 来匹配根节点,而可能是 <xsl:template match="/users">。

在调用 Service 返回 XML 数据,然后重新用 xslt 组织输出的应用场景中就好办了,不用解析原始的 XML 生成 JavaBean,再用标签显示,而是直接把原始的 XML 扔给 xslt 去处理。当然能力的发挥就要体现在 xslt 模板文件中了,xslt 中有不少函数,再不行 xslt 中可以使用 javascript 或 java 代码中的函数。

最后再看两种情况,exposedValue 是个单纯的字符串和是个用 {user1, user2} 表示的集合。

exposedValue 是个字符串,也就是在 Action 中返回前写成 contextMap.put("user", "Unmi"); xslt 文件还是那个 user.xslt,这种情况其实没必要说的,完全可以想见到它的输出就是: <result>Unmi</result>。但还是要总结一下,当 exposedValue 类型是基本类型,像 int,long 等,以及它们的包装类型,再加上字符串类型,形成的 XML 的结构将会是 <result>toString() 返回值</result>,如果是 null 将会报错,是不允许的。

之所以提到 exposedValue 是个简单字符串是为接下来作铺垫的,字符串是 "Unmi",输出为 <result>Unmi</result> 我是没意见的。但要是我的字符串是 "<user><name>Unmi</name></user>",期待它直接作为 XML 数据时,以 Struts2 现有的方式会输出为

<?xml version="1.0" encoding="UTF-8"?>
<result>
    &lt;user&gt;&lt;name&gt;Unmi&lt;/name&gt;&lt;/user&gt;
</result>

这就不是我想要的,它不能作为一个简单的字符串,我们希望它直接被转换为相应的 org.w3c.dom.Document,输出结果应该为 <user><name>Unmi</name></user>,这就涉及到定制自己的 AdapterFactory 在字符串符合 XML 格式时直接转换为 Document,而不是简单框上 <result>,当然事先转换为 Document 再作为 exposedValue 也行的,只是通用性不强。

exposedValue 为 {user1, user2} 时 -- 如果 user1 和 user2 是放在 ContextMap 中的话,用 {#user1, #user2} 的形式。在前面的 Action 的  execute() 方法里,往 ContextMap 放值的代码改为:

contextMap.put("user1", user);
contextMap.put("user2", user);

然后,在 struts.xml 配置文件中,exposedValue 属性值改为 {#user1, #user2},再看最后执行的页面输出:

<?xml version="1.0" encoding="UTF-8"?>
<result>
    <item>   
        <id>100</id>
        <name>Unmi</name>
    </item>
    <item>
        <id>100</id>
        <name>Unmi</name>
    </item>
</result>

要说明一下,在 XSLTResult.execute() 代码中,通过

ValueStack stack = invocation.getStack();
result = stack.findValue(“{#user1, #user2}”);

取到的是一个 ArrayList<User> 类型数据,遍历时把每一个元素用 <item> 包上。上面所有的如果不是直接提供给 xslt Document 类型,在 xml 中将会失去 javabean 本身的类型表述,而代之以 <result><item>。

再如果是个 Map 是什么样的情况,不妨看下,Action 里:

map.put("user", user);
contextMap.put("user", map);

exposedValue 还是 user

输出 XML 为:

<result>
    <entry>
        <key>user</key>
        <value>
            <id>100</id>
            <name>Unmi</name>
        </value>
    </entry>
</result>

<key><value> 来了,再和 List 一结合,就有些乱了,乱了,不带有类型信息的 XML 太抽象难懂,并且造成 xslt 文件的难写,因为节点名都是难以捉摸的。所以直接送 org.w3c.dom.Document 经 xslt 会高明许多,个人认为。

参考:1. 使用struts2 返回 xslt result 资料
2. http://struts.apache.org/2.0.14/docs/xsl-result.html

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

Leave a Reply

Be the First to Comment!

avatar
wpDiscuz