Version-info: BeanUtils 1.7.0
Bean Copy 포스트의 말미에서 얘기했듯이 특정 property를 변환하여 bean을 복사하는 방법에 대해서 알아보자.
내가 생각한 방법은 BeanUtils 또는 PropertyUtils의 describe 함수를 사용하여 original bean이 가지고 있는 properties의 Map 객체를 받은 후 Map에 있는 Object를 변경하고, BeanUtils 객체의 populate 함수를 사용하여 destination bean으로 복사를 하는 방법이다.
describe는 bean이 가지고 있는 모든 property를 Map으로 반환하는 함수이고, populate는 Map을 원하는 bean으로 복사하는 함수니까 얼핏 보면 간단할 것 같다.
그런데 내가 삽질한 부분은 BeanUtils와 PropertyUtils의 describe함수가 다르게 동작한다는 것이다.
BeanUtils API 문서를 보면 알겠지만 Map이 Generic으로 되어 있지 않다. BeanUtils.describe도 return type이 java.util.Map이고 PropertyUtils.describe도 마찬가지로 java.util.Map이다. 그래서 Map에 들어가 있는게 뭔지 모르겠다. 이게 뭥미…
공통적으로
두 함수의 모두 Map의 Key값으로 String 객체를 이용하며, property name이 된다. 예를 들어 bean에 setName함수와 getName함수가 있다면 “name”으로된 key가 있고 getName 함수의 return 값이 “name” key의 value로 들어간다.
PropertyUtils.describe(Object bean) 함수의 경우
return type은 Map<String, Object> 의 형태다. 즉, bean에 설정된 property의 객체 그대로를 Map에 담는다. 즉 bean에서 다음과 같이 선언되어 있다면
public Name getName(){ ... }
맵에는 {“name”, Name instance} 형태로 저장이 된다는 얘기다.
BeanUtils.describe(Object bean)의 경우
java.util.Map<String, String> 형태다. 즉, 모든 property는 String.class에 설정된 Converter를 통해서 변환되어 Map에 담게 된다.
위와 같이 getName이 선언되어 있고 기본 ConvertUtils를 사용한다면 사용자가 직접 정의한 Name 객체에 대한 변환 로직이 당연히 없을 것이기 때문에, Object.toString함수를 이용하여 변환한다. 만약 Name 객체에서 toString함수를 Overriding했다면 그 함수를 실행해서 변환하게 되지만, 그마저도 구현하지 않았다면 Object.toString 함수를 그대로 사용하기 때문에 “name”에 대한 value는 Name 인스턴스의 hashCode가 String형태로 들어가게 된다.
결과적으로
아마도 BeanUtils의 String 변환은 출력에 사용될 경우 많은 곳에 쓰이겠지만, 난 변환된 bean을 로직에 사용하고자 하므로 PropertyUtils의 describe를 쓰는 것이 맞겠다. 그런데 일일이 Map에서 꺼내서 변환하면 귀찮으니까 ConvertUtils 스타일로 Convert하나 만들었다.
ConvertUtilsBean.java
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.insoul.util.Converter;
public class ConvertBeanUtil {
static Map<String, Converter> converters = new HashMap<String, Converter>();
public static Map<String, Object> convert(Map<String, Object> des) throws Exception{
Iterator<String> property_names = des.keySet().iterator();
while(property_names.hasNext()){
String property_name = property_names.next();
if(converters.containsKey(property_name)){
Object new_value = converters.get(property_name)
.convert(des.get(property_name));
des.put(property_name, new_value);
}
}
return des;
}
public static void register(String property_name, Converter conv){
converters.put(property_name, conv);
}
public static void deregister(String property_name){
converters.remove(property_name);
}
}
Converter.java
package org.insoul.util;
public interface Converter {
public Object convert(Object obj) throws Exception;
}
Implementation of Converter
import org.insoul.util.Converter;
public class ConverterSearchType implements Converter{
public Object convert(Object obj) throws Exception{
try{
return ((String)obj).charAt(0);
}catch(ClassCastException e){
throw new Exception("Type conversion failed", e);
}
}
}
Usage
ConvertBeanUtil.register("searchType", new ConvertSearchType());
Map<String, Object> des = PropertyUtils.describe(orignal_bean);
Map<String, Object> map = ConvertBeanUtil.convert(des);
BeanUtils.populate(newbean, map);
Posted in dev.