- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
作者:宁海翔 。
对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化.
Java对象拷贝分为深拷贝和浅拷贝,目前常用的属性拷贝工具,包括Apache的BeanUtils、Spring的BeanUtils、Cglib的BeanCopier、mapstruct都是浅拷贝.
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容称为深拷贝.
深拷贝常见有以下四种实现方式
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝称为浅拷贝。通过实现Cloneabe接口并重写Object类中的clone()方法可以实现浅克隆.
目前常用的属性拷贝工具,包括Apache的BeanUtils、Spring的BeanUtils、Cglib的BeanCopier、mapstruct.
2.1.1 Apache BeanUtils 。
使用方式:BeanUtils.copyProperties(target, source); BeanUtils.copyProperties 对象拷贝的核心代码如下
// 1.获取源对象的属性描述
PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);
PropertyDescriptor[] temp = origDescriptors;
int length = origDescriptors.length;
String name;
Object value;
// 2.循环获取源对象每个属性,设置目标对象属性值
for(int i = 0; i < length; ++i) {
PropertyDescriptor origDescriptor = temp[i];
name = origDescriptor.getName();
// 3.校验源对象字段可读切目标对象该字段可写
if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
try {
// 4.获取源对象字段值
value = this.getPropertyUtils().getSimpleProperty(orig, name);
// 5.拷贝属性
this.copyProperty(dest, name, value);
} catch (NoSuchMethodException var10) {
}
}
}
循环遍历源对象的每个属性,对于每个属性,拷贝流程为
由于单字段拷贝时每个阶段都会调用PropertyUtilsBean.getPropertyDescriptor获取属性配置,而该方法通过for循环获取类的字段属性,严重影响拷贝效率。 获取字段属性配置的核心代码如下:
PropertyDescriptor[] descriptors = this.getPropertyDescriptors(bean);
if (descriptors != null) {
for (int i = 0; i < descriptors.length; ++i) {
if (name.equals(descriptors[i].getName())) {
return descriptors[i];
}
}
}
使用方式: BeanUtils.copyProperties(source, target); BeanUtils.copyProperties核心代码如下:
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
PropertyDescriptor[] arr$ = targetPds;
int len$ = targetPds.length;
for(int i$ = 0; i$ < len$; ++i$) {
PropertyDescriptor targetPd = arr$[i$];
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
} catch (Throwable var15) {
throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
}
}
}
}
}
拷贝流程简要描述如下
与Apache BeanUtils的属性拷贝相比,Spring通过Map缓存,避免了类的属性描述重复获取加载,通过懒加载,初次拷贝时加载所有属性描述.
2.1.3 Cglib BeanCopier 。
使用方式:
BeanCopier beanCopier = BeanCopier.create(AirDepartTask.class, AirDepartTaskDto.class, false);
beanCopier.copy(airDepartTask, airDepartTaskDto, null);
create调用链如下
BeanCopier.create -> BeanCopier.Generator.create -> AbstractClassGenerator.create ->DefaultGeneratorStrategy.generate -> BeanCopier.Generator.generateClass 。
BeanCopier 通过cglib动态代理操作字节码,生成一个复制类,触发点为BeanCopier.create 。
2.1.4 mapstruct 。
使用方式
mapstruct基于注解,构建时自动生成实现类,调用链如下: MappingProcessor.process -> MappingProcessor.processMapperElements MapperCreationProcessor.process:生成实现类Mapper MapperRenderingProcessor:将实现类mapper,写入文件,生成impl文件 使用时需要声明转换接口,例如:
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface AirDepartTaskConvert {
AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert.class);
AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);
}
生成的实现类如下:
public class AirDepartTaskConvertImpl implements AirDepartTaskConvert {
@Override
public AirDepartTaskDto convertToDto(AirDepartTask airDepartTask) {
if ( airDepartTask == null ) {
return null;
}
AirDepartTaskDto airDepartTaskDto = new AirDepartTaskDto();
airDepartTaskDto.setId( airDepartTask.getId() );
airDepartTaskDto.setTaskId( airDepartTask.getTaskId() );
airDepartTaskDto.setPreTaskId( airDepartTask.getPreTaskId() );
List<String> list = airDepartTask.getTaskBeginNodeCodes();
if ( list != null ) {
airDepartTaskDto.setTaskBeginNodeCodes( new ArrayList<String>( list ) );
}
// 其他属性拷贝
airDepartTaskDto.setYn( airDepartTask.getYn() );
return airDepartTaskDto;
}
}
以航空业务系统中发货任务po到dto转换为例,随着拷贝数据量的增大,研究拷贝数据耗时情况 。
经过以上分析,随着数据量的增大,耗时整体呈上升趋势 。
使用时可以使用map缓存,减少同一类对象转换时,create次数 。
/**
* BeanCopier的缓存,避免频繁创建,高效复用
*/
private static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIER_MAP_CACHE = new ConcurrentHashMap<String, BeanCopier>();
/**
* BeanCopier的copyBean,高性能推荐使用,增加缓存
*
* @param source 源文件的
* @param target 目标文件
*/
public static void copyBean(Object source, Object target) {
String key = genKey(source.getClass(), target.getClass());
BeanCopier beanCopier;
if (BEAN_COPIER_MAP_CACHE.containsKey(key)) {
beanCopier = BEAN_COPIER_MAP_CACHE.get(key);
} else {
beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
BEAN_COPIER_MAP_CACHE.put(key, beanCopier);
}
beanCopier.copy(source, target, null);
}
/**
* 不同类型对象数据copylist
*
* @param sourceList
* @param targetClass
* @param <T>
* @return
*/
public static <T> List<T> copyListProperties(List<?> sourceList, Class<T> targetClass) throws Exception {
if (CollectionUtils.isNotEmpty(sourceList)) {
List<T> list = new ArrayList<T>(sourceList.size());
for (Object source : sourceList) {
T target = copyProperties(source, targetClass);
list.add(target);
}
return list;
}
return Lists.newArrayList();
}
/**
* 返回不同类型对象数据copy,使用此方法需注意不能覆盖默认的无参构造方法
*
* @param source
* @param targetClass
* @param <T>
* @return
*/
public static <T> T copyProperties(Object source, Class<T> targetClass) throws Exception {
T target = targetClass.newInstance();
copyBean(source, target);
return target;
}
/**
* @param srcClazz 源class
* @param tgtClazz 目标class
* @return string
*/
private static String genKey(Class<?> srcClazz, Class<?> tgtClazz) {
return srcClazz.getName() + tgtClazz.getName();
}
mapstruct支持多种形式对象的映射,主要有下面几种 。
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface AirDepartTaskConvert {
AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert.class);
// a.基本映射
@Mapping(target = "createTime", source = "updateTime")
// b.映射表达式
@Mapping(target = "updateTimeStr", expression = "java(new SimpleDateFormat( \"yyyy-MM-dd\" ).format(airDepartTask.getCreateTime()))")
AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);
}
@Mapper
public interface AddressMapper {
AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
// c.多个对象映射到一个对象
@Mapping(source = "person.description", target = "description")
@Mapping(source = "address.houseNo", target = "houseNumber")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
@Mapper
public interface CarMapper {
// d.映射集合
Set<String> integerSetToStringSet(Set<Integer> integers);
List<CarDto> carsToCarDtos(List<Car> cars);
CarDto carToCarDto(Car car);
// e.映射map
@MapMapping(valueDateFormat = "dd.MM.yyyy")
Map<String,String> longDateMapToStringStringMap(Map<Long, Date> source);
// f.映射枚举
@ValueMappings({
@ValueMapping(source = "EXTRA", target = "SPECIAL"),
@ValueMapping(source = "STANDARD", target = "DEFAULT"),
@ValueMapping(source = "NORMAL", target = "DEFAULT")
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
// g.嵌套映射
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", ignore = true)
@Mapping(target = "ornament", source = "interior.ornament")
@Mapping(target = "material.materialType", source = "material")
@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
FishTankDto map( FishTank source );
}
以上就是我在使用对象拷贝过程中的一点浅谈。在日常系统开发过程中,要深究底层逻辑,哪怕发现一小点的改变能够使我们的系统更加稳定、顺畅,都是值得我们去改进的.
最后,希望随着我们的加入,系统会更加稳定、顺畅,我们会变得越来越优秀.
最后此篇关于Java对象拷贝原理剖析及最佳实践的文章就讲到这里了,如果你想了解更多关于Java对象拷贝原理剖析及最佳实践的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个基类和两个派生类,我需要将一个指向派生类对象的指针复制到另一个类中,就像示例一样。 class Base { public: Base(const Base& other); } cl
考虑 Container 类,它主要存储 Box 对象的 unique_ptr vector ,并可以对它们执行一些计算。 class Container { private: std::
引用是指保存的值为对象的地址。在 Python 语言中,一个变量保存的值除了基本类型保存的是值外,其它都是引用,因此对于它们的使用就需要小心一些。下面举个例子: 问题描述:已知一个列表,求生成一个
我正在尝试实现 Bron-Kerbosch 算法,这是一种用于查找派系的递归算法。我设法达到了一个点,它返回了正确数量的派系,但是当我打印它们时,它们不正确 - 添加了额外的节点。我在这里遗漏了什么明
在评估中,我选择了选项LINE I 上的运行时错误。没有未定义行为这样的选项,尽管我认为这是正确的选择。 我不确定,但我认为评估有误。我编译并运行了该程序,它确实打印了 3, 9, 0, 2, 1,
在函数签名中通过 const 值传递参数是否有任何好处(或相反,成本)? 所以: void foo( size_t nValue ) { // ... 对比 void foo( const s
我为 answer to another question 写了一个 OutputIterator .在这里: #include using namespace std; template clas
我有一个由第三方生成的 dll,它具有某种内部数据结构,将其大小限制为 X 个元素。 所以基本上,它有一个以 X 为限制的队列。 据我所知,DLL 是每个进程的,但是是否可以多次加载 DLL?也许每个
假设我有以下两个数据结构: std::vector all_items; std::set bad_items; all_items vector 包含所有已知项和 bad_items vector
如何在不渲染 CGImage 的情况下从另一个 CIImage 复制一个 CIImage 最佳答案 CIImage *copiedImage = [originalImage copy]; 正如您在
我有一个名为 UINode 的 GUI,我想创建一个拷贝并只更改一些内容。该项目由 3 个基本线程组成。 PingThread、RosThread 和 GuiThread。我试图复制粘贴项目文件夹并将
Qt 新手。如果这个问题太幼稚,请多多包涵。在 Windows 操作系统环境中,我有 Qt 对话框框架应用程序,它具有“重复”- 按钮。在同一目录中,有 Qt 应用程序 - (一个带有关闭按钮的对话框
我正在尝试创建一个函数来复制我的卡片结构。我只需复制 cvalue 即可轻松开始。然而,我的 cvalue 没有复制,当应该读取 1000 时它仍然读取 5。 #include #include
string str1("someString"); string str2 = string(str1);//how many copies are made here //copy2 =
我希望了解 boost::bind 执行何种函数对象的内部拷贝。由于这些对象的构造函数似乎没有被调用,我推测这是一种“非常浅的复制”,所以我引入了动态内存分配来产生一些错误。但是,下面代码的运行时输出
我正在查看 http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c22-make-default-operations-consis
下面的类方法Augmented3dPoint::getWorldPoint()返回对其成员的引用 cv::Point3f world_point; class Augmented3dPoint { p
我需要通过 MyClass2 将用户定义的 lambda 传递给 MyClass1。我想确保只有一步,没有拷贝。下面的代码实现了吗?有没有更好的方法来做到这一点(比如使用编译器完成的隐式移动)? 注意
在我的数据库访问代码中,我想写一个方法: variant_t GetQueryRows (...) 我想这样调用它: const variant_t result = GetQueryRows (..
我有一个包含引用的类,例如: class A { A(B &b) : b(b) {} // constructor B &b; } 有时b必须是只读的,有时是可写的。当我创建一个 const A
我是一名优秀的程序员,十分优秀!