原副标题:Java第一类复本基本上原理探究及最差课堂教学
译者 | 天猫云合作开发人员-象山翔
书名镜像:https://my.oschina.net/u/4090830/blog/5598304
1 序言
第一类复本,是他们在合作开发操作过程中,绕不行的操作过程,既存有于 Po、Dto、Do、Vo 各整体表现层统计数据的切换,也存有于控制系统可视化如格式化、反格式化。
Java 第一类复本分成深复本和浅复本,现阶段常用的特性复本辅助工具,主要包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct 都是浅复本。
1.1 深复本
深复本:对基本上正则表达式进行值传达,对提及正则表达式,建立两个捷伊第一类,并拷贝其文本称作深复本。
深复本常用有下列三种同时实现形式:
缺省 Serializable 格式化 同时实现 Cloneable USB JSON 格式化1.2 浅复本
浅复本:对基本上正则表达式展开值传达,对提及正则表达式展开提及传达般的复本称作浅复本。通过同时实现 Cloneabe USB并重写 Object 类中的 clone 方法可以同时实现浅克隆。
2 常用第一类复本辅助工具基本上原理探究及性能对比
现阶段常用的特性复本辅助工具,主要包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct。
Apache BeanUtils:BeanUtils 是 Apache commons 组件里面的成员,由 Apache 提供的一套开源 api,用于简化对 javaBean 的操作,能够对基本上类型自动切换。 Spring BeanUtils:BeanUtils 是 spring 框架下自带的辅助工具,在 org.springframework.beans 包下, spring 项目可以直接使用。 Cglib BeanCopier:cglib(Code Generation Library)是两个强大的、高性能、高质量的代码生成类库,BeanCopier 依托于 cglib 的字节码增强能力,动态生成同时实现类,完成第一类的复本。 mapstruct:mapstruct 是两个 Java 注释处理器,用于生成类型安全的 bean 映射类,在构建时,根据注解生成同时实现类,完成第一类复本。2.1 基本上原理分析2.1.1 Apache BeanUtils
使用形式:BeanUtils.copyProperties (target, source);
BeanUtils.copyProperties 第一类复本的核心代码如下:
PropertyDeor[] origDeors = this.getPropertyUtils.getPropertyDeors(orig);
PropertyDeor[] temp = origDeors;
intlength = origDeors.length;
String name;
Object value;
第一类每个特性,设置目标第一类特性值
for( inti = 0; i < length; ++i) {
PropertyDeor origDeor = temp[i];
name = origDeor.getName;
// 3.校验源第一类字段可读切目标第一类该字段可写
if(! “class”. equals(name) && this.getPropertyUtils.isReadable(orig, name) &&this.getPropertyUtils.isWriteable(dest, name)) {
try{
value= this.getPropertyUtils.getSimpleProperty(orig, name);
// 5.复本特性
this.copyProperty(dest, name, value);
} catch(NoSuchMethodException var10) {
}
}
}
循环遍历源第一类的每个特性,对于每个特性,复本流程为:
校验目标类的字段是否可写 isWriteable
pleProperty
设置目标类字段的值
PropertyDeor[] deors = this.getPropertyDeors(bean);
if(deors != null) {
for(int i = 0; i < deors.length; ++i) {
if(name.equals(deors[i].getName)) {
returndeors[i];
}
}
}
2.1.2 Spring BeanUtils
使用形式: BeanUtils.copyProperties (source, target);
BeanUtils.copyProperties 核心代码如下:
PropertyDeor[] targetPds = getPropertyDeors(actualEditable);
List<String> ignoreList = ignoreProperties !=null? Arrays.asList(ignoreProperties) : null;
PropertyDeor[] arr$ = targetPds;
intlen$ = targetPds.length;
for( inti$ = 0; i$ < len$; ++i$) {
PropertyDeor targetPd = arr$[i$];
Method writeMethod = targetPd.getWriteMethod;
if(writeMethod != null&& (ignoreList == null|| !ignoreList.contains(targetPd.getName))) {
PropertyDeor sourcePd = getPropertyDeor(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) {
thrownewFatalBeanException( “Could not copy property “+ targetPd.getName + ” from source to target”, var15);
}
}
}
}
}
复本流程简要描述如下:
写目标特性值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
使用形式:
引入 pom 依赖 声明切换USBmapstruct 基于注解,构建时自动生成同时实现类,调用链如下:
MappingProcessor.process -> MappingProcessor.processMapperElements
MapperCreationProcessor.process: 生成同时实现类 Mapper
MapperRenderingProcessor: 将同时实现类 mapper,写入文件,生成 impl 文件
使用时需要声明切换USB,例如:
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
publicinterfaceAirDepartTaskConvert{
AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert.class);
AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);
}
生成的同时实现类如下:
publicclassAirDepartTaskConvertImplimplementsAirDepartTaskConvert{
@Override
publicAirDepartTaskDto convertToDto(AirDepartTask airDepartTask) {
if( airDepartTask == null) {
returnnull;
}
AirDepartTaskDto airDepartTaskDto =newAirDepartTaskDto;
airDepartTaskDto.setId( airDepartTask.getId );
airDepartTaskDto.setTaskId( airDepartTask.getTaskId );
airDepartTaskDto.setPreTaskId( airDepartTask.getPreTaskId );
List<String> list= airDepartTask.getTaskBeginNodeCodes;
if( list!= null) {
airDepartTaskDto.setTaskBeginNodeCodes( newArrayList<String>(list) );
}
// 其他特性复本
airDepartTaskDto.setYn( airDepartTask.getYn );
returnairDepartTaskDto;
}
}
2.2 性能对比
以航空业务控制系统中发货任务 po 到 dto 切换为例,随着复本统计数据量的增大,研究复本统计数据耗时情况
2.3 复本选型
经过以上分析,随着统计数据量的增大,耗时整体呈上升趋势
整体情况下,Apache BeanUtils 的性能最差,日常使用操作过程中不建议使用 在统计数据规模不大的情况下,spring、cglib、mapstruct 差异不大,spring 框架下建议使用 spring 的 beanUtils,不需要额外引入依赖包 统计数据量大的情况下,建议使用 cglib 和 mapstruct 涉及大量统计数据切换,特性映射,格式切换的,建议使用 mapstruct3 最差课堂教学 3.1 BeanCopier使用时可以使用 map 缓存,减少同一类第一类切换时,create 次数
/**
* BeanCopier的缓存,避免频繁建立,高效复用
*/
privatestaticfinalConcurrentHashMap<String, BeanCopier> BEAN_COPIER_MAP_CACHE = newConcurrentHashMap<String, BeanCopier>;
/**
* BeanCopier的copyBean,高性能推荐使用,增加缓存
*
* @paramsource 源文件的
* @paramtarget 目标文件
*/
publicstaticvoid 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
*
* @paramsourceList
* @paramtargetClass
* @param<T>
* @return
*/
publicstatic<T> List<T> copyListProperties( List<?> sourceList,Class<T> targetClass) throwsException{
if(CollectionUtils.isNotEmpty(sourceList)) {
List<T> list= newArrayList<T>(sourceList.size);
for(Object source : sourceList) {
T target = copyProperties(source, targetClass);
list.add(target);
}
returnlist;
}
returnLists.newArrayList;
}
/**
* 返回不同类型第一类统计数据copy,使用此方法需注意不能覆盖默认的无参构造方法
*
* @paramsource
* @paramtargetClass
* @param<T>
* @return
*/
publicstatic<T> T copyProperties(Object source,Class<T> targetClass) throwsException{
T target = targetClass.newInstance;
copyBean(source, target);
returntarget;
}
/**
* @paramsrcClazz 源class
* @paramtgtClazz 目标class
* @returnstring
*/
privatestaticString genKey(Class<?> srcClazz, Class<?> tgtClazz) {
returnsrcClazz.getName + tgtClazz.getName;
}
3.2 mapstruct
mapstruct 支持多种形式第一类的映射,主要有下面几种
基本上映射 映射表达式 多个第一类映射到两个第一类 映射集合 映射 map 映射枚举 嵌套映射 @Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)publicinterfaceAirDepartTaskConvert{
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
publicinterfaceAddressMapper{
AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
// c.多个第一类映射到两个第一类
@Mapping(source = “person.deion”, target = “deion”)
@Mapping(source = “address.houseNo”, target = “houseNumber”)
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
@Mapper
publicinterfaceCarMapper{
// 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 );
}
4 总结
以上就是我在使用第一类复本操作过程中的一点浅谈。在日常控制系统合作开发操作过程中,要深究底层逻辑,哪怕发现一小点的改变能够使他们的控制系统更加稳定、顺畅,都是值得他们去改进的。
最后,希望随着他们的加入,控制系统会更加稳定、顺畅,他们会变得越来越优秀。
你参与开源吗?
抽开源中国周边啦~
END
合作开发人员必备的Firfox插件
这里有最新开源资讯、软件更新、技术干货等文本
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦~