关注收藏是我创作的最大动力
大家可能用各种方法复制过bean。 有很多类型,包括第一个xsdsj的BeanUtil、Spring的BeanUtil和MapStruct。 它们都有各自的实现方法,反射可以生成映射,也可以生成字节码。
可以说每种方式都有各自的优缺点。 (意思是不评价那些好坏。 )今天我只谈cglib的BeanCopier,它有什么特点呢? 因为生成字节码并生成调用set get的方法,所以非常高效。 (实际上生成的代码是b.setxx (a.getax ) ) ) ) )。
如果属性名称匹配但类型不匹配,则支持自定义转换
仅支持符合javaBean规范的属性
复制值,即只有最外面的对象是新的,所有属性都使用原始对象的值(而不是深度复制)。
没错,其最大的特点是生成字节码。
让我们来看看它的使用方法:
beancopierbeancopier=bean copier.create (source class,targetClass,false );
bean copier.copy (源,目标,空);
首先,生成BeanCopier实例。 继承BeanCopier的子类由cglib生成。
在第2步中,调用复制方法。
如果缓存了BeanCopier实例,则此copy方法将变得更快(只是getter和setter )。 另外,您是否有过一点思考,如果需要复制的对象,并且目标类型没有参与结构,该怎么办?
以下是可以很好地解决此问题的objenesisstd (将其集成到spring中)。
在官方网站文档中,Objenesis尝试使用多种方法实例化对象,具体取决于对象类型、JVM版本、JVM供应商和安全管理器。
还没结束。 如果尝试在bean和map之间相互旋转,则BeanCopier不满意。 但是,cglib还有一个BeanMap。 BeanMap是map的实现,我们可以将其用作map。
所以,试着写一下beanCopy的包吧。 是这样的(代码中没有多余的部分)。
公共财务类beancopyutil {
私密beancopyutil
}
privatestaticthreadlocalobjenesisstdthreadlocal=thread local.with initial (objenesisstd 3360: new );
私有静态ConcurrentHashMap,ConcurrentHashMap,beancopiercache=newconcurrenthashmap (;
publicstatictcopy (对象源,类目标) {
返回复制(source,objenesisStdThreadLocal.get ).newinstance );
}
publicstatictcopy (对象源,T target ) {
beancopierbeancopier=getcachebeancopier (source.getclass (,target.getClass ) );
bean copier.copy (源,目标,空);
返回目标;
}
publicstaticlistcopylist (列表源,类目标) {
if(sources.isempty () ) ) )。
return Collections.emptyList (;
}
ArrayList list=new ArrayList (sources.size );
objenesisstdobjenesisstd=objenesisstdthreadlocal.get (;
对象源:源(for ) {
if(source==null ) {
thrownewapiexception (syscode.sys 2000;
}
tnewinstance=objenesisstd.new instance (target;
beancopierbeancopier=getcachebeancopier (source.getclass ),target );
信标拷贝
r.copy(source, newInstance, null);list.add(newInstance);
}
return list;
}
public static T mapToBean(Map, ?> source, Class target) {
T bean = objenesisStdThreadLocal.get().newInstance(target);
BeanMap beanMap = BeanMap.create(bean);
beanMap.putAll(source);
return bean;
}
public static Map, ?> beanToMap(T source) {
return BeanMap.create(source);
}
private static BeanCopier getCacheBeanCopier(Class source, Class target) {
ConcurrentHashMap, BeanCopier> copierConcurrentHashMap = cache.computeIfAbsent(source, aClass -> new ConcurrentHashMap<>(16));
return copierConcurrentHashMap.computeIfAbsent(target, aClass -> BeanCopier.create(source, target, false));
}
}
上面只是一个参考,建议大家自己看下cglib的bean包源码,很有意思,生成字节码的部分在net.sf.cglib.beans.BeanCopier.Generator#generateClass。
我上面说过BeanCopier只支持符合javaBean规范的,原因在这里:
在generateClass方法中,通过ReflectUtils.getBeanGetters获取源类型和目标类型的getter方法
PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(this.source);
PropertyDescriptor[] setters = ReflectUtils.getBeanGetters(this.target);
Map names = new HashMap();
for(int i = 0; i < getters.length; ++i) {
names.put(getters[i].getName(), getters[i]);
}
...
for(int i = 0; i < setters.length; ++i) {
...
}
而ReflectUtils虽然看名字是反射工具类,但getBeanGetters方法是通过反省来拿到对应方法的,
BeanInfo info = Introspector.getBeanInfo(...)
所以,它原则需要符合javaBean规范的属性拷贝。
看到我这么说,是不是感觉有漏洞,再怎么反省,它拿的还是get set方法,只要源类有get方法,目标类有set方法,就没问题,属性名首字母大写也没问题。
最后一点,属性名相同,但是类型不同的,怎么办?
我们看源码:
if (useConverter) {
...
} else if (compatible(getter, setter)) {
e.dup2();
e.invoke(read);
e.invoke(write);
}
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
// TODO: allow automatic widening conversions? return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
}
只有在compatible返回true的时候,才能拷贝值,不然就只能通过转换器Converter了,这里说一下,如果使用了转换器,所有的属性都会走转换器,所以转换器里需要考虑所有类型的情况,这里就不做演示了。
总结
我个人非常喜欢用cglib里的包,cglib的BeanCopier效率也是非常高的,推荐使用。