如何赋值一个字段值在原有基础上加几个字_乐信基础框架系列BeanUtil

6b743461b147ec0e61e21e1118ee1b94.png

作者| fredwang(王文武)   

导读

别再为VO到DTO,DTO到PO而苦恼,乐信BeanUtil组件为你排忧!

    在日常java项目开发的过程中,经常会遇到需要将某一个对象的内容拷贝到另一个对象的场景。比如VO到DTO,DTO到PO,这些拷贝大部分的字段名字相同的。对开发同学而言,最直接的办法一路set,像下面这样。

Person p = new Person();
Student s = new Student();

p.setId(s.getId());
p.setName(s.getName());

需求

然而这种做法在字段少,偶尔需要用到字段赋值的时候还行,如果遇到字段多又经常需要在不同类之间转换的时候,此类做法就会导致代码快速膨胀;而且如果某天我加个字段,还得在这里也得修改一遍,很容易遗漏掉,代码的可维护性也很差

当前市面上上已经有多种

  • apache的BeanUtils

  • Spring的BeanUtils

  • cglib的Beancopier

梳理各业务团队自有的共享库,也有一些类似的设计。
对于lexin_common而言,首先要做的是,确认大家对BeanUtil有什么需求。经过分析,有以下几点:

  • 类之间的copy  

  • Map和类之间的转换

而对于市面上几种BeanUtil而言,都能满足第一点诉求,对第二点都未能直接满足。其中apache的BeanUtils可以实现map2Bean,但是没有实现bean2Map。

另外对于apache的BeanUtil来说,他已经上了阿里巴巴Java代码规范的黑名单,原因是性能太差。

设计

既然没有满足的,那就自己来干了。除了以上需求外,设计一个BeanUtil有哪些需要注意的呢?

  • 哪些字段需要copy

  • 如何保证完整copy

  • 如何确保所需字段都能copy

  • 如何才能快速完成copy

  • lexin_common的一些小特性

哪些字段需要copy

对于一个class而言,最基本的字段就是该class本身定义的相关字段了。除此以外,有些class还会继承自某一个父类,那么这个时候父类的public和protect字段也是需要关注的(敲黑板:Java各作用域的可见性)

fields = superClazz.getDeclaredFields();
fields = fields == null? new Field[]{} : fields;
for (Field field : fields) {
    int modifier = field.getModifiers();
    if(Modifier.isPublic(modifier) || Modifier.isProtected(modifier)){
        list.add(field);
    }
}
i++;
superClazz = superClazz.getSuperclass();
如何保证完整copy

对于需要拷贝的字段,如何保证我们的值都能copy过去也是我们需要考量的。这里主要需区别是基础类型和引用类型。对于基础类型,我们可以直接调用fied.set就能解决;对于引用类型,还需要多走一些拷贝

//这里做了一些小优化,把String,Number等类型也直接处理了 
if(ReflectUtil.isSimpleDataType(field.getType())){
    ReflectUtil.setFieldValue(obj, field.getName(), source);
}else{
    copyProperties(source, target);
}
如何确保所需字段都能copy

平常工作中,很多同学会使用Lombok这一利器来减少我们的代码。然后在带来代码简洁的同时也对我们的工作带来一些复杂度,对市面上的集中BeanUtil分析测试发现对Lombok支持有如下表现

BeanUtil方法支持 @Data支持 @Accessors
apache
spring
cglib

apache和cglib都不能支持的原因在于他们在为字段set值时,去找返回为空的set方法(带上@Accessors注解以后,字段的set方法会返回this,不是默认的void),找不到字段的set方法就没法给字段set值

lexin_common由于直接使用了fied的get/set方法,所以Lombok的这些注解都不会影响他们,从原理来说,就算没有这些getter/setter方法,lexin_common也能把所有字段拷贝过去

如何才能快速完成copy

在做拷贝的过程中,原理都是从Java字节码反射获取到相关class,而反射本身是非常耗性能的。在开发BeanUtil的过程中,性能问题被大家抛了出来。主要的解决方案是针对反射环节做缓存。在整个BeanUtil模块,针对了以下几个环节做缓存

  • 针对构造方法的缓存,避免每次生成新对象找对应构造方法

  • 针对字段的缓存,避免每次获取某个类的所有字段需要重新反射

另外由于担心某些场景缓存的class或者字段太多,引发进程OOM,所以所有的缓存都是用WeakHashMap来实现。

增加缓存相关能力以后,lexin_common比起apache和cglib的性能都有几倍的提升。在不加一些特殊feature场景下,和spring的性能差不多。

lexin_common的一些小特性
字段别名

在lexin_common的开发过程中,针对公司一些场景的需求,lexin_common的BeanUtil增加了名字alias的feature,当我们需要拷贝的源class A 的字段A1到目标class B的字段B1的时候,可能会由于某些原因,A1和B1不相同并且无法做变更,这个时候可以使用字段B1打上FieldAliasAnnotation({"A1"})注解,就能把A1拷贝到B1上面去了。另外别名注解在源或目标Bean上都可以生效,别名也可以有多个。欢迎大家体验一下哦。

Date类型格式化

有些源Bean日期类型Date字段A,需要拷贝到目标Bean的字符串字段A上,可以使用日期格式化注解@DateFormatFieldAnnotation("yyyy-MM-dd"),这样日期字段拷贝成目标格式字符串;

当下

BeanUtil已经发布到线上支持了下列三个方法

  • copyProperties

  • toBean

  • toMap

这些方法都是单个对象之间做转换的,也有部分团队有List的对象做转换的需求,不过按照现在命名规则,会写成toMapList,容易造成误解,暂时没有发布出来。研发同学对这里有建议或者诉求的,欢迎提出来哈

各方案对比
方案性能(百万次,ms)支持map转换别名日期格式转换
apache BeanUtil3610×××
spring BeanUtils390×××
cglib BeanCopier202×××
hutool569×
lexin_common563

end

6a1b6142d5d50734f34303cfb8353dfd.png

5dfaf7221463fd33338270b060f475cc.png在看点这里 02a22341cbd685d1c32e9f97da3a1fbe.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值