性能与实用性:为什么我不推荐使用MapStruct
本文深入探讨了MapStruct的性能和使用成本。虽然MapStruct在性能上表现出色,但其引入的复杂度和学习成本可能会超过其性能优势带来的收益。在大规模工程化或技术成为流行趋势的情况下,性能并非首要考虑因素,更重要的是实用性、灵活性和性价比。因此,我们需要根据具体的场景和需求来选择最合适的工具,而不是盲目追求性能。要注意,即使我们选择不使用某个工具,但也可以从其设计和实现中学习有价值的知识。
在日常开发中,对象赋值是常见的需求,通常我们会调用对象的 set/get 方法。当需要转换的两个对象属性大致相同,我们可能会考虑使用属性拷贝工具。许多博客分析了各种属性拷贝工具,其中许多推荐使用 MapStruct,主要因为其高效率,几乎等同于直接使用 set/get 方法。但是我个人并不推荐使用 MapStruct,本文将详细解释原因。
MapStruct 是什么
MapStruct 是一个代码生成器,它简化了 Java 应用程序中对象之间的映射/转换。以下是一个简单的使用示例:
假设有以下几个类:
public class Customer {
private String name;
private Address address;
// getters and setters
}
public class Address {
private String street;
private String city;
// getters and setters
}
public class CustomerDTO {
private String name;
private String street;
private String city;
// getters and setters
}
我们想要将 Customer
对象转换为 CustomerDTO
对象,其中 Address
的属性需要展平到 CustomerDTO
中。同时,我们想要将 Customer
的 name
转换为大写。
我们可以创建一个 CustomerMapper
接口,如下所示:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);
@Mappings({
@Mapping(source = "address.street", target = "street"),
@Mapping(source = "address.city", target = "city"),
@Mapping(source = "name", target = "name", qualifiedByName = "toUpperCase")
})
CustomerDTO customerToCustomerDTO(Customer customer);
@Named("toUpperCase")
default String toUpperCase(String name) {
return name.toUpperCase();
}
}
在这个接口中,我们使用 @Mappings
和 @Mapping
注解来定义复杂的映射规则。我们还定义了一个 toUpperCase
方法来实现自定义的类型转换。
然后,我们就可以在代码中使用 CustomerMapper
了:
Customer customer = new Customer();
customer.setName("John Doe");
Address address = new Address();
address.setStreet("123 Main St");
address.setCity("Springfield");
customer.setAddress(address);
CustomerDTO customerDTO = CustomerMapper.INSTANCE.customerToCustomerDTO(customer);
MapStruct 原理分析
MapStruct 的原理其实比较简单,就是基于 APT(Annotation Processing Tool)。
APT是Java的一个工具,它可以在编译时扫描和处理注解。MapStruct就是使用APT来处理@Mapper
注解,并生成相应的映射代码。
当你在接口或抽象类上使用@Mapper
注解,并且编译你的项目时,APT会调用MapStruct的注解处理器。然后,MapStruct的注解处理器会分析这个接口或抽象类,找出所有的映射方法,并为每个映射方法生成实现代码。
这种在编译时生成代码的方式,使得MapStruct的运行效率非常高,因为所有的映射逻辑都已经在编译时确定,运行时不需要进行任何反射或动态代理。
比如在上文的例子中,MapStruct 生成了一个 mapper 接口的实现类:
为什么我不推荐使用 MapStruct
下图是在网上找到的对十一种属性转换操作的性能时间对比:
get_set | Spring BeanUtils | MapStruct | |
---|---|---|---|
一百次 | 0 | 4 | 0 |
一千次 | 0 | 6 | 0 |
一万次 | 1 | 17 | 1 |
十万次 | 4 | 68 | 3 |
一百万次 | 8 | 226 | 15 |
从上述数据可以看出:
- MapStruct的性能表现出色,与直接使用set/get方法相比几乎没有差距。
- Spring的BeanUtils虽然稍慢,但这种微小的差距对系统运行影响微乎其微。
然而,正是这种微不足道的性能差异,导致许多人选择使用MapStruct。尽管MapStruct在业务代码中的转换非常简单,但它需要新增一个Mapper接口,而且接口中的逻辑并不简单,对于不熟悉MapStruct的开发者来说,这增加了使用成本。大多数Java Web开发都会使用Spring,而为了使用MapStruct,我们放弃了无需添加任何外部依赖就可以直接使用的Spring BeanUtils,反而增加了MapStruct的依赖,使项目变得更加庞大。
MapStruct的高速原理类似于我们为了解决MySQL查询速度慢的问题而添加缓存,这就需要考虑缓存预热、缓存与数据库的一致性等问题。为了解决一个可能并不那么重要的性能问题,我们反而使架构变得更复杂。
这让我想起了计算机编程领域的一句经典名言:“过早优化是万恶之源”:
这句话的含义有两层:
- 关注点不当:在软件开发的早期,更重要的是关注功能的实现和整体架构,而不是过度关注性能优化。过早地陷入优化细节可能会使开发者失去对整体结构和设计的关注,从而影响最终的软件质量。
- 资源浪费:早期的优化往往是基于假设和预测进行的,这可能导致资源在不必要的地方被浪费。在软件的实际使用情况变得明确之前,进行过早的优化可能会导致资源被用在了不必要的地方,而不是真正需要优化的地方。
这与本文的观点相吻合,我们为了一个“没有很大意义”的性能优化(关注点不当),引入了一个依赖,而且增加了代码的复杂度和其他同学的学习成本,甚至于可能引出其他的坑,比如经典的与Lombok冲突问题,这又需要我们花费时间去处理(资源浪费)。
而提到性能,我又想起了一位业界大佬说的一句话“性能在大规模工程化的时候,或者技术成为流行趋势的时候,永远不是第一顺位的选择”。
拿编程语言来说,Java/Golang 一般认为没有Rust/C++快,如果想更快可以直接ASM。但是实际上90%的业务系统,特别是大规模业务系统基本上都是Java来实现的。大型银行核心业务之前的技术栈都是COBOL或C,目前都在转Java了。证券保险类的业务也是这个大趋势。为什么这样呢,一般情况高性能的东西会具有更大的复杂度,一个大型系统如果用汇编语言来写,复杂度和协作程度要严重很多倍,生产率就下降了。
拿出门旅行来说,飞机比较快,战斗机更快,目前还是火车/大巴/自驾为主要方式。开车比电动车和自行车快,但是我在小区附近活动的时候,自行车还是最佳方式。为啥,足够用,足够灵活,性价比高,这往往是比性能更重要的指标。
还是回到那句话,“脱离场景谈性能都是耍流氓”。
要注意的是,我个人虽然不推荐使用 MapStruct,但并不代表我不关注它的原理,反而这个框架的实现原理是值得我们学习的。
总结
本文深入探讨了MapStruct的性能和使用成本。虽然MapStruct在性能上表现出色,但其引入的复杂度和学习成本可能会超过其性能优势带来的收益。在大规模工程化或技术成为流行趋势的情况下,性能并非首要考虑因素,更重要的是实用性、灵活性和性价比。因此,我们需要根据具体的场景和需求来选择最合适的工具,而不是盲目追求性能。要注意,即使我们选择不使用某个工具,但也可以从其设计和实现中学习有价值的知识。
References
- https://blog.csdn.net/qq_41692766/article/details/122491333
- https://www.zhihu.com/question/270355472/answer/3238160454
欢迎关注公众号:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)