|
该帖已经被评为精华帖
|
|
|---|---|
| 作者 | 正文 |
|
时间:2006-10-22
被投为新手贴了.这里重新发一下.主要是做一个BeanUtils不支持的复制整个java bean对象树的库.
其实说起来,我要的功能并不复杂。BeanUtils只能处理String, int之类的转换,否则property的类型必须一致才行。 而我遇到的需求,是从一个对象图转换到另一个对象图。比如:
class Person{
private String name;
private Date birthdate;
//getters and setters
}
class JobCategory{
private String category;
//getter and setter
}
class Adult extends Person {
private Adult spouse;
private Person[] kids
private JobCategory jobCategory;
//getters and setters
}
class PersonBean{
private String name;
private Date birthdate;
//getters and setters
}
class JobCategoryBean{
private String categoryName;
//getter and setter
}
class AdultBean extends PersonBean {
private String name;
private Date birthdate;
private JobCategoryBean jobCategory;
private AdultBean spouse;
private List kids;
//getters and setters
}
假设AdultBean, PersonBean是从XMLBeans自动代码生成出来的,现在要把这些东西转换成Adult, Person, JobCategory这种对象树。 我理想的情况是, AdultBean bean = ...; Adult adult = new Adult(); BeanUtils.copy(adult, bean); 然后所有的name, birthdate, jobcategory, spouse, kids都自动转换好了。不能手工转换么?当然能,问题是,当我们每个bean class有二十多个property,有七八种这种bean class,有从axis到xmlbeans,从xmlbeans到业务bean,从甲层到乙层等等等等的转换任务时,这种转换就是一种对程序员的摧残了。 我还以为这个需求不是特别特殊,应该有人遇到过的呢。 当然,我上面举的例子因为JobCategory.name和JobCategoryBean.categoryName这两个property明子不匹配,更一般地说,不是每个property都是那么一对一的,很可能有其它的匹配的不那么整齐的情况发生。 对这种情况,我的解决方法是写一个conversion class:
class AdultConversion{
public static void convert(JobCategory cat, JobCategoryBean bean){
cat.setName(bean.getCategoryName());
}
}
然后把这个conversion class传递给Beans这个facade: AdultBean bean = ...; Adult adult = new Adult(); Beans.copy(adult, bean, new AdultConversion()); Beans会分析AdultConversion的meta data,发现存在一个客户自定义的从JobCategoryBean到JobCategory的转换方法,于是转换仍然可以成功。 目前编码和测试基本完成,即将进入production乐。 居然没人遇到过这个问题么?考虑是否把这个咚咚开源中. 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
时间:2006-10-27
没人关心么?
还是大家还是坚持认为这是一个初学贴,只是给俺留点面子没有再次投票? 今天google,发现还是有人做这方面的东西的。而且从回复来看,大家也都认为这是一个实实在在存在的问题呀。 http://www.theserverside.com/news/thread.tss?thread_id=38898 只不过目前相关的工具还比较少。jxpath不错,不过不能完全解决这个问题。Dozer和otom似乎是这个领域唯二的两个工具。 我不是很喜欢Dozer的xml配置,对otom的代码生成也有点保留。 我目前的解决方法是: Beans.map(target, "personName<=name, hobby<=likes", src); 底层仍然用BeanUtils,不过增加了递归支持,增加了自定义转换,增加了简单的property name mapping。 觉得比写xml配置舒服。 如果我把这个东西开源,有人感兴趣一块儿作么? |
|
| 返回顶楼 | |
|
时间:2006-10-27
偶上次不是已经答复过你了么,用ognl可以很好的解决,现成的util有:com.opensymphony.xwork.util.OgnlUtil.copy,嫌它依赖太多的话,自己写一个也就是几十行代码的事情阿。
|
|
| 返回顶楼 | |
|
时间:2006-10-27
Readonly说的没错,其实xwork已经提供了这样的一个转换框架,思路感觉跟ajoo描述的是一样的。不过有个xx_conversion.propertes看着讨厌,可以自己开发一个。
ajoo的方案还有很多事情要做? ajoo的说的很清晰,学习,论坛有bug,晚点再投个精华帖 另外我在想,既然总有xxbean->yybean的这种数据的传递。JavaBean可以定义get,set方法叫做规范。干脆也定义个bean之间的命名规则好了。这样用户一行代码也不写。 |
|
| 返回顶楼 | |
|
时间:2006-10-27
这方面需求的确碰得很少,最多就是自己重构时的内部一些转换。针对其他人开发的遗留系统的大批量转换我没有遇见过,但是肯定也会存在。
ajoo 写道 我目前的解决方法是:
Beans.map(target, "personName<=name, hobby<=likes", src); 就你的代码示例来看,如果两个对象树中,处于不同层次的属性拷贝,似乎不能处理了,比如 user.person.name <= user.firstName 直觉上,readonly说的ognl来处理更加合适一些。 ognl:user.person.name=user.firstName,user.person.name 意思是: 1. a, b 读取a, 写入b 2. b=a 把a赋值给b |
|
| 返回顶楼 | |
|
时间:2006-10-27
Readonly 写道 偶上次不是已经答复过你了么,用ognl可以很好的解决,现成的util有:com.opensymphony.xwork.util.OgnlUtil.copy,嫌它依赖太多的话,自己写一个也就是几十行代码的事情阿。
ognl和beanutil没有本质区别亚。 我要的是能够任意定制对象树上的任意节点的转换逻辑的(比如一边是name,另一边是通过某个算法计算someAlgorithm(firstname, lastname)这种)。并不是简简单单的做一个property名字映射就能全部搞定的。 你要是能几十行代码搞定我倒是一定要瞧瞧的。 |
|
| 返回顶楼 | |
|
时间:2006-10-27
sorphi 写道 这方面需求的确碰得很少,最多就是自己重构时的内部一些转换。针对其他人开发的遗留系统的大批量转换我没有遇见过,但是肯定也会存在。
ajoo 写道 我目前的解决方法是:
Beans.map(target, "personName<=name, hobby<=likes", src); 就你的代码示例来看,如果两个对象树中,处于不同层次的属性拷贝,似乎不能处理了,比如 user.person.name <= user.firstName 直觉上,readonly说的ognl来处理更加合适一些。 ognl:user.person.name=user.firstName,user.person.name 意思是: 1. a, b 读取a, 写入b 2. b=a 把a赋值给b 对这个我的方法是:
Beans.copy(target, src, new Object(){
public void convert(TargetUser target, SourceUser user){
targetUser.getPerson().setName(user.getFirstName());
...
}
});
或者,还可以用jxpath来更方便地写。 这个和直接写setXXX(getYYY())有什么区别?区别在于,只需要少量定制一些特殊的mapping在这个converer对象里面,而名字相同的property,或者仅仅是名字不同的property都是直接自动搞定。 我这个方法的好处是灵活,可以放任意的业务逻辑在转换器中,而不是仅仅局限在property name mapping上。 当然,能支持"user.person.name <= user.firstName"的语法的话也是不错的特性。毕竟非常方便。 也许应该考虑支持这个功能。 |
|
| 返回顶楼 | |
|
时间:2006-10-27
改一下标题
让标题说明一些东西.... 如果还是这样 被当成新手贴也是早晚的事 |
|
| 返回顶楼 | |
|
时间:2006-10-27
ajoo 写道 Readonly 写道 偶上次不是已经答复过你了么,用ognl可以很好的解决,现成的util有:com.opensymphony.xwork.util.OgnlUtil.copy,嫌它依赖太多的话,自己写一个也就是几十行代码的事情阿。
ognl和beanutil没有本质区别亚。 我要的是能够任意定制对象树上的任意节点的转换逻辑的(比如一边是name,另一边是通过某个算法计算someAlgorithm(firstname, lastname)这种)。并不是简简单单的做一个property名字映射就能全部搞定的。 你要是能几十行代码搞定我倒是一定要瞧瞧的。 ajoo 写道 我这个方法的好处是灵活,可以放任意的业务逻辑在转换器中,而不是仅仅局限在property name mapping上。 ognl的表达式当然不仅仅是property这么简单丫 ognl: new.name = someAlgorithm(old.firstname, old.lastname), new.name 不清楚jxpath,关于ognl实在是太强大了,operators就不说了,表达式支持见附件(屏考了ognl的文档目录)。这些表达式应该能够达到你描述的转换器的功能了。 一个ognl版本的beans transformation 的API应用场景:
BeansTransform.prepare(target, "target", src, "src") //产生一个ognl 的 root context, 表达式"target"指向target实例,"src"指向src实例。或许可以准备更多的参与者。
.map(TransformPolicy.PlainPropertiesCopy) //缺省,拷贝所有同名同级同类型属性
.map("target.newName=src.oldName") //属性复制
.map("target.age=@Converter@Aconvert(src.birthdate)") //特殊逻辑转换复制
//... 更多复杂的转换映射
.excute(); //执行所有映射的ognl表达式、策略等
|
|
| 返回顶楼 | |
|
时间:2006-10-28
恩。低估ognl了。
问几个问题撒: 1。这个BeanTransform类我怎么没找到?和readonly说的OgnlUtil.copy有什么联系?我怎么没看到OgnlUtil.copy能够做这个BeanTransform例子里面做的事情? 2。ognl怎么处理对象创建?obj1.a.b.c = obj2.c.d.e的时候,obj1.a如果是null,ognl是否先创建实例给.a, .a.b?怎么创建?能否定制? 3。给的这个例子是纯粹的所谓context based。就是说,给定一个对象,从根部指定property或者property的property的映射规则。有没有type based?就是说只定义类型之间的转换规则,这个规则通用(除非在context based里面明确制定) 我暂时还是对把大量的转换逻辑用ognl来表达有点犹豫。这样refactor, type checking, auto complete的好处就都没了。 而我的方法就相对来说没这么革命性。复杂的转换逻辑还是在一个java类里面写java代码,除非这个逻辑简单到纯粹名字映射。这样损失的静态类型检查和重构能力就不是很多。 暂时就想到这么多。不过ognl看来明显也是做bean-to-bean mapping的一个有力工具。尤其是项目中不敢用groovy, jruby而又眼馋scripting的时候,倒是一个简洁实现业务逻辑的好的替代品。 |
|
| 返回顶楼 | |










