今天重构以前代码过程中发现一个struts2设置参数的问题,前后足足追了半天,顺便看了OGNL的源码,在这里记录下原因和解决方案:
在struts2的action中接收参数时,通常我是不喜欢写getter,只写setter。这样可以更加清楚的表示这是一个输入参数,比如
public class XXAction{ private int size; public viod setSize(int size){ this.size=size; } }
当只接收普通类型时没有问题,可对于如下复杂类型
public class PageBean{ int pageSize; int pageNo; String orderBy; // getter &setter // ... }
如果不写getter
public class XXAction{ private PageBean page; public void setPage(PageBean page){ this.page=page; } }
前端传过来的参数 page.pageSize=10&page.pageNo=1&page.orderBy=name
在execute()方法中会发现page对象只有 pageSize被赋值了(10),pageNo和orderBy都为null。而且无论怎么调整传入参数的顺序结果都是一样的。
这个问题让我很费解,网上找了半天也没找到答案,老老实实的去跟OGNL的源码,最后发现原因是这样的:
1) OGNL 首先收集所有需要赋值的属性( page.pageSize, page.pageNo, page.orderBy)
2) 如果是复杂对象(如这里的PageBean),OGNL并不是首先构造好这个PageBean,赋上所有的值(pageSize,pageNo,orderBy),然后再把整个对象赋给action.page;而是通过一个循环依次往action.page这个对象上追加值。循环中每次执行时并不知道action.page对象是否已经执行过初始化,所以每次都要用以下方法测试action.page是否存在,若不存在则会初始化它 (action.page=new PageBean()), 然后完成本次赋值
if (OgnlRuntime.hasSetProperty(ognlContext, o, name)) { OgnlRuntime.setProperty(ognlContext, o, name, value); return; }
由于我没有在Action中对page属性写setter,所以.hasSetPropertyf方法每次都会返回false,然后OGNL就会傻傻的重新初始化并赋值, 对于以上三个属性,它一共会初始化page对象三遍,而只保留下最后赋值的属性pageSize。
至于为什么每次都是pageSize排在最后呢? 这是因为OGNL在步骤1)中收集对象时用的是 TreeMap, 而前段穿过来的这三个key(page.pageSize, page.pageNo, page.orderBy) 按照String.compareTo()的实现总是page.pageSize排在最后一个。
知道了原因,解决方法当然很简单,只要加上 page属性的setter就行了。不过这里还是要对Struts的设计吐个槽,如果是我来设计,输入参数肯定不会要求实现者提供getter,而是用一个Map保存对象的中间状态,最后统一调用setter赋给action。 现在这种拿action当临时对象用的方法看起来简化了一些实现,但作为框架来说,强制要求使用者提供getter完全是过分的要求。