最近,一个曾经出现的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
问题解决,顺便附上源码包。
分享到:
相关推荐
动态方法调用是Struts2提供的一种机制,允许用户在表单提交时指定一个Action实例下的特定方法来处理请求。在HTML表单中,`action`属性不再是直接指向Action类的名字,而是以`ActionName!methodName.action`的形式...
- **原理**:通过拦截器实现,拦截器会查找与 Action 类中的 setter 方法相匹配的请求参数,并调用这些方法填充数据。 **3.3 数据传递** - **方法**:通过 Action 的 getter 方法获取数据,然后将其添加到 request ...
在Struts2中,你可以创建领域对象,通过setter和getter方法来接收和设置请求参数,这些对象可以在多个Action之间共享,提高了代码的复用性。 4. **Struts2_2.1.6版本的中文问题** 在某些版本的Struts2中,可能出现...
- 请求参数绑定:Struts2支持自动将HTTP请求参数绑定到Action的属性上,例如,如果URL参数中有`id`,那么`setStudentId(int id)`会被调用来设置Action中的`studentId`属性。 - 结果转发:当Action执行完毕后,会...
当用户发送一个HTTP请求,比如`http://example.com/myAction?username=John&age=30`,Struts2的拦截器会自动调用setter方法将请求参数`username`和`age`的值注入到Action的相应属性中。这是通过`...
当用户提交表单时,Struts2会根据配置的ActionMapping将请求转发到相应的execute()方法。在该方法中,我们获取表单参数,调用UserDAO的insert()方法,将数据保存到数据库。 **3. 删除用户** 删除用户也是通过...
3. 在调用setter方法前,Struts2会检查该属性是否有自定义的类型转换器。如果存在,它会在Action同级目录下的`action名称-conversion.properties`文件中查找转换规则。若在`src`目录下的`-conversion.properties`...
7. **Struts2的类型转换**:Struts2提供了内置的类型转换器,可以自动将请求参数转换为Java对象。如果需要自定义转换逻辑,可以实现`Converter`接口。 在`Struts2_0900_ModelDrivenParamInput`这个文件中,可能包含...
Struts2是一个强大的MVC(模型-视图-控制器)框架,它被广泛应用于Java Web开发中,提供了灵活的架构和丰富的插件支持。在本教程中,我们将深入探讨如何利用Struts2来实现对Oracle数据库的CRUD(创建、读取、更新和...
3. Struts自动将请求参数值绑定到ActionForm的属性上,这得益于JavaBean规范中的getter和setter方法。 4. ActionServlet调用ActionForm的validate()方法进行表单验证。 5. 如果验证成功,ActionServlet将ActionForm...
### Struts2基础知识点...从过滤器和拦截器的作用到动态方法调用的实现,再到具体的登录和注册请求处理示例,旨在帮助读者全面了解Struts2的基本概念和使用方法。希望本文能对你在学习Struts2框架的过程中有所帮助。
- **请求处理流程**:当用户请求到达时,Struts2的前端控制器会根据配置将请求交给相应的Action处理。 #### 二、Struts2框架搭建与配置 ##### 2.1 导入Jar包 - **下载Struts2的Jar包**:首先需要下载Struts2的库...
- **Struts2** 为每个请求创建新的Action实例,解决了线程安全问题,同时也更易于管理和测试。 5. **Servlet依赖** - **Struts1.x** 的Action类直接依赖于Servlet API,这使得测试变得更加困难。 - **Struts2** ...
为了实现分页功能,我们需要在Struts2中定义Action类来接收用户的翻页请求,并通过Hibernate进行数据查询。具体步骤如下: 1. **定义PageTool类** 在Struts2中,我们通常会定义一个PageTool类来封装分页所需的信息...
OGNL(Object-Graph Navigation Language)是Struts2框架中的一个重要组件,用于在Web应用中访问和操作Java对象的属性,调用其方法,以及执行类型转换等操作。 OGNL是对象图导航语言的缩写,它是一种功能强大的...
- **MVC模式**:理解模型、视图和控制器之间的关系,如何将用户请求转发给Action,Action如何调用模型(业务逻辑)并返回结果给视图(如JSP页面)。 - **Struts2配置**:熟悉`struts.xml`配置文件,包括Action的...
4. **拦截器(Interceptors)**:Struts2的核心特性之一是拦截器,它们在Action调用前后执行,可以用来处理验证、日志、事务管理等任务。例如,`params`拦截器负责将请求参数绑定到Action,`validation`拦截器处理...
3. **Interceptor(拦截器)**:拦截器是Struts2的核心特性之一,它们按照预定义的顺序在Action调用前后执行,可以实现事务管理、权限验证等功能。 4. **Result类型**:Action执行后,会返回一个Result,决定如何...
当客户端提交表单时,Struts2会自动调用setter方法将表单数据注入到Action中。例如,可以通过`ActionContext.getContext().put("newsList", newsList)`将新闻列表存入上下文,然后在页面上通过`”newsList”>`标签...