Struts2学习笔记(十一) 类型转换(Type Conversion)(上)
类型转换概述
把请求参数映射到动作属性的工作是由Parameters拦截器来负责,它是defaultStack拦截器栈中的医院。我们知道,所有的请求参数都是String类型,但是动作的属性却并不都是String类型,那么肯定需要通过某种方式来实现String类型和其他数据类型之间的转换。前面我刚刚学习了OGNL,我们知道通过OGNL能够在拦截器和视图中操作我们的Action成员属性,我们也知道将请求参数映射到Action属性的工作是由Parameters拦截器来实现,那么我们可以推测Struts2正是通过这二者的结合来完成数据类型的转换的。如果我们查看Struts2的源代码,我们就会发现Strtus2确实是通过OGNL的API来实现类型转换的。
OGNL中有一个TypeConversion接口,实现这个类接口的类都可以被当作类型转换器,并且OGNL提供了一个默认的实现类DefaultTypeConversion,这个类通过调用OgnlOps类的converteValue静态方法来实现类型转换:
public static Object convertValue(Object value, Class toType, boolean preventNulls) { Object result = null; if (value != null && toType.isAssignableFrom(value.getClass())) return value; if (value != null) { /* If array -> array then convert components of array individually */ 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 (value.getClass().isArray() && !toType.isArray()) { return convertValue(Array.get(value, 0), toType); } else if (!value.getClass().isArray() && toType.isArray()){ if (toType.getComponentType() == Character.TYPE) { result = stringValue(value).toCharArray(); } else if (toType.getComponentType() == Object.class) { return new Object[] { value }; } } 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); } else if (preventNulls && toType == Boolean.class) { result = Boolean.FALSE; } else if (preventNulls && Number.class.isAssignableFrom(toType)){ result = OgnlRuntime.getNumericDefaultValue(toType); } } if (result == null && preventNulls) return value; if (value != null && result == null) { throw new IllegalArgumentException("Unable to convert type " + value.getClass().getName() + " of " + value + " to type of " + toType.getName()); } return result; }
Struts2中也有一个DefaultTypeConverter,该类实现了ognl.TypeConverter接口,并且实现了一些常用数据类型的转换。XWorkConverter类继承了DefaultTypconvertor类,并在其中做了一些扩展,正式这些扩展实现了Strtus2特色的自定义类型转换功能。在XworkConverter类中重写了DefaultTypeConverter类的convertValue方法。(具体的细节可以查看XworkConverter类的源码)
public Object convertValue(Map<String, Object> context, Object target, Member member, String property, Object value, Class toClass) { // // Process the conversion using the default mappings, if one exists // TypeConverter tc = null; if ((value != null) && (toClass == value.getClass())) { return value; } // allow this method to be called without any context // i.e. it can be called with as little as "Object value" and "Class toClass" if (target != null) { Class clazz = target.getClass(); Object[] classProp = null; // this is to handle weird issues with setValue with a different type if ((target instanceof CompoundRoot) && (context != null)) { classProp = getClassProperty(context); } if (classProp != null) { clazz = (Class) classProp[0]; property = (String) classProp[1]; } tc = (TypeConverter) getConverter(clazz, property); if (LOG.isDebugEnabled()) LOG.debug("field-level type converter for property [" + property + "] = " + (tc == null ? "none found" : tc)); } if (tc == null && context != null) { // ok, let's see if we can look it up by path as requested in XW-297 Object lastPropertyPath = context.get(ReflectionContextState.CURRENT_PROPERTY_PATH); Class clazz = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); if (lastPropertyPath != null && clazz != null) { String path = lastPropertyPath + "." + property; tc = (TypeConverter) getConverter(clazz, path); } } if (tc == null) { if (toClass.equals(String.class) && (value != null) && !(value.getClass().equals(String.class) || value.getClass().equals(String[].class))) { // when converting to a string, use the source target's class's converter tc = lookup(value.getClass()); } else { // when converting from a string, use the toClass's converter tc = lookup(toClass); } if (LOG.isDebugEnabled()) LOG.debug("global-level type converter for property [" + property + "] = " + (tc == null ? "none found" : tc)); } if (tc != null) { try { return tc.convertValue(context, target, member, property, value, toClass); } catch (Exception e) { if (LOG.isDebugEnabled()) LOG.debug("unable to convert value using type converter [#0]", e, tc.getClass().getName()); handleConversionException(context, property, value, target); return TypeConverter.NO_CONVERSION_POSSIBLE; } } if (defaultTypeConverter != null) { try { if (LOG.isDebugEnabled()) LOG.debug("falling back to default type converter [" + defaultTypeConverter + "]"); return defaultTypeConverter.convertValue(context, target, member, property, value, toClass); } catch (Exception e) { if (LOG.isDebugEnabled()) LOG.debug("unable to convert value using type converter [#0]", e, defaultTypeConverter.getClass().getName()); handleConversionException(context, property, value, target); return TypeConverter.NO_CONVERSION_POSSIBLE; } } else { try { if (LOG.isDebugEnabled()) LOG.debug("falling back to Ognl's default type conversion"); return super.convertValue(value, toClass); } catch (Exception e) { if (LOG.isDebugEnabled()) LOG.debug("unable to convert value using type converter [#0]", e, super.getClass().getName()); handleConversionException(context, property, value, target); return TypeConverter.NO_CONVERSION_POSSIBLE; } } } protected void addConverterMapping(Map<String, Object> mapping, Class clazz) { try { String converterFilename = buildConverterFilename(clazz); InputStream is = FileManager.loadFile(converterFilename, clazz); if (is != null) { if (LOG.isDebugEnabled()) { LOG.debug("processing conversion file [" + converterFilename + "] [class=" + clazz + "]"); } Properties prop = new Properties(); prop.load(is); for (Map.Entry<Object, Object> entry : prop.entrySet()) { String key = (String) entry.getKey(); if (mapping.containsKey(key)) { break; } // for keyProperty of Set if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) || key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) { if (LOG.isDebugEnabled()) { LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as String]"); } mapping.put(key, entry.getValue()); } //for properties of classes else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) || key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) || key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX)) ) { TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); if (LOG.isDebugEnabled()) { LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); } mapping.put(key, _typeConverter); } //for keys of Maps else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) { Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); //check if the converter is a type converter if it is one //then just put it in the map as is. Otherwise //put a value in for the type converter of the class if (converterClass.isAssignableFrom(TypeConverter.class)) { TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); if (LOG.isDebugEnabled()) { LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); } mapping.put(key, _typeConverter); } else { if (LOG.isDebugEnabled()) { LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + converterClass + "]"); } mapping.put(key, converterClass); } } //elements(values) of maps / lists else { Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); if (LOG.isDebugEnabled()) { LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + _c + "]"); } mapping.put(key, _c); } } } } catch (Exception ex) { LOG.error("Problem loading properties for " + clazz.getName(), ex); } // Process annotations Annotation[] annotations = clazz.getAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof Conversion) { Conversion conversion = (Conversion) annotation; for (TypeConversion tc : conversion.conversions()) { String key = tc.key(); if (mapping.containsKey(key)) { break; } if (LOG.isDebugEnabled()) { LOG.debug(key + ":" + key); } if (key != null) { try { if (tc.type() == ConversionType.APPLICATION) { defaultMappings.put(key, createTypeConverter(tc.converter())); } else { if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY) || tc.rule().toString().equals(ConversionRule.CREATE_IF_NULL)) { mapping.put(key, tc.value()); } //for properties of classes else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) || tc.rule().toString().equals(ConversionRule.KEY.toString()) || tc.rule().toString().equals(ConversionRule.COLLECTION.toString()) ) { mapping.put(key, createTypeConverter(tc.converter())); } //for keys of Maps else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) { Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); if (LOG.isDebugEnabled()) { LOG.debug("Converter class: " + converterClass); } //check if the converter is a type converter if it is one //then just put it in the map as is. Otherwise //put a value in for the type converter of the class if (converterClass.isAssignableFrom(TypeConverter.class)) { mapping.put(key, createTypeConverter(tc.converter())); } else { mapping.put(key, converterClass); if (LOG.isDebugEnabled()) { LOG.debug("Object placed in mapping for key " + key + " is " + mapping.get(key)); } } } //elements(values) of maps / lists else { mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); } } } catch (Exception e) { } } } } } Method[] methods = clazz.getMethods(); for (Method method : methods) { annotations = method.getAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof TypeConversion) { TypeConversion tc = (TypeConversion) annotation; String key = tc.key(); if (mapping.containsKey(key)) { break; } // Default to the property name if (key != null && key.length() == 0) { key = AnnotationUtils.resolvePropertyName(method); LOG.debug("key from method name... " + key + " - " + method.getName()); } if (LOG.isDebugEnabled()) { LOG.debug(key + ":" + key); } if (key != null) { try { if (tc.type() == ConversionType.APPLICATION) { defaultMappings.put(key, createTypeConverter(tc.converter())); } else { if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY)) { mapping.put(key, tc.value()); } //for properties of classes else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) || tc.rule().toString().equals(ConversionRule.KEY.toString()) || tc.rule().toString().equals(ConversionRule.COLLECTION.toString()) ) { mapping.put(key, createTypeConverter(tc.converter())); } //for keys of Maps else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) { Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); if (LOG.isDebugEnabled()) { LOG.debug("Converter class: " + converterClass); } //check if the converter is a type converter if it is one //then just put it in the map as is. Otherwise //put a value in for the type converter of the class if (converterClass.isAssignableFrom(TypeConverter.class)) { mapping.put(key, createTypeConverter(tc.converter())); } else { mapping.put(key, converterClass); if (LOG.isDebugEnabled()) { LOG.debug("Object placed in mapping for key " + key + " is " + mapping.get(key)); } } } //elements(values) of maps / lists else { mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); } } } catch (Exception e) { } } } } } }
Strtus2通过这个类来实现它特有的类型转换器配置功能,对OGNL做了扩展。OGNL在进行类型转换时会调用OgnlContetxt的getTypeConverter方法来获取类型转换器,通常情况下是Ognl自带的默认类型转换器,Struts2将自己的XWorkConverter设置为OGNL使用的类型转换器,一次来实现对OGNL类型转换的扩展。
类型转换器的查找调用顺序:
(1) 检测我们是否设置了自定义类型转换器,如果找到自定义类型转换器,则使用自定义的类型转换器,否则进行下一步
(2) 检查defaultTypeConverter(默认类型转换器)属性是否可用,如果可用,则使用默认类型转换器进行转换,否则进行下一步
(3) 调用父类DefaultTypeConverter进行类型转换
注:这些调用顺序是覆盖的关系,即执行了(1)就不会再执行(2),比如我们自定义了int类型的类型转换器,那么不管我们是否能够实现类型转换,它都不会再去使用系统内建的类型转换器了。
内建的类型转换器
Strtus2内建的类型转换器能处理绝大多数的需求,只有在少数情况下需要我们自己自定义类型转换器。关系系统内建的类型转换器的实现可以查看XworkBasicConverter类和EnumTypeConverter类的源码。下面我们就来看看这些内建的类型转换器能够完成哪些工作。
简单类型
Struts2已经内置了基本数据类型及其包装类和其他一些常见的用于表示数字/日期类型的类型转换器,包括:
int/Integer:整数型
short/Short:短整数型
long/Long:长整型
float/Float:浮点型
double/Double:双精度型
boolean/Boolean:布尔型
byte/Byte:字节型
char/Character:字符型
BigInteger:大整数型
BigDecimal:大浮点数型
Date:日期型
Array和List
数组和List在表单提交页面的表示方法并没有差别,只是在Action中声明时有点差别。
HelloWorld.java
public class HelloWorld extends ActionSupport { private String[] names; private List<String> list; //省去set和get方法的定义 public String execute() throws Exception { return "success"; } }
input.jsp
<form action="hello.action" method="post">
name1 : <input type="text" name="names"/><br/>
name2 :<input type="text" name="names"><br/>
list:<input type="text" name="list[0]"><br/>
list:<input type="text" name="list[1]"><br/>
<input type="submit" value="submit"/>
</form>
数组和List表单提交页面的表示方式使用上面的两种方式均可。
Map类型
使用Map类型的方式和List相似,区别就是我们需要为Map类型指定key值,即在表单输入界面的表示形式如下:
map:<input type="text" name="map[‘name1’]"><br/>
map:<input type="text" name="map[‘name2’]"><br/>
枚举类型
枚举类型的使用方式和基本数据类型相同,只是限定了只可以输入指定的数据。
普通JaveBean
这个我们在学习Action的时候已经学习过了,就是当我们在Action中使用一个自定的类对象来接收请求参数。那么我只需要在表单提交页面做一些修改就可以让Struts2自动将请求中的参数转移到我们的JavaBean对象上了,这其实就是将复杂对象的属性分开来进行类型转换并填充。这里就不做演示了。
注:这里我们所说的数组、List、Map以及JavaBean的自动类型转换,仅仅限制在他们所包含的对象或属性都是Struts2内建拦截器能够实现类型转换的前提下。如果我们的List中的对象的类型无法使用框架内建的类型转换器来完成,那么还得需要我们自定义类型转换器才行。