strtusではリクエストからActionFormにデータを格納するのにBeansUtil.populate()メソッドを使います。このメソッドは任意のMapを指定してJavaBeanのプロパティに値を設定するという使い方ができます。使い方はcommons-beanutils 基礎の基礎 - 倭マン日記で紹介されています。
// サンプルの Bean Person person = new Person(); // サンプルの Map Map<String, String> props = new HashMap<String, String>(); props.put("name", "倭マン"); props.put("age", "97"); // プロパティを一括セット BeanUtils.populate(person, props);
ソースを見るとこんな感じです。処理本体はBeanUtilsBean.populate()メソッドに実装されています。
public static void populate(Object bean, Map properties) throws IllegalAccessException, InvocationTargetException { BeanUtilsBean.getInstance().populate(bean, properties); }
特に変わったところはありません。MapからentrySet()を取ってきて、ぐるぐる回してsetPropertyメソッドでbeanに値を設定していきます。
public void populate(Object bean, Map properties) throws IllegalAccessException, InvocationTargetException { // Do nothing unless both arguments have been specified if ((bean == null) || (properties == null)) { return; } if (log.isDebugEnabled()) { log.debug("BeanUtils.populate(" + bean + ", " + properties + ")"); } // Loop through the property name/value pairs to be set Iterator entries = properties.entrySet().iterator(); while (entries.hasNext()) { // Identify the property name and value(s) to be assigned Map.Entry entry = (Map.Entry)entries.next(); String name = (String) entry.getKey(); if (name == null) { continue; } // Perform the assignment for this property setProperty(bean, name, entry.getValue()); } }
setPropertyでは4つの処理を行っています。
- プロパティの取得(ネストしたプロパティに対応するためResolverを使ってる。)
- beanの型と設定するプロパティの型を取得(beanの型で分岐するゴリゴリの処理)
- 設定するプロパティの型に合わせてvalueを型変換(変換自体はConvertUtilsBeanを使用)
- プロパティに値を設定
public void setProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException { // Trace logging (if enabled) if (log.isTraceEnabled()) { StringBuffer sb = new StringBuffer(" setProperty("); sb.append(bean); sb.append(", "); sb.append(name); sb.append(", "); if (value == null) { sb.append("<NULL>"); } else if (value instanceof String) { sb.append((String) value); } else if (value instanceof String[]) { String[] values = (String[]) value; sb.append('['); for (int i = 0; i < values.length; i++) { if (i > 0) { sb.append(','); } sb.append(values[i]); } sb.append(']'); } else { sb.append(value.toString()); } sb.append(')'); log.trace(sb.toString()); } // Resolve any nested expression to get the actual target bean Object target = bean; Resolver resolver = getPropertyUtils().getResolver(); while (resolver.hasNested(name)) { try { target = getPropertyUtils().getProperty(target, resolver.next(name)); name = resolver.remove(name); } catch (NoSuchMethodException e) { return; // Skip this property setter } } if (log.isTraceEnabled()) { log.trace(" Target bean = " + target); log.trace(" Target name = " + name); } // Declare local variables we will require String propName = resolver.getProperty(name); // Simple name of target property Class type = null; // Java type of target property int index = resolver.getIndex(name); // Indexed subscript value (if any) String key = resolver.getKey(name); // Mapped key value (if any) // Calculate the property type if (target instanceof DynaBean) { DynaClass dynaClass = ((DynaBean) target).getDynaClass(); DynaProperty dynaProperty = dynaClass.getDynaProperty(propName); if (dynaProperty == null) { return; // Skip this property setter } type = dynaProperty.getType(); } else if (target instanceof Map) { type = Object.class; } else if (target != null && target.getClass().isArray() && index >= 0) { type = Array.get(target, index).getClass(); } else { PropertyDescriptor descriptor = null; try { descriptor = getPropertyUtils().getPropertyDescriptor(target, name); if (descriptor == null) { return; // Skip this property setter } } catch (NoSuchMethodException e) { return; // Skip this property setter } if (descriptor instanceof MappedPropertyDescriptor) { if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) { if (log.isDebugEnabled()) { log.debug("Skipping read-only property"); } return; // Read-only, skip this property setter } type = ((MappedPropertyDescriptor) descriptor). getMappedPropertyType(); } else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) { if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) { if (log.isDebugEnabled()) { log.debug("Skipping read-only property"); } return; // Read-only, skip this property setter } type = ((IndexedPropertyDescriptor) descriptor). getIndexedPropertyType(); } else if (key != null) { if (descriptor.getReadMethod() == null) { if (log.isDebugEnabled()) { log.debug("Skipping read-only property"); } return; // Read-only, skip this property setter } type = (value == null) ? Object.class : value.getClass(); } else { if (descriptor.getWriteMethod() == null) { if (log.isDebugEnabled()) { log.debug("Skipping read-only property"); } return; // Read-only, skip this property setter } type = descriptor.getPropertyType(); } } // Convert the specified value to the required type Object newValue = null; if (type.isArray() && (index < 0)) { // Scalar value into array if (value == null) { String[] values = new String[1]; values[0] = null; newValue = getConvertUtils().convert(values, type); } else if (value instanceof String) { newValue = getConvertUtils().convert(value, type); } else if (value instanceof String[]) { newValue = getConvertUtils().convert((String[]) value, type); } else { newValue = convert(value, type); } } else if (type.isArray()) { // Indexed value into array if (value instanceof String || value == null) { newValue = getConvertUtils().convert((String) value, type.getComponentType()); } else if (value instanceof String[]) { newValue = getConvertUtils().convert(((String[]) value)[0], type.getComponentType()); } else { newValue = convert(value, type.getComponentType()); } } else { // Value into scalar if (value instanceof String) { newValue = getConvertUtils().convert((String) value, type); } else if (value instanceof String[]) { newValue = getConvertUtils().convert(((String[]) value)[0], type); } else { newValue = convert(value, type); } } // Invoke the setter method try { getPropertyUtils().setProperty(target, name, newValue); } catch (NoSuchMethodException e) { throw new InvocationTargetException (e, "Cannot set " + propName); } }
処理が4つもあるのは「単一責任の原則」に反しているのでメソッドを分けた方がいいと思います。そもそもBeanUtilはstruts1.0用のクラスでした。それをcommons-beanutilsに持って来たため影響範囲が恐ろしく広いです*1。今更変更できないのかもしれません。技術的負債がフレームワークで公開されてしまうのは恐ろしいですね。