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 中执行方法的代码:
1 2 3 4 5 6 7 8 9 10 11 |
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 的配置:
1 2 3 4 5 6 7 |
<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 文件,最简单较通用的内容
1 2 3 4 5 6 7 |
<?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 输出的内容:
1 2 3 4 5 |
<?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() 方法中是用:
1 2 3 4 5 6 7 |
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 表达式。然后最后一行调用跑到当前类的:
1 2 3 4 |
protected Source getDOMSourceForStack(Object value) throws IllegalAccessException, InstantiationException { return new DOMSource(getAdapterFactory().adaptDocument("result", value) ); } |
这里可以看到设置了一个 "result" 字符串作为根节点的标签名。再深入一步,来到 org.apache.struts2.views.xslt.AdapterFactory 类:
1 2 3 4 5 6 7 |
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 现有的方式会输出为
1 2 3 4 |
<?xml version="1.0" encoding="UTF-8"?> <result> <user><name>Unmi</name></user> </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 放值的代码改为:
1 2 |
contextMap.put("user1", user); contextMap.put("user2", user); |
然后,在 struts.xml 配置文件中,exposedValue 属性值改为 {#user1, #user2},再看最后执行的页面输出:
1 2 3 4 5 6 7 8 9 10 11 |
<?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 里:
1 2 |
map.put("user", user); contextMap.put("user", map); |
exposedValue 还是 user
输出 XML 为:
1 2 3 4 5 6 7 8 9 |
<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
本文链接 https://yanbin.blog/struts2-xsltresult-details/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。