一个完整网站开发需要什么技术,建e网室内设计网下载,广东东莞最近出什么事了,渭南做网站的公司电话简介#xff1a; 随着系统模块分层不断细化#xff0c;在Java日常开发中不可避免地涉及到各种对象的转换#xff0c;如#xff1a;DO、DTO、VO等等#xff0c;编写映射转换代码是一个繁琐重复且还易错的工作#xff0c;一个好的工具辅助#xff0c;减轻了工作量、提升开…简介 随着系统模块分层不断细化在Java日常开发中不可避免地涉及到各种对象的转换如DO、DTO、VO等等编写映射转换代码是一个繁琐重复且还易错的工作一个好的工具辅助减轻了工作量、提升开发工作效率的同时还能减少bug的发生 作者 | 久贤 来源 | 阿里技术公众号
一 前言
随着系统模块分层不断细化在Java日常开发中不可避免地涉及到各种对象的转换如DO、DTO、VO等等编写映射转换代码是一个繁琐重复且还易错的工作一个好的工具辅助减轻了工作量、提升开发工作效率的同时还能减少bug的发生。
二 常用方案及分析
1 fastjson
CarDTO entity JSON.parseObject(JSON.toJSONString(carDO), CarDTO.class);
这种方案因为通过生成中间json格式字符串然后再转化成目标对象性能非常差同时因为中间会生成json格式字符串如果转化过多gc会非常频繁同时针对复杂场景支持能力不足基本很少用。
2 BeanUtil类
BeanUtil.copyProperties()结合手写get、set对于简单的转换直接使用BeanUtil复杂的转换自己手工写get、set。该方案的痛点就在于代码编写效率低、冗余繁杂还略显丑陋并且BeanUtil因为使用了反射invoke去赋值性能不高。
只能适合bean数量较少、内容不多、转换不频繁的场景。
apache.BeanUtils
org.apache.commons.beanutils.BeanUtils.copyProperties(do, entity);
这种方案因为用到反射的原因同时本身设计问题性能比较差。集团开发规约明确规定禁止使用。 spring.BeanUtils
org.springframework.beans.BeanUtils.copyProperties(do, entity);
这种方案针对apache的BeanUtils做了很多优化整体性能提升不少不过还是使用反射实现比不上原生代码处理其次针对复杂场景支持能力不足。 3 beanCopier
BeanCopier copier BeanCopier.create(CarDO.class, CarDTO.class, false);
copier.copy(do, dto, null);
这种方案动态生成一个要代理类的子类,其实就是通过字节码方式转换成性能最好的get和set方式,重要的开销在创建BeanCopier整体性能接近原生代码处理比BeanUtils要好很多尤其在数据量很大时但是针对复杂场景支持能力不足。
4 各种Mapping框架
分类
Object Mapping 技术从大的角度来说分为两类一类是运行期转换另一类则是编译期转换
运行期反射调用 set/get 或者是直接对成员变量赋值。这种方式通过invoke执行赋值实现时一般会采用beanutil, Javassist等开源库。运行期对象转换的代表主要是Dozer和ModelMaper。编译期动态生成 set/get 代码的class文件在运行时直接调用该class的 set/get 方法。该方式实际上仍会存在 set/get 代码只是不需要开发人员自己写了。这类的代表是MapStruct,Selma,Orika。
分析
无论哪种Mapping框架基本都是采用xml配置文件 or 注解的方式供用户配置然后生成映射关系。编译期生成class文件方式需要DTO仍然有set/get方法只是调用被屏蔽而运行期反射方式在某些直接填充 field的方案中set/get代码也可以省略。编译期生成class方式会有源代码在本地方便排查问题。编译期生成class方式因为在编译期才出现java和class文件所以热部署会受到一定影响。反射型由于很多内容是黑盒在排查问题时不如编译期生成class方式方便。参考GitHub上工程java-object-mapper-benchmark可以看出主要框架性能比较。反射型调用由于是在运行期根据映射关系反射执行其执行速度会明显下降N个量级。通过编译期生成class代码的方式本质跟直接写代码区别不大但由于代码都是靠模板生成所以代码质量没有手工写那么高这也会造成一定的性能损失。综合性能、成熟度、易用性、扩展性mapstruct是比较优秀的一个框架。
三 Mapstruct使用指南
1 Maven引入 2 简单入门案例
DO和DTO
这里用到了lombok简化代码lombok的原理也是在编译时去生成get、set等被简化的代码。
Data
public class Car { private String make; private int numberOfSeats; private CarType type;
}
Data
public class CarDTO { private String make; private int seatCount; private String type;
}
定义Mapper
Mapper中描述映射在编辑的时候mapstruct将会根据此描述生成实现类
当属性与其目标实体副本同名时它将被隐式映射。当目标实体中的属性具有不同名称时可以通过Mapping注释指定其名称。
Mapper
public interface CarMapper { Mapping(source numberOfSeats, target seatCount) CarDTO CarToCarDTO(Car car); }
使用Mapper
通过Mappers 工厂生成静态实例使用。
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) CarDTO CarToCarDTO(Car car);
}
Car car new Car(...);
CarDTO carDTO CarMapper.INSTANCE.CarToCarDTO(car);
getMapper会去load接口的Impl后缀的实现类。 通过生成spring bean注入使用Mapper注解加上spring配置会自动生成一个bean直接使用bean注入即可访问。
Mapper(componentModel spring)
public interface CarMapper { Mapping(source numberOfSeats, target seatCount) CarDTO CarToCarDTO(Car car);
}
自动生成的MapperImpl内容
如果配置了spring bean访问会在注解上自动加上Component。 3 进阶使用
逆向映射
如果是双向映射例如 从DO到DTO以及从DTO到DO正向方法和反向方法的映射规则通常是相似的并且可以通过切换源和目标来简单地逆转。
使用注解InheritInverseConfiguration 指示方法应继承相应反向方法的反向配置。
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) CarDTO CarToCarDTO(Car car); InheritInverseConfiguration Car CarDTOToCar(CarDTO carDTO);
}
更新bean映射
有些情况下不需要映射转换产生新的bean而是更新已有的bean。
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) void updateDTOFromCar(Car car, MappingTarget CarDTO carDTO);
集合映射
集合类型ListSetMap等的映射以与映射bean类型相同的方式完成即通过在映射器接口中定义具有所需源类型和目标类型的映射方法。MapStruct支持Java Collection Framework中的多种可迭代类型。
生成的代码将包含一个循环该循环遍历源集合转换每个元素并将其放入目标集合。如果在给定的映射器或其使用的映射器中找到用于集合元素类型的映射方法则将调用此方法以执行元素转换如果存在针对源元素类型和目标元素类型的隐式转换则将调用此转换。
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) CarDTO CarToCarDTO(Car car); ListCarDTO carsToCarDtos(ListCar cars); SetString integerSetToStringSet(SetInteger integers); MapMapping(valueDateFormat dd.MM.yyyy) MapString, String longDateMapToStringStringMap(MapLong, Date source);
}
编译时生成的实现类 多个源参数映射
MapStruct 还支持具有多个源参数的映射方法。例如将多个实体组合成一个数据传输对象。
在原案例新增一个Person对象CarDTO中新增driverName属性根据Person对象获得。
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source car.numberOfSeats, target seatCount) Mapping(source person.name, target driverName) CarDTO CarToCarDTO(Car car, Person person); }
编译生成的代码 默认值和常量映射
如果相应的源属性是null 则可以指定默认值以将预定义值设置为目标属性。在任何情况下都可以指定常量来设置这样的预定义值。默认值和常量被指定为字符串值。当目标类型是原始类型或装箱类型时String 值将采用字面量在这种情况下允许位/八进制/十进制/十六进制模式只要它们是有效的文字即可。在所有其他情况下常量或默认值会通过内置转换或调用其他映射方法进行类型转换以匹配目标属性所需的类型。
Mapper
public interface SourceTargetMapper { SourceTargetMapper INSTANCE Mappers.getMapper( SourceTargetMapper.class ); Mapping(target stringProperty, source stringProp, defaultValue undefined) Mapping(target longProperty, source longProp, defaultValue -1) Mapping(target stringConstant, constant Constant Value) Mapping(target integerConstant, constant 14) Mapping(target longWrapperConstant, constant 3001) Mapping(target dateConstant, dateFormat dd-MM-yyyy, constant 09-01-2014) Mapping(target stringListConstants, constant jack-jill-tom) Target sourceToTarget(Source s);
}
自定义映射方法或映射器
在某些情况下可能需要手动实现 MapStruct 无法生成的从一种类型到另一种类型的特定映射。
可以在Mapper中定义默认实现方法生成转换代码将调用相关方法
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) Mapping(source length, target lengthType) CarDTO CarToCarDTO(Car car); default String getLengthType(int length) { if (length 5) { return large; } else { return small; } }
}
也可以定义其他映射器如下案例Car中Date需要转换成DTO中的String
public class DateMapper { public String asString(Date date) { return date ! null ? new SimpleDateFormat( yyyy-MM-dd ).format( date ) : null; } public Date asDate(String date) { try { return date ! null ? new SimpleDateFormat( yyyy-MM-dd ).parse( date ) : null; } catch ( ParseException e ) { throw new RuntimeException( e ); } }
}
Mapper(uses DateMapper.class)
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) CarDTO CarToCarDTO(Car car);
}
编译生成的代码 若遇到多个类似的方法调用时会出现模棱两可需使用qualifiedBy指定
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) Mapping(source length, target lengthType, qualifiedByName newStandard) CarDTO CarToCarDTO(Car car); Named(oldStandard) default String getLengthType(int length) { if (length 5) { return large; } else { return small; } } Named(newStandard) default String getLengthType2(int length) { if (length 7) { return large; } else { return small; } }
}
表达式自定义映射
通过表达式可以包含来自多种语言的结构。
目前仅支持 Java 作为语言。例如此功能可用于调用构造函数整个源对象都可以在表达式中使用。应注意仅插入有效的 Java 代码MapStruct 不会在生成时验证表达式但在编译期间生成的类中会显示错误。
Data
AllArgsConstructor
public class Driver { private String name; private int age;
}
Mapper
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source car.numberOfSeats, target seatCount) Mapping(target driver, expression java( new com.alibaba.my.mapstruct.example4.beans.Driver(person.getName(), person.getAge()))) CarDTO CarToCarDTO(Car car, Person person);
}
默认表达式是默认值和表达式的组合
Mapper( imports UUID.class )
public interface SourceTargetMapper { SourceTargetMapper INSTANCE Mappers.getMapper( SourceTargetMapper.class ); Mapping(targetid, sourcesourceId, defaultExpression java( UUID.randomUUID().toString() )) Target sourceToTarget(Source s);
}
装饰器自定义映射
在某些情况下可能需要自定义生成的映射方法例如在目标对象中设置无法由生成的方法实现设置的附加属性。
实现起来也很简单用装饰器模式实现映射器的一个抽象类在映射器Mapper中添加注解DecoratedWith指向装饰器类使用时还是正常调用。
Mapper
DecoratedWith(CarMapperDecorator.class)
public interface CarMapper { CarMapper INSTANCE Mappers.getMapper(CarMapper.class); Mapping(source numberOfSeats, target seatCount) CarDTO CarToCarDTO(Car car);
}
public abstract class CarMapperDecorator implements CarMapper { private final CarMapper delegate; protected CarMapperDecorator(CarMapper delegate) { this.delegate delegate; } Override public CarDTO CarToCarDTO(Car car) { CarDTO dto delegate.CarToCarDTO(car); dto.setMakeInfo(car.getMake() new SimpleDateFormat( yyyy-MM-dd ).format(car.getCreateDate())); return dto; }
}
原文链接 本文为阿里云原创内容未经允许不得转载。