传统bean转换问题
随着应用程序变得越来越复杂,处理大量数据对象之间的转换成为一项常见任务。在处理这些转换时,性能往往是一个关键因素。
以三层系统为例:假设我们正在构建一个活动管理系统,其中有三个实体对象:Activity(活动)、ActivityDTO(活动的数据传输对象)和ActivityEntity(活动的持久化实体)。它们在不同的层中使用。 controller层代码
@RestController
@RequestMapping("/activities")
public class ActivityController {
@Autowired
private ActivityService activityService;
@PostMapping
public BaseResponse createActivity(@RequestBody ActivityDTO activityDTO) {
// 将ActivityDTO转换为Activity对象
Activity activity = convertToActivity(activityDTO);
// 调用Service层创建活动
activityService.createActivity(activity);
return BaseResponse.SUCCESS();
}
// 将ActivityDTO转换为Activity对象的方法
private Activity convertToActivity(ActivityDTO activityDTO) {
Activity activity = new Activity();
activity.setTitle(activityDTO.getTitle());
activity.setDescription(activityDTO.getDescription());
// 其他属性的转换...
return activity;
}
// 将Activity转换为ActivityDTO对象的方法
private ActivityDTO convertToActivityDTO(Activity activity) {
ActivityDTO activityDTO = new ActivityDTO();
activityDTO.setId(activity.getId());
activityDTO.setTitle(activity.getTitle());
// 其他属性的转换...
return activityDTO;
}
}
service层
@Service
public class ActivityService {
@Autowired
private ActivityRepository activityRepository;
public void createActivity(Activity activity) {
// 直接使用Repository层保存活动对象
activityRepository.save(activity);
}
}
Repository层
@Repository
public class ActivityRepository {
@Autowired
private ActivityMapper activityMapper;
public void save(Activity activity){
ActivityEntity activityentity = convertToActivityEntity(activity);
activityMapper.insert(activityentity);
}
private ActivityEntity convertToActivityEntity(Activity activity) {
ActivityEntity activityentity = new ActivityEntity();
activityentity.setTitle(activity.getTitle());
activityentity.setDescription(activity.getDescription());
// 其他属性的转换...
return activityentity;
}
// Repository层的其他方法...
}
经常看到上述对象拷贝的重复代码,这样编写代码有 3 类问题:
-
代码设计不合理:对象拷贝代码分散到不同层级,遍布不同的角落,从代码设计的角度看来不合理,而且代码中大段的同名同类型字段的重复设置,代码不够整洁;
-
开发枯燥又容易犯错:RD 编写大量同名同类型字段的拷贝代码很枯燥又容易犯错;
-
可扩展性差、不易维护:类新增、修改、删除字段时,不仅需要修改 DTO、DO、ENTITY 等类定义,还需要确保散落在各层的对象拷贝方法都修改一遍,只要有任何一处遗漏或者字段设置错误就会出现问题。
为了简化对象拷贝的工作,我们使用过各种bean copy工具,下面是常用的一些工具性能对比:
数据来源这里参考了:www.jianshu.com/p/20a1c04e9…
Benchmark Mode Cnt Score Error Units
BeanCopyTest.apacheBeanUtilsCopyTest thrpt 9 542.549 ± 43.868 ops/ms
BeanCopyTest.cglibBeanCopierTest thrpt 9 250689.491 ± 3690.880 ops/ms
BeanCopyTest.getSetTest thrpt 9 250695.090 ± 4487.843 ops/ms
BeanCopyTest.mapstructTest thrpt 9 249712.148 ± 4686.468 ops/ms
BeanCopyTest.springBeanCopierTest thrpt 9 238908.247 ± 37465.537 ops/ms
BeanCopyTest.springBeanUtilsCopyTest thrpt 9 11528.375 ± 797.580 ops/ms
对比常用几种bean copy得出如下结论:
- 通过Set/Get、cglib bean copier、mapstruct进行属性copy,其性能基本持平。
- spring bean包中的bean utils与apache common-beanutils的bean utils的copy,性能比较差。
MapStruct
简介
MapStruct 是一个对象映射库,旨在提供与手动对象拷贝相当的性能。它被设计用于支持不同的业务系统,特别是对执行速度要求高的 API 系统。
主要特性
MapStruct 提供以下特性,以简化对象映射并减少代码重复:
- 性能优化:MapStruct 致力于实现与手动对象拷贝相当的性能,适用于对执行速度敏感的 API 系统。
- 简化代码:MapStruct 提供易于使用的 API,大大减少了重复代码的编写量。
- 多样化的对象映射:MapStruct 支持各种对象映射场景,包括自动拷贝相同命名属性、隐式拷贝不同命名属性、嵌套属性和扁平属性的映射、自定义转换逻辑以及字段忽略等功能。
MapStruct 原理
MapStruct 采用两种方式进行对象映射:命令式编程和声明式编程。在 Java 中,声明式编程常常通过注解实现,注解提供了更简洁的方式来实现相同的功能。
MapStruct 利用注解处理器、字节码增强和反射等技术来实现对象映射:
- 注解处理器:利用 Java 编译器提供的注解处理工具(APT),MapStruct 可以在编译期扫描和处理注解。这使得它可以生成新的 Java 文件或修改抽象语法树(AST)和字节码文件。
- 字节码增强:字节码增强指的是在运行时修改或生成全新的字节码文件的技术。常用的支持字节码增强的框架包括 Cglib 和 Javassist。
- 反射:反射是指在运行时获取类的 Class 对象,创建类的实例,以及获取类的属性、方法和构造器等完整结构。
尽管这三种技术都可以实现对象映射,但它们的性能有所不同:
- 注解处理器技术在编译期处理注解并生成代码,因此在运行时可以提供与手写对象拷贝相当的性能。
- 字节码增强技术在运行时修改字节码,会带来一定的性能开销,但性能仍优于反射。
- 反射涉及动态类型解析、自动装箱拆箱操作、方法可见性检查和参数校验等,因此运行时性能较差。JavaDoc 建议在对性能要求较高的系统中避免频繁使用反射。
MapStruct 使用注解处理器技术实现对象映射:
- 编译时生成映射代码:MapStruct在编译时生成优化的Java代码来实现对象映射。这种静态代码生成的方式消除了运行时的反射开销,提高了转换的效率。生成的代码是类型安全的,不会引入潜在的运行时错误。因此,MapStruct能够在运行时提供快速而可靠的对象映射,实现卓越的性能表现。
- 基于注解的配置:MapStruct通过注解来配置对象之间的映射关系。开发人员只需在需要映射的字段或方法上添加相应的注解,MapStruct就能够自动为其生成转换逻辑。这种基于注解的配置方式简化了转换代码的编写,并且提高了开发效率。同时,MapStruct的注解处理器能够在编译时进行静态检查,确保映射配置的正确性,进一步提高了性能和可靠性。
- 优化的转换算法:MapStruct使用一系列优化算法来提高转换的效率。例如,它能够在转换过程中进行字段映射的缓存,避免重复的计算。此外,MapStruct还支持高级特性,如集合映射、嵌套映射和条件映射等,使得复杂对象之间的转换变得简单而高效。
总结
MapStruct之所以拥有卓越的性能表现,主要得益于其编译时生成的映射代码、基于注解的配置、优化的转换算法等关键因素。相较于手动转换和其他常见的对象映射框架,MapStruct在性能上具备显著的优势。它不仅提供了高效的转换功能,还能减少开发人员的工作量,提高开发效率。因此,MapStruct成为开发人员首选的对象映射框架之一,为复杂数据对象之间的转换提供了可靠而高效的解决方案。