如果你因struts2暴露的安全漏洞 而希望升级到struts 2.2.1,那么你需要仔细测试你的程序是否能在2.2.1的环境下顺利运行。因为struts 2.2.1版本升级了OGNL、freemarker,以及对我们代码影响很大的xwork。
其中xwork的一个bug编号为"WW-2869 "的修订,对我们熟悉的chain result type做了重要的修改。这个bug的出发点是在一个Action a执行后,跳转到执行result。假设这个result是一张jsp页面,而这张jsp页面中调用了<s:action name="bar"/>标签。假设在执行a Action时,执行了类似代码"addActionError("an error from FooAction");",那么会停止对Action bar的正确调用,而执行Action bar的input。
这个问题的归结原因是在ChainingInterceptor中,在调用Action bar前,会将上一个Action(面假设中的Action a)的一些属性值拷贝到bar中。以下是在strtus 2.2.1前的xwork中的ChainingInterceptor代码
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ValueStack stack = invocation.getStack();
CompoundRoot root = stack.getRoot();
if (root.size() > 1) {
List<CompoundRoot> list = new ArrayList<CompoundRoot>(root);
list.remove(0);
Collections.reverse(list);
Map<String, Object> ctxMap = invocation.getInvocationContext().getContextMap();
Iterator<CompoundRoot> iterator = list.iterator();
int index = 1; // starts with 1, 0 has been removed
while (iterator.hasNext()) {
index = index + 1;
Object o = iterator.next();
if (o != null) {
if (!(o instanceof Unchainable)) {
//拷贝属性值到将要执行的Action中
reflectionProvider.copy(o, invocation.getAction(), ctxMap, excludes, includes);
}
}
else {
LOG.warn("compound root element at index "+index+" is null");
}
}
}
@Override public String intercept(ActionInvocation invocation) throws Exception { ValueStack stack = invocation.getStack(); CompoundRoot root = stack.getRoot(); if (root.size() > 1) { List<CompoundRoot> list = new ArrayList<CompoundRoot>(root); list.remove(0); Collections.reverse(list); Map<String, Object> ctxMap = invocation.getInvocationContext().getContextMap(); Iterator<CompoundRoot> iterator = list.iterator(); int index = 1; // starts with 1, 0 has been removed while (iterator.hasNext()) { index = index + 1; Object o = iterator.next(); if (o != null) { if (!(o instanceof Unchainable)) { //拷贝属性值到将要执行的Action中 reflectionProvider.copy(o, invocation.getAction(), ctxMap, excludes, includes); } } else { LOG.warn("compound root element at index "+index+" is null"); } } }
对于这种方式的好处,我们或许已经默默的在使用了。还是前面提到的Action a和Action bar,若Action a和Action bar都有一个共同的属性叫common,并且都有get、set方法。那么在Action a中的一些处理后,属性common有了值,经过chain resultType时,Action bar的common会自动被赋值。如果你在升级到struts2.2.1后,发现一些请求返回了NullPointerException,那么很有可 能是struts2.2.1对ChainingInterceptor的修改导致了这个问题。
以下是struts 2.2.1中的ChainingInterceptor源代码
public String intercept(ActionInvocation invocation) throws Exception {
ValueStack stack = invocation.getStack();
CompoundRoot root = stack.getRoot();
if (root.size() > 1 && isChainResult(invocation)) {
List<CompoundRoot> list = new ArrayList<CompoundRoot>(root);
list.remove(0);
Collections.reverse(list);
Map<String, Object> ctxMap = invocation.getInvocationContext().getContextMap();
Iterator<CompoundRoot> iterator = list.iterator();
int index = 1; // starts with 1, 0 has been removed
while (iterator.hasNext()) {
index = index + 1;
Object o = iterator.next();
if (o != null) {
if (!(o instanceof Unchainable)) {
reflectionProvider.copy(o, invocation.getAction(), ctxMap, excludes, includes);
}
} else {
LOG.warn("compound root element at index " + index + " is null");
}
}
}
return invocation.invoke();
}
public String intercept(ActionInvocation invocation) throws Exception { ValueStack stack = invocation.getStack(); CompoundRoot root = stack.getRoot(); if (root.size() > 1 && isChainResult(invocation)) { List<CompoundRoot> list = new ArrayList<CompoundRoot>(root); list.remove(0); Collections.reverse(list); Map<String, Object> ctxMap = invocation.getInvocationContext().getContextMap(); Iterator<CompoundRoot> iterator = list.iterator(); int index = 1; // starts with 1, 0 has been removed while (iterator.hasNext()) { index = index + 1; Object o = iterator.next(); if (o != null) { if (!(o instanceof Unchainable)) { reflectionProvider.copy(o, invocation.getAction(), ctxMap, excludes, includes); } } else { LOG.warn("compound root element at index " + index + " is null"); } } } return invocation.invoke(); }
和前面的相比,if (root.size() > 1) 中对了一个判断isChainResult(invocation)。这个判断让我有些纳闷,因为根据我的一些跟踪,在ActionInvocation 调用invoke()方法前,ActionInvocation中的result属性是null,所以这个if总是false。难道有什么其他设置能恢复 原来类似功能吗?希望有高人解答。正是因为这个问题,若你还希望使用原来chain的拷贝功能,你需要删除 isChainResult(invocation)这个判断。
以下是一个很有趣的实验,首先我们看核心的struts.xml配置信息:
<package name="default" namespace="/" extends="struts-default">
<!-- <default-action-ref name="index" /> -->
<action name="index">
<result>index.jsp</result>
</action>
<action name="chainSrc" class="ChainSrcAction">
<result type="chain">chainDest</result>
</action>
<action name="chainDest" class="ChainDestAction">
<result>chainDest.jsp</result>
</action>
</package>
<package name="default" namespace="/" extends="struts-default"> <!-- <default-action-ref name="index" /> --> <action name="index"> <result>index.jsp</result> </action> <action name="chainSrc" class="ChainSrcAction"> <result type="chain">chainDest</result> </action> <action name="chainDest" class="ChainDestAction"> <result>chainDest.jsp</result> </action> </package>
ChainSrcAction的源代码:
import com.opensymphony.xwork2.ActionSupport;
public class ChainSrcAction extends ActionSupport {
private String p1;
private String p2 = "chain";
@Override
public String execute() throws Exception {
System.out.println(ChainSrcAction.class+"p1="+p1);
System.out.println(ChainSrcAction.class+"p2="+p2);
return SUCCESS;
}
public String getP1() {
return p1;
}
public void setP1(String p1) {
this.p1 = p1;
}
public String getP2() {
return p2;
}
}
import com.opensymphony.xwork2.ActionSupport; public class ChainSrcAction extends ActionSupport { private String p1; private String p2 = "chain"; @Override public String execute() throws Exception { System.out.println(ChainSrcAction.class+"p1="+p1); System.out.println(ChainSrcAction.class+"p2="+p2); return SUCCESS; } public String getP1() { return p1; } public void setP1(String p1) { this.p1 = p1; } public String getP2() { return p2; } }
ChainDestAction的源代码:
import com.opensymphony.xwork2.ActionSupport;
public class ChainDestAction extends ActionSupport {
private String p1;
private String p2 ;
@Override
public String execute() throws Exception {
System.out.println(ChainDestAction.class+"p1="+p1);
System.out.println(ChainDestAction.class+"p2="+p2);
return SUCCESS;
}
public String getP1() {
return p1;
}
public void setP1(String p1) {
this.p1 = p1;
}
public String getP2() {
return p2;
}
public void setP2(String p2) {
this.p2 = p2;
}
}
import com.opensymphony.xwork2.ActionSupport; public class ChainDestAction extends ActionSupport { private String p1; private String p2 ; @Override public String execute() throws Exception { System.out.println(ChainDestAction.class+"p1="+p1); System.out.println(ChainDestAction.class+"p2="+p2); return SUCCESS; } public String getP1() { return p1; } public void setP1(String p1) { this.p1 = p1; } public String getP2() { return p2; } public void setP2(String p2) { this.p2 = p2; } }
在struts 2.2.1的环境下,在地址栏上输入"http://127.0.0.1/s221/chainSrc.action?p1=struts2.2.1"你将会得到以下答案:
class ChainSrcActionp1=struts2.2.1
class ChainSrcActionp2=chain
class ChainDestActionp1=struts2.2.1
class ChainDestActionp2=null
在struts 2.1.8的环境下,相同的访问你会得到答案:
class ChainSrcActionp1=struts2.2.1
class ChainSrcActionp2=chain
class ChainDestActionp1=struts2.2.1
class ChainDestActionp2=chain
如果你将ChainingInterceptor中的if (root.size() > 1 && isChainResult(invocation))改为if (root.size() > 1 ),你也可以得到struts 2.1.8的结果。对于熟悉p1,它始终都得到了赋值是因为还有ParametersInterceptor对它赋值。