记一次500 Unable to instantiate Action错误(2011-2-27)
今天周末,但是项目快要进入UAT,所以项目组的几位哥们都自发的去公司加班,因为bug有一堆需要解决,才到公司打开skype,身在旧金山的D哥就发过来一封Email说才部署到客户那边的一个新功能有问题,这个功能我也参与其中,所以便义不容辞的要拿下来了.
首先简单的介绍一下,项目采用的是Struts2.0.14 + Hibernate 3.2.6 ga + Spring 2.5.6作为各层的实现,表现层大量使用了Extjs.
从Email上的截图可以看到错误时一个弹出框,根据上面的提示信息搜了一下code,发现是发送一个Ajax请求时对应的action发生错误,但是苦于今天的VPN网络速度实在是太慢,完全登陆不上客户那边进行详细的查看,正好QA说本地也能重现该错误,那OK,在本地起个server来看看.
但是清晰的记得在上code之前,在本地是做过测试的,不会有这个问题,先暂且看看再说。
server一起来,拿了一个符合条件的数据进行测试,哎,还真有这错,通过Firebug进行详细的查看,有如下错误信息:
Unable to instantiate Action, reportCbxReportAction, defined for 'reportGenCbxReport' in namespace '/'Error creating bean with name 'reportCbxReportAction' defined in file [C:\cvs\project\target\classes\spring\actionContext.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are: PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'defaultReportName' threw exception; nested exception is java.lang.ArrayIndexOutOfBoundsException: 1
是个数组越界的错误,这下我来劲了,应该会比较好定位(提示很明显:这个bean: reportCbxReportAction的这个属性:defaultReportName),呵呵.

去到相应的文件actionContext.xml的相关代码处:
<bean id="reportCbxReportAction" scope="prototype" value="${cbxReport.defaultReportName}"/> </bean>
defaultReportName这个属性还引用了properties文件中的值,于是再继续追踪properties文件的相关内容
cbxReport.defaultReportName=MARKET_PO_SUMMARY_V001-[Curr_Date_YYMMDD],PO_PREPACK_V001-[Curr_Date_YYMMDD],TICKET_PO_V001-[Curr_Date_YYMMDD]
看来问题就是出在将这个属性值赋值给defaultReportName这个属性时出错了,于是进一步定位到reportCbxReportAction这个bean对应的action类中关于defaultReportName的setter方法来一探究竟

以下是具体的setter方法:
private final Map<String,String> defaultFileNames = new HashMap<String, String>(); public void setDefaultReportName(final String defaultReportName) { this.defaultReportName = defaultReportName; if(defaultFileNames.size() == 0){ final String[] defaultFileNameArray = defaultReportName.split(","); for(final String s : defaultFileNameArray){ final String[] temp = s.split(":"); this.defaultFileNames.put(temp[0], temp[1]); } } }
看了看这段代码,看来并不是在设属性defaultReportName的时候出错,而是下面这部分,对拿出来的defaultReportName值进行循环的分割出了错,其实也就是两次分割,第一次是通过','来split,得到了第一个String数组,而第二次则是对这个数组中的每一个String再按照':'来split,得到的String数组的第一,二个元素分别作为key,value被放入defaultFileNames这个Map里面.这个时候再回过头去检查一下properties文件中的cbxReport.defaultReportName这个属性,已经可以看到端倪了,呵呵,我们并没有按照这种首先通过逗号,再通过分号分割的形式来组织数据,在第一次按照逗号分割的情况下,没有问题,都能够顺利分割,但是在第二次按照冒号分割的情况下,每个子String[]由于都不含有':',所以只能整个的被作为一个temp[0],而没有temp[1],这样,在put(temp[0], temp[1])的时候,由于引用了越界下标的数组,就会报异常了.
基于这样的分析,我改动了一下properties文件中对应的属性值:按照代码的解析规则,加上了按照:分割的部分,如下所示:
cbxReport.defaultReportName=PO-SUMMARY:MARKET_PO_SUMMARY_V001-[Curr_Date_YYMMDD],PO-PACK:PO_PREPACK_V001-[Curr_Date_YYMMDD],PO-TICKET:TICKET_PO_V001-[Curr_Date_YYMMDD]
这样,重新启动服务器,再次进入相关的功能模块进行操作,就没有异常了.但是撇开相应的功能不说,我觉得这种编码方式还可以再提高一点,至少应该把数组越界这种情况考虑在内,这样如果不熟悉规则的program进行配置,就不会再出现类似的异常错误了.至于为什么在我进行单元测试的时候没有发现问题,现在也明白了,肯定是另外一个同事提交了这部分的code,而我在测试的时候并没有应用到这部分最新的code,呵呵,这也是一个应该注意的问题,完成同样功能的几个人,应该相互协调,相互交流,以便有最新的代码的更改时能够及时获悉及时测试,避免不必要的错误发生.

哈哈,最后其实我还有一个不明白的问题,我在查看log信息的时候,发现在spring容器启动的时候,就已经调用了这段setter代码对相关的defaultReportName属性进行了设置,但是为什么在那个时候没有出现exception信息导致容器不能启动呢?而是在用户请求这个action的bean的时候才出现数组越界的exception?这个问题还没有想明白,第一次在这里发帖,也望各位能够指点一二,谢谢

先睡觉,明天继续备战UAT,哈哈.