在 Spring Boot 中,常见的类转换(对象映射)工具如下:
1. ModelMapper
- •
特点:自动根据属性名匹配转换,配置灵活。
- •
依赖:
xml
复制
<dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>3.2.0</version> </dependency>
2. MapStruct
- •
特点:编译时生成映射代码,性能高,无反射。
- •
依赖(需结合注解处理器):
xml
复制
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency>
3. Spring BeanUtils(轻量复制)
- •
特点:Spring 自带工具,仅支持同名属性浅拷贝。
- •
示例:
java
下载
复制
运行
BeanUtils.copyProperties(source, target);
4. Apache BeanUtils(不推荐)
- •
注意:性能较差,一般推荐使用 Spring BeanUtils 替代。
5. Dozer(已停止维护)
- •
特点:老项目中较流行,但当前版本已不再活跃。
6. Orika
- •
特点:字节码生成映射,性能接近 MapStruct。
- •
依赖:
xml
复制
<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.4</version> </dependency>
📌 选择建议:
- 1.
追求性能 → 选 MapStruct。
- 2.
快速开发 → 选 ModelMapper 或 Orika。
- 3.
简单属性复制 → 直接用 Spring BeanUtils。
根据具体场景(性能要求、复杂度、反射容忍度)选择合适的工具即可。
“快速开发”在这里指的是以最短的学习成本、最少的代码量、最快的速度实现对象映射功能,让开发者能更专注于核心业务逻辑,而不是花费大量时间在映射配置上。
具体来说,选择 ModelMapper 或 Orika 能带来以下“快速”优势:
🚀 为何它们适合“快速开发”?
- 1.
开箱即用,配置简单
- •
ModelMapper:大多数情况下,只需声明一个Bean(
new ModelMapper())即可使用,它会自动尝试匹配属性名。 - •
Orika:也只需简单的初始化,就能处理大部分常规映射。
- •
- 2.
“约定优于配置”
- •
当源对象(如
UserEntity)和目标对象(如UserDTO)的属性名称和类型相同或兼容时,无需任何额外配置或代码,工具会自动完成映射。 - •
示例:
userEntity.getName()会自动复制到userDTO.setName()。
- •
- 3.
灵活的注解或API配置
- •
当属性名不一致时(如
sourceCode->targetCode),可以通过简单的API调用或轻量注解来配置,无需编写完整的转换代码。
- •
⚖️ 与“高性能选择”的对比
为了更直观,这里与 MapStruct 进行对比:
|
特性 |
快速开发工具 (ModelMapper/Orika) |
高性能工具 (MapStruct) |
对“快速开发”的影响 |
|---|---|---|---|
|
原理 |
基于反射在运行时动态分析并映射属性。 |
编译时生成Java映射代码,无反射。 |
反射让使用更简单,但牺牲一些性能。 |
|
配置/代码 |
声明式,配置少,或零配置。 |
需要定义Mapper接口并声明方法。 |
省去了定义接口和方法的步骤,上手更快。 |
|
启动速度 |
首次运行时需构建元数据,稍慢。 |
编译期完成,启动无额外开销。 |
对开发体验影响微乎其微。 |
|
错误发现时机 |
配置错误或映射失败可能在运行时才发现。 |
编译时就会检查类型、缺失属性等问题。 |
这是“快速”的主要代价,可能将问题隐藏到运行时。 |
📊 总结与建议
|
场景 |
推荐工具 |
原因 |
|---|---|---|
|
原型设计、内部工具、管理后台 |
ModelMapper / Orika |
业务变化快,追求开发效率,性能非关键瓶颈。 |
|
复杂的、高性能的核心业务(如交易、网关) |
MapStruct |
需要最佳性能、编译时安全和长期可维护性。 |
|
简单的属性复制(如PO到VO) |
Spring BeanUtils |
无复杂逻辑,Spring自带,无引入额外依赖的成本。 |
简单来说:如果你希望现在就写更少的代码、让项目尽快跑起来,并且可以接受微小的性能损失和潜在的运行时映射风险,那么 ModelMapper 或 Orika 就是“快速开发”的绝佳选择。当项目发展到对性能或稳定性有更高要求时,再考虑迁移到像MapStruct这样的编译期工具。
ModelMapper 与 MapStruct 使用示例对比
一、项目准备
假设我们有以下实体类和数据对象:
java
下载
复制
运行
// 实体类
public class UserEntity {
private Long id;
private String username;
private String email;
private LocalDate birthDate;
private Address address;
// getters/setters 省略...
}
// 地址实体
public class Address {
private String city;
private String street;
// getters/setters 省略...
}
// DTO
public class UserDTO {
private Long id;
private String userName; // 注意:与实体属性名不同
private String emailAddress; // 注意:与实体属性名不同
private int age; // 需要计算
private String city; // 嵌套属性映射
// getters/setters 省略...
}
二、ModelMapper 示例
1. 添加依赖
xml
复制
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.0</version>
</dependency>
2. 基本使用
java
下载
复制
运行
import org.modelmapper.ModelMapper;
import org.modelmapper.Converter;
import org.modelmapper.PropertyMap;
import org.modelmapper.TypeMap;
public class ModelMapperExample {
public static void main(String[] args) {
// 1. 创建 ModelMapper 实例
ModelMapper modelMapper = new ModelMapper();
// 2. 基本映射(属性名相同时自动映射)
UserEntity entity = new UserEntity();
entity.setId(1L);
entity.setUsername("张三");
entity.setEmail("zhangsan@example.com");
entity.setBirthDate(LocalDate.of(1990, 1, 1));
entity.setAddress(new Address("北京", "长安街"));
// 3. 简单转换
UserDTO dto = modelMapper.map(entity, UserDTO.class);
System.out.println(dto.getUserName()); // 自动匹配 username -> userName
}
}
3. 高级配置(处理复杂映射)
java
下载
复制
运行
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
// 配置全局映射
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
.setSourceNamingConvention(NamingConventions.JAVABEANS_ACCESSOR)
.setDestinationNamingConvention(NamingConventions.JAVABEANS_MUTATOR);
// 自定义类型映射
TypeMap<UserEntity, UserDTO> typeMap = modelMapper.createTypeMap(UserEntity.class, UserDTO.class);
// 1. 属性名不匹配时的映射
typeMap.addMapping(UserEntity::getUsername, UserDTO::setUserName);
typeMap.addMapping(UserEntity::getEmail, UserDTO::setEmailAddress);
// 2. 嵌套属性映射
typeMap.addMapping(src -> src.getAddress().getCity(), UserDTO::setCity);
// 3. 自定义转换器(计算年龄)
Converter<LocalDate, Integer> ageConverter = ctx ->
Period.between(ctx.getSource(), LocalDate.now()).getYears();
typeMap.addMappings(mapper -> mapper.using(ageConverter)
.map(UserEntity::getBirthDate, UserDTO::setAge));
// 4. 跳过某些属性
typeMap.addMappings(mapper -> mapper.skip(UserDTO::setId));
return modelMapper;
}
}
4. Spring Boot 中使用
java
下载
复制
运行
@Service
public class UserService {
@Autowired
private ModelMapper modelMapper;
public UserDTO convertToDTO(UserEntity entity) {
return modelMapper.map(entity, UserDTO.class);
}
public UserEntity convertToEntity(UserDTO dto) {
return modelMapper.map(dto, UserEntity.class);
}
// 批量转换
public List<UserDTO> convertToListDTO(List<UserEntity> entities) {
return entities.stream()
.map(entity -> modelMapper.map(entity, UserDTO.class))
.collect(Collectors.toList());
}
}
三、MapStruct 示例
1. 添加依赖
xml
复制
<properties>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
2. 创建 Mapper 接口
java
下载
复制
运行
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring") // 与 Spring 集成
public interface UserMapper {
// 获取实例(非Spring方式)
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// 1. 基本映射 - 属性名不同时使用 @Mapping
@Mapping(source = "username", target = "userName")
@Mapping(source = "email", target = "emailAddress")
@Mapping(source = "address.city", target = "city")
@Mapping(target = "age", expression = "java(calculateAge(entity.getBirthDate()))")
UserDTO toDTO(UserEntity entity);
// 2. 反向映射
@InheritInverseConfiguration
@Mapping(target = "id", ignore = true) // 忽略ID
UserEntity toEntity(UserDTO dto);
// 3. 更新已有对象
@Mapping(target = "id", ignore = true)
void updateEntity(UserDTO dto, @MappingTarget UserEntity entity);
// 4. 批量转换
List<UserDTO> toDTOList(List<UserEntity> entities);
// 5. 自定义方法(计算年龄)
default int calculateAge(LocalDate birthDate) {
if (birthDate == null) {
return 0;
}
return Period.between(birthDate, LocalDate.now()).getYears();
}
// 6. 多参数映射
@Mapping(source = "user.id", target = "id")
@Mapping(source = "user.username", target = "userName")
@Mapping(source = "address.city", target = "city")
UserDTO toDTOWithParams(UserEntity user, Address address);
}
3. 更复杂的配置示例
java
下载
复制
运行
@Mapper(componentModel = "spring",
uses = {DateMapper.class}, // 使用其他Mapper
imports = {LocalDate.class}) // 导入类
public interface AdvancedUserMapper {
// 条件映射
@Mapping(source = "username", target = "userName",
conditionExpression = "java(entity.getUsername() != null && !entity.getUsername().isEmpty())")
@Mapping(source = "birthDate", target = "birthDateString",
dateFormat = "yyyy-MM-dd")
@Mapping(source = "address", target = "fullAddress",
qualifiedByName = "addressToString")
UserDTO toDTO(UserEntity entity);
// 限定符:当有多个相同类型映射时使用
@Named("addressToString")
default String addressToString(Address address) {
return address.getCity() + ", " + address.getStreet();
}
}
4. 日期转换器
java
下载
复制
运行
@Mapper(componentModel = "spring")
public abstract class DateMapper {
public String asString(LocalDate date) {
return date != null ? date.toString() : null;
}
public LocalDate asLocalDate(String date) {
return date != null ? LocalDate.parse(date) : null;
}
}
5. Spring Boot 中使用
java
下载
复制
运行
@Service
public class UserService {
@Autowired
private UserMapper userMapper; // 自动注入MapStruct生成的实现类
public UserDTO getUserDTO(UserEntity entity) {
// 直接调用接口方法
return userMapper.toDTO(entity);
}
public List<UserDTO> getAllUsers(List<UserEntity> entities) {
return userMapper.toDTOList(entities);
}
public void updateUser(UserDTO dto, UserEntity entity) {
// 更新现有实体
userMapper.updateEntity(dto, entity);
}
}
6. 测试验证
java
下载
复制
运行
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void testMapping() {
UserEntity entity = new UserEntity();
entity.setId(1L);
entity.setUsername("张三");
entity.setEmail("zhangsan@example.com");
entity.setBirthDate(LocalDate.of(1990, 1, 1));
entity.setAddress(new Address("北京", "长安街"));
UserDTO dto = userMapper.toDTO(entity);
// 验证映射结果
assertEquals("张三", dto.getUserName());
assertEquals("zhangsan@example.com", dto.getEmailAddress());
assertEquals("北京", dto.getCity());
assertTrue(dto.getAge() > 0);
}
}
四、对比总结
|
对比项 |
ModelMapper |
MapStruct |
|---|---|---|
|
映射方式 |
运行时反射 |
编译时生成代码 |
|
性能 |
中(反射开销) |
高(无反射) |
|
代码量 |
少(自动配置) |
中(需定义接口) |
|
错误检查 |
运行时 |
编译时 |
|
与IDE集成 |
一般 |
好(生成真实类) |
|
调试便利性 |
困难(反射) |
容易(可查看生成的Java文件) |
|
复杂映射支持 |
强大(多种转换器) |
强大(注解丰富) |
|
学习成本 |
低 |
中 |
生成的文件位置(MapStruct):
MapStruct 在编译时会在 target/generated-sources/annotations/目录下生成实现类:
复制
target/generated-sources/annotations/
└── com/example/mapper/
└── UserMapperImpl.java ← MapStruct 自动生成的实现
五、选择建议
- 1.
选择 ModelMapper 当:
- •
需要快速原型开发
- •
项目简单,映射关系不复杂
- •
可以接受运行时的小性能损失
- •
不想写太多映射代码
- •
- 2.
选择 MapStruct 当:
- •
性能是关键因素
- •
需要编译时类型安全检查
- •
项目较大,需要长期维护
- •
需要更好的调试体验
- •
愿意为类型安全写一些接口代码
- •
两种工具都可以在Spring Boot中通过@Bean或@Mapper(componentModel = "spring")轻松集成,选择哪种主要取决于你的具体需求和对性能/安全性的权衡。
工具:ModelMapper 与 MapStruct 使用示例对比&spm=1001.2101.3001.5002&articleId=159629559&d=1&t=3&u=c00377deee1242a1b44982558359e416)
4274

被折叠的 条评论
为什么被折叠?



