Bug.:
/-- Encapsulated exception ------------\
java.lang.NoSuchMethodException: setYycbSq([Ljava.lang.String;)
at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:810)
at ognl.OgnlRuntime.setMethodValue(OgnlRuntime.java:964)
at ognl.ObjectPropertyAccessor.setPossibleProperty
(ObjectPropertyAccessor.java:75)
at ognl.ObjectPropertyAccessor.setProperty (ObjectPropertyAccessor.java:
131)
at com.opensymphony.xwork2.ognl.accessor.ObjectAccessor.setProperty
(ObjectAccessor.java:28)
at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:1656)
at ognl.ASTProperty.setValueBody(ASTProperty.java:101)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:177)
在使用 Struts2 框架开发时,有时会遇到上面的异常, Action 类里定义的实体类对象的属性或者直接在 action 中定义的属性变量中如有 Double 类型或 Float 类型时,当页面传值为 0 , 0.0 , 0.00 等为 0 的值时,有时很可能报上面的异常,原因是用户请求后进入到相应的 Action 中,首先执行的是封装页面获取的属性值,即调用属性的 get(),set() 方法,此时由于 struts2 核心包的版本低或者 jar 包冲突覆盖部分功能等问题,不能处理将从页面获取的“ 0 ”或“ 0.0 ”,“ 0.00 ” 转换成 Double 类型或 Float 类型(但可以转换成 Integer 类型),故在通过调用 set() 方法封装页面属性值时就会报类型转换的异常。
Struts2 的类型转换器虽然实现了基本类型转换功能,可以将 String 类型转换为 Integer , Double , Float , Date 等类型,但是对于“ 0 ”或“ 0.0 ”,这个特殊的字符串来说,有时还避免不了会报类型转换的错误。从上面的异常信息我们可以看出,其实 Struts2 是利用 OGNL 进行类型转换的。
使用 Struts2 框架,在页面传 0 ,或 0.0 时,这个异常也不是一定会出现的,有时OGNL也可以帮你正常转换成功,但如果因为 jar 包或其他问题而出现时这样的 bug 时,你可以考虑去下载最新版本的 jar 包;或者在页面传值时加以限定,比如为 Double 值时要求所传值大于 0 等;也可以在 action 里定义变量时尽量用 String 类型代替 Double 和 Float 类型(这样就没利用 Struts2 类型自动转换这个优点了)。
下面是我在网上找到的一点关于对 Struts2 的默认转换的阐述,觉得挺详细的,有利于我们了解 Struts2 类型转换器实现原理
Struts2 可以自动完成大多数常用的类型转换,目前已支持的与字符串之间的转换类型包括:
l String
l boolean/Boolean
l char/Character
l int/Integer,float/Float,long/Long,double/Double
l dates - 使用当前 request 指定的 Locale 信息对应的 SHORT 格式
l arrays - 假定每一个字符串都能够转换成对应的数组元素
l collections - 如果不能确定对象类型 , 将假定集合元素类型为 String, 并创建一个新的 ArrayList
注意,对于数组的类型转换将按造数组元素的类型来单独转换每一个元素。而在其他类型转换中,如果转换无法实现 , 将使用标准的类型转换错误报告。
Struts2 几乎支持了所有的类型,如果没有什么特别的需求,完全没有必要自己去配置类型转换,这也可以说是 Struts2 一个比较优秀的方面。
转换器的源码分析:
现在通过源代码来分析一下 Struts2 是如何实现这些转换器功能的。想弄清楚类型转换是怎么实现的,首先必须找到源代码的起点。如果需要自己手动配置一个类型转换器的时候,可以通过继承 org.apache.struts2.util.StrutsTypeConverter 来实现,在这个类中提供了两个相互转换的函数,通过这两个函数可以很方便的编写处理其他对象和字符串相互转换的类型转换器。这个类的源代码文件的完整路径是: \struts-2.0.9-all\struts-2.0.9\src\core\src\main\java\org\apache\struts2\util 。那么就先看看这个类的代码片断:
StrutsTypeConverter.java
package org.apache.struts2.util;
import java.util.Map;
import ognl.DefaultTypeConverter;
public abstract class Struts2TypeConverter extends DefaultTypeConverter {
public Object convertValue(Map context, Object o, Class toClass) {
//根据不同的类型分别调用不同的转换函数
if (toClass.equals(String.class)) {
//其它类型向字符串类型的转换
return convertToString(context, o);
} else if (o instanceof String[]) {
//字符串类型向其他类型转换
return convertFromString(context, (String[]) o, toClass);
} else if (o instanceof String) {
return convertFromString(context, new String[]{(String) o}, toClass);
} else {
return performFallbackConversion(context, o, toClass);
}
}
protected Object performFallbackConversion(Map context, Object o, Class toClass) {
return super.convertValue(context, o, toClass);
}
public abstract Object convertFromString(Map context, String[] values, Class toClass);
public abstract String convertToString(Map context, Object o);
}
在这个类中定义了 convertValue() 方法,该方法就是为了完成字符串与其他类型之间的转换。如果是其他类型向字符串转换,即 toClass.equals(String.class) ,那么就会调用 convertToString(context, o) 方法,这个方法在下面已经定义了,是一个抽象方法,在子类当中必须加以实现,不同的类型转换为 String 类型有不同的方法。同理,如果是字符串向其他类型的转换就会调用 convertFromString(context, (String[]) o, toClass) 方法,这也是一个抽象方法,需要在子类中自己实现。所以如果想自己实现类型转换器,就只要继承这个类,然后实现类中这两个方法就可以了。如果没有类型要转换,就会执行最后一个 else 语句,就会做到 return performFallbackConversion(context, o, toClass) ,这个方法就只是调用父类中 convertValue(context, o, toClass) ,就是说如果默认的话,就会执行父类中 convertValue(context, o, toClass) 方法。
我们的目的是为了看到 Struts2 在什么地方有对数据类型转换的相关代码,那么就要再向上看这个类的父类 ognl.DefaultTypeConverter 类。从包名可以知道这是在 ognl 包中的一个类。到 ognl 源代码中搜索这个类可以很快找到这个类的源代码。
注意:在 Struts2 压缩包是没有 ognl 的源代码的,需要读者自己再到 ognl 官网去下一个源代码的压缩包,随便选择一版本下载,这一部分代码实现都大同小异,当然最好使用版本相符的版本。
下面就进入 DefaultTypeConverter 类看看它是如何实现的。
DefaultTypeConverter.java
package ognl;
import java.lang.reflect.Member;
import java.util.Map;
public class DefaultTypeConverter implements TypeConverter{
//构造函数
public DefaultTypeConverter(){
super();
}
//实现类型转换的函数
public Object convertValue(Map context, Object value, Class toType){
return OgnlOps.convertValue(value, toType);
}
public Object convertValue(Map context, Object target, Member member,
String propertyName, Object value, Class toType){
return convertValue(context, value, toType);
}
}
StrutsTypeConverter 类从 DefaultTypeConverter 类继承了 convertValue() 方法,在这个父类中的 convertValue() 方法还是继续返回类 OgnlOps 中静态的 convertValue(value, toType) ,这样就表明默认的类型转换是在类 OgnlOps 的 convertValue(value, toType) 中实现的。是不是这样呢?我们先看看这个实现的接口 TypeConverter 的源代码是怎么样的。
TypeConverter.java
package ognl;
import java.lang.reflect.Member;
import java.util.Map;
public interface TypeConverter{
public Object convertValue(Map context, Object target, Member member,
String propertyName, Object value, Class toType);
}
这个接口中就只是定义了一个convertValue()方法,所以最后的实现应该就是在类OgnlOps的convertValue(value, toType)中完成的。我们先来看看这个类的源代码。
OgnlOps.java
package ognl;
/*省略导入的包*/
public abstract class OgnlOps implements NumericTypes{
/*省略其他的方法函数*/
public static Object convertValue( Object value, Class toType){
Object result = null;
if (value != null) {
//不同类型的具体转换
if ( value.getClass().isArray() && toType.isArray() ) {
Class componentType = toType.getComponentType();
result = Array.newInstance(componentType, Array.getLength(value));
for (int i = 0, icount = Array.getLength(value); i < icount; i++) {
Array.set(result, i, convertValue(Array.get(value, i), componentType));
}
} else {
if ( ( toType == Integer.class ) || ( toType == Integer.TYPE ) )
result = new Integer((int)longValue(value));
if ( ( toType == Double.class ) || ( toType == Double.TYPE ) )
result = new Double(doubleValue(value));
if ( ( toType == Boolean.class ) || ( toType == Boolean.TYPE ) )
result = booleanValue(value) ? Boolean.TRUE : Boolean.FALSE;
if ( ( toType == Byte.class ) || ( toType == Byte.TYPE ) )
result = new Byte((byte)longValue(value));
if ( ( toType == Character.class ) || ( toType == Character.TYPE ) )
result = new Character((char)longValue(value));
if ( ( toType == Short.class ) || ( toType == Short.TYPE ) )
result = new Short((short)longValue(value));
if ( ( toType == Long.class ) || ( toType == Long.TYPE ) )
result = new Long(longValue(value));
if ( ( toType == Float.class ) || ( toType == Float.TYPE ) )
result = new Float(doubleValue(value));
if ( toType == BigInteger.class )
result = bigIntValue(value);
if ( toType == BigDecimal.class )
result = bigDecValue(value);
if ( toType == String.class )
result = stringValue(value);
}
} else {
if (toType.isPrimitive()) {
result = OgnlRuntime.getPrimitiveDefaultValue
(toType);
}
}
return result;
}
}
看到这个类的源代码,我们只对 convertValue( Object value, Class toType) 函数感兴趣,可以看到它是一个静态方法,正是这个函数完成许多基本的类型转换。从上面代码中应该很清楚 Struts2 默认的类型转换是怎么实现的了。代码中粗体的部分就是对应的可以被转化的类型,用了多个 if/else 语句,完成了常用类型的转换。每种类型的转换方法都不一样,具体转换代码如源代码所示。这个类当中还有许多其他函数,在此省略了。
通过查找和分析我们发现,这个类型转换的操作早就在 ognl 中实现, Struts2 只是引用了这个包中的函数,然后在自己的源码中进行了一些封装,以便使用的时候可以方便的访问到,不用再去访问 ognl 中的函数。当然 Struts2 早就把 ognl 包作为必须装载的 jar 包之一。
By the way, 供大家交流Pentaho的圈子,里面可以共享有关pentahoBI平台学习的资料,期待您的加入! http://pentahofrends.group.iteye.com/group/share