struts2请求转发之调用setter

最近,一个曾经出现的struts2报错 在项目部署在某服务器后再次出现,于是很有兴致的去挖了一下struts2的源代码(struts2-core-2.1.6、xwork-2.1.2、ognl-2.6.11)。报错如下:


ognl.MethodFailedException: Method "setEndtime" failed for object com.piptrade.action.tradetools.eCalerddarAction@17db177 [java.lang.NoSuchMethodException:
setEndtime([Ljava.lang.String;)]

首先需要的是开头,于是来到web.xml找到过滤器:

org.apache.struts2.dispatcher.FilterDispatcher

直接找到doFilter,关键源码(395行):

dispatcher.serviceAction(request, response, servletContext, mapping);

进入方法serviceAction,关键源码(468行):

proxy.execute();

进入类StrutsActionProxy方法execute,关键源码(52行):

return invocation.invoke();

之后在类DefaultActionInvocation方法invoke中进行一系列拦截器的调用(231-243行):

if (interceptors.hasNext()) {final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();String interceptorMsg = "interceptor: " + interceptor.getName();UtilTimerStack.push(interceptorMsg);try {resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);}finally {UtilTimerStack.pop(interceptorMsg);}}
 

而调用action中setter的拦截器名称是:

com.opensymphony.xwork2.interceptor.ParametersInterceptor

其中方法doIntercept中带有关键源码(186-187行):

ValueStack stack = ac.getValueStack();setParameters(action, stack, parameters);

继续进入方法setParameters,关键源码(248行、273行):

ValueStack newStack = valueStackFactory.createValueStack(stack);
newStack.setValue(name, value);

继续进入类OgnlValueStack方法setValue链,关键源码(155行):

ognlUtil.setValue(expr, context, root, value);

继续进入类OgnlUtil方法setValue,关键源码(192行):

Ognl.setValue(compile(name), context, root, value);

 

这时,方法调用到达ongl包的核心部分,关键源码(476行):

n.setValue( ognlContext, root, value );

调用链如下:
类ognl.SimpleNode(246行、177行):

evaluateSetValueBody(context, target, value);
setValueBody(context, target, value);

到达类ASTProperty方法setValueBody,关键源码(101行):

OgnlRuntime.setProperty( context, target, getProperty( context, target), value );

继续进入类OgnlRuntime方法setProperty,关键源码(1656行):

accessor.setProperty( context, target, name, value );

这时accessor类型为CompoundRootAccessor,
继续进入类CompoundRootAccessor方法setProperty,关键源码(49-52行):

if (OgnlRuntime.hasSetProperty(ognlContext, o, name)) {
OgnlRuntime.setProperty(ognlContext, o, name, value);
return;}

再次进入类OgnlRuntime方法setProperty,关键源码(1656行):

accessor.setProperty( context, target, name, value );

这时accessor类型为ObjectAccessor,
并调用到父类ObjectPropertyAccessor方法setProperty,关键源码(131行):

if (setPossibleProperty(context, target, name, value) == OgnlRuntime.NotFound)

继续,目标方法调用出现(75-76行):

 if (!OgnlRuntime.setMethodValue(ognlContext, target, name, value, true))
result = OgnlRuntime.setFieldValue(ognlContext, target, name, value) ? null : OgnlRuntime.NotFound;


这里涉及2个方法,顺便简单分析一下这2个方法的关键源码:
1、    OgnlRuntime.setMethodValue(964行):

callAppropriateMethod(context, target, target, m.getName(), propertyName, Collections.nCopies(1, m), args);

从中可见,ognl.MethodFailedException就是从这个callAppropriateMethod方法中抛出的
2、    OgnlRuntime.setFieldValue(1136行、1140行、1146行)

state = context.getMemberAccess().setup(context, target, f, propertyName);
f.set(target, value);
context.getMemberAccess().restore(context, target, f, propertyName, state);

从中可见,为field赋值时,如果访问范围是不可外部访问的,先改为可访问,赋值后再改为原访问范围

这2个方法都有一个共同的逻辑:
都判断了(1)传入参数的类型是否为setter方法的参数类型(isAssignableFrom)
否则(2)把传入参数转换为setter方法的参数类型(getConvertedType)
其中逻辑(2)使用了接口ognl.TypeConverter,xwork2中的对应实现类为

com.opensymphony.xwork2.ognl.OgnlTypeConverterWrapper

并且再提供了接口com.opensymphony.xwork2.conversion.TypeConverter,默认的实现类为

com.opensymphony.xwork2.conversion.impl.XWorkBasicConverter


至此,到达本文开始提到的报错问题相关的源代码位置:
类XWorkBasicConverter方法convertValue,关键源码(102-103行):

else if (Date.class.isAssignableFrom(toType)) {
result = doConvertToDate(context, value, toType);}

方法doConvertToDate,关键源码(305行、310-319行、332-336行):

Locale locale = getLocale(context);
else if (java.sql.Timestamp.class == toType) {Date check = null;SimpleDateFormat dtfmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.MEDIUM,locale);SimpleDateFormat fullfmt = new SimpleDateFormat(dtfmt.toPattern() + MILLISECOND_FORMAT, locale);
SimpleDateFormat dfmt = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT,locale);
else if (java.util.Date.class == toType) {Date check = null;SimpleDateFormat d1 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, locale);SimpleDateFormat d2 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale);SimpleDateFormat d3 = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);                

方法getLocale,源码(145-157行):

private Locale getLocale(Map<String, Object> context) {if (context == null) {return Locale.getDefault();}Locale locale = (Locale)context.get(ActionContext.LOCALE);if (locale == null) {locale = Locale.getDefault();}return locale;}


分析:由于java.sql.Timestamp extends java.util.Date,
Date.class.isAssignableFrom判断成功,进入doConvertToDate,

其中使用的Locale变量取自getLocale方法,当context中未被设置Locale时,使用的是本地的默认值。

因此,不管是Date还是Timestamp,当服务器的默认Locale不是通常使用的zh_CN时,转换异常,返回后在callAppropriateMethod中抛出java.lang.NoSuchMethodException。

解决方法有两种:
1、    服务器配置:将服务器的环境变量Lang改为zh_CN.UTF-8
2、    工程配置:对配置文件struts.properties,
写入struts.locale=zh_CN,顺便加上struts.i18n.encoding=UTF-8

 

注:方法2在struts2.1.6下发现无效,经查是由于xwork-core-2.1.2中如下代码段缺少造成的:

ParametersInterceptor.setParameters

缺少代码

//keep locale from original context    
context.put(ActionContext.LOCALE,stack.getContext().get(ActionContext.LOCALE));
 

建议替换为已解决此bug的struts2.1.8

 

或者将新版本的ParametersInterceptor类编译覆盖源文件,地址:

http://svn.apache.org/viewvc?view=revision&revision=956389

问题解决,顺便附上源码包。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值