文章目录
- 入门必看
- 概述
- MapStruct的关键特性
- MapStruct的工作原理
- 如何使用MapStruct
- MapStruct的优缺点
- Code
- POM
- Test Case 1 : 基本映射
- Test Case 2 : 复杂类型映射
- Test Case 3 : 使用Java表达式
- Test Case 4 : 使用自定义方法
- Test Case 5 : 集合映射
- Test Case 6 : 使用依赖注入
- Test Case 7 : 更新现有对象
- Test Case 8 : 多源映射
- Performance of Java Mapping Frameworks

入门必看

概述
MapStruct是一个代码生成库,旨在简化Java Bean之间的映射。它允许开发者在定义了映射规则后,通过注解处理器在编译时自动生成映射代码。MapStruct遵循“约定优于配置”的原则,大多数情况下,它能够智能地处理常见的映射场景,而无需开发者编写繁琐的映射逻辑。
MapStruct的关键特性
- 类型安全:MapStruct在编译时检查映射规则,确保源对象和目标对象之间的属性映射是类型安全的。这减少了运行时因类型转换错误而导致的问题。
- 性能:生成的映射代码使用简单的getters和setters,避免了使用反射,因此在运行时可以提供更好的性能。
- 易于理解和使用:MapStruct生成的代码简单易懂,开发者可以轻松阅读和理解映射逻辑。
- 自定义映射:MapStruct允许开发者定义复杂的映射规则,包括深拷贝和自定义转换函数。
- 错误提前暴露:编译时就能发现潜在的错误,如映射不完整或映射方法不正确,这样可以提前修复问题,避免在运行时出现故障。
MapStruct的工作原理
MapStruct基于Java的JSR 269规范,该规范允许在编译期处理注解。MapStruct通过定义的注解处理器,在编译期读取映射接口,并生成相应的实现类。这个过程中,它会解析接口中声明的映射方法,并创建对应的getters和setters调用。
如何使用MapStruct
- 添加依赖:首先,在项目的构建配置文件中(如Maven或Gradle)添加MapStruct的依赖。
- 定义映射接口:定义一个接口,使用
@Mapper
注解,声明需要映射的方法。 - 编写映射规则:在映射接口中,使用
@Mapping
注解指定属性映射规则。 - 编译代码:编译项目时,MapStruct注解处理器会根据定义的映射规则生成实现类。
- 使用映射器:在代码中,通过
Mappers.getMapper()
方法获取映射器的实例,并调用映射方法。
MapStruct的优缺点
优点:
- 提供了类型安全的映射,减少了运行时错误。
- 生成的代码执行效率高,因为避免了使用反射。
- 可以实现深拷贝,保持对象之间的独立性。
- 增量式开发友好,可以单独编译和测试每个映射。
- 易于理解,减少了编写和维护大量样板代码的需要。
缺点:
- 必须定义接口或抽象类,可能在一定程度上增加了代码的复杂性。
- 对于复杂的映射逻辑,可能需要编写自定义的映射函数。
- 如果项目中对性能要求极高,可能需要考虑手动优化生成的代码。
MapStruct因其简单、高效、类型安全的特点,在Java社区中得到了广泛的应用和认可。通过减少重复的样板代码,它让开发者能够更加专注于业务逻辑的实现,提高开发效率。
Code

POM
| <dependency> |
| <groupId>org.mapstruct</groupId> |
| <artifactId>mapstruct</artifactId> |
| <version>${org.mapstruct.version}</version> |
| </dependency> |
| |
| <dependency> |
| <groupId>org.mapstruct</groupId> |
| <artifactId>mapstruct-processor</artifactId> |
| <version>${org.mapstruct.version}</version> |
| |
| |
| |
| <scope>provided</scope> |
| </dependency> |
Test Case 1 : 基本映射
基本映射 使用MapStruct,可以轻松实现两个Java Bean对象之间的基本映射。只需定义一个映射器接口,并使用注解指定源类和目标类,MapStruct会在编译期生成实现类。
Entity
| package com.artisan.mapstruct.entity; |
| |
| import com.artisan.mapstruct.CarType; |
| import lombok.AllArgsConstructor; |
| import lombok.Data; |
| import lombok.NoArgsConstructor; |
| |
| |
| |
| |
| |
| |
| |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private int numberOfSeats; |
| private CarType type; |
| |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int seatCount; |
| private String type; |
| } |
| package com.artisan.mapstruct; |
| |
| |
| |
| |
| |
| public enum CarType { |
| BMW(1, "BMW"), |
| FLL(2, "FLL"); |
| |
| |
| private int code; |
| private String brand; |
| |
| CarType(int code, String brand) { |
| this.code = code; |
| this.brand = brand; |
| } |
| |
| public int getCode() { |
| return code; |
| } |
| |
| public String getBrand() { |
| return brand; |
| } |
| |
| |
| public static String getBrandByCode(int code) { |
| for (CarType carType : CarType.values()) { |
| if (carType.getCode() == code) { |
| return carType.getBrand(); |
| } |
| } |
| return null; |
| } |
| } |
Mapper
| package com.artisan.mapstruct.entity; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.Mapping; |
| import org.mapstruct.factory.Mappers; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| @Mapping(source = "numberOfSeats", target = "seatCount") |
| CarDto carToCarDto(Car car); |
| } |
单元测试
| package com.artisan.mapstruct; |
| |
| import com.artisan.bootbeanutils.BootBeanUtilsApplication; |
| import com.artisan.mapstruct.entity.Car; |
| import com.artisan.mapstruct.entity.CarDto; |
| import com.artisan.mapstruct.entity.CarMapper; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| import org.springframework.boot.test.context.SpringBootTest; |
| |
| |
| |
| |
| @SpringBootTest(classes = BootBeanUtilsApplication.class) |
| class MapStructApplicationTests1 { |
| |
| |
| @Test |
| public void testBasicTypeConvert() { |
| Car car = new Car("artisan", 7, CarType.BMW); |
| CarDto cardto = CarMapper.INSTANCE.carToCarDto(car); |
| |
| System.out.println(car); |
| System.out.println(cardto); |
| |
| Assertions.assertEquals(car.getNumberOfSeats(), cardto.getSeatCount()); |
| } |
| |
| |
| } |

Test Case 2 : 复杂类型映射
Entity
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private int numberOfSeats; |
| private CarType type; |
| |
| private AnotherPojo anotherPojo; |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class AnotherPojo { |
| |
| private String pa; |
| private long pb; |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int seatCount; |
| private String type; |
Mapper
| package com.artisan.mapstruct.entity2; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.Mapping; |
| import org.mapstruct.factory.Mappers; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| @Mapping(source = "anotherPojo.pa", target = "pa") |
| @Mapping(source = "anotherPojo.pb", target = "pb") |
| CarDto carToCarDto(Car car); |
| } |
单元测试
| package com.artisan.mapstruct; |
| |
| import com.artisan.bootbeanutils.BootBeanUtilsApplication; |
| import com.artisan.mapstruct.entity2.Car; |
| import com.artisan.mapstruct.entity2.CarDto; |
| import com.artisan.mapstruct.entity2.CarMapper; |
| import com.artisan.mapstruct.entity2.AnotherPojo; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| import org.springframework.boot.test.context.SpringBootTest; |
| |
| |
| |
| |
| @SpringBootTest(classes = BootBeanUtilsApplication.class) |
| class MapStructApplicationTests2 { |
| |
| @Test |
| public void testComplexConvert() { |
| Car car = new Car("artisan", 7, CarType.BMW ,new AnotherPojo("paValue",66L)); |
| CarDto cardto = CarMapper.INSTANCE.carToCarDto(car); |
| |
| System.out.println(car); |
| System.out.println(cardto); |
| |
| Assertions.assertEquals(car.getAnotherPojo().getPa() , cardto.getPa()); |
| Assertions.assertEquals(car.getAnotherPojo().getPb() , cardto.getPb()); |
| |
| } |
| |
| |
| } |

Test Case 3 : 使用Java表达式
MapStruct支持在映射器中使用表达式。通过编写Lambda表达式或方法引用,可以实现复杂的映射逻辑
Entity
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private String brand; |
| private int numberOfSeats; |
| private CarType type; |
| |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int seatCount; |
| private String type; |
| private String fullInfo; |
| } |
Mapper
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| |
| @Mapping(expression = "java(car.getMake() + ' ' + car.getBrand())", target = "fullInfo") |
| CarDto carToCarDto(Car car); |
| } |
单元测试
| package com.artisan.mapstruct; |
| |
| import com.artisan.bootbeanutils.BootBeanUtilsApplication; |
| import com.artisan.mapstruct.entity3.Car; |
| import com.artisan.mapstruct.entity3.CarDto; |
| import com.artisan.mapstruct.entity3.CarMapper; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| import org.springframework.boot.test.context.SpringBootTest; |
| |
| |
| |
| |
| @SpringBootTest(classes = BootBeanUtilsApplication.class) |
| class MapStructApplicationTests3 { |
| |
| |
| @Test |
| public void testExpressConvert() { |
| Car car = new Car("artisan", "BMW", 7, CarType.BMW); |
| CarDto cardto = CarMapper.INSTANCE.carToCarDto(car); |
| |
| System.out.println(car); |
| System.out.println(cardto); |
| |
| Assertions.assertEquals(car.getMake() + ' ' + car.getBrand(), cardto.getFullInfo()); |
| |
| } |
| |
| |
| } |

Test Case 4 : 使用自定义方法
MapStruct允许在映射器中定义自定义方法,实现复杂的映射逻辑。例如,可以定义一个方法,将源对象中的某个字段进行转换后赋值给目标对象
Entity
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private int numberOfSeats; |
| private CarType type; |
| |
| |
| private String manufactureDate; |
| |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int seatCount; |
| private String type; |
| |
| |
| |
| private LocalDate manufactureDate2; |
| } |
Mapper
| package com.artisan.mapstruct.entity4; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.Mapping; |
| import org.mapstruct.Named; |
| import org.mapstruct.factory.Mappers; |
| |
| import java.time.LocalDate; |
| import java.time.format.DateTimeFormatter; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| @Mapping(source = "manufactureDate", target = "manufactureDate2", qualifiedByName = "stringToLocalDate") |
| CarDto carToCarDto(Car car); |
| |
| |
| @Named("stringToLocalDate") |
| default LocalDate stringToLocalDate(String date) { |
| return LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")); |
| } |
| |
| } |
这样也可以
| package com.artisan.mapstruct.entity4; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.Mapping; |
| import org.mapstruct.Named; |
| import org.mapstruct.factory.Mappers; |
| |
| import java.time.LocalDate; |
| import java.time.format.DateTimeFormatter; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| @Mapping(source = "manufactureDate", target = "manufactureDate2") |
| CarDto carToCarDto(Car car); |
| |
| |
| default LocalDate stringToLocalDate(String date) { |
| return LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")); |
| } |
| |
| } |
可以用Java Interface的default接口实现
单元测试
| package com.artisan.mapstruct; |
| |
| import com.artisan.bootbeanutils.BootBeanUtilsApplication; |
| import com.artisan.mapstruct.entity4.Car; |
| import com.artisan.mapstruct.entity4.CarDto; |
| import com.artisan.mapstruct.entity4.CarMapper; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| import org.springframework.boot.test.context.SpringBootTest; |
| |
| |
| |
| |
| |
| |
| @SpringBootTest(classes = BootBeanUtilsApplication.class) |
| class MapStructApplicationTests4 { |
| |
| |
| @Test |
| public void testCustomConvert() { |
| Car car = new Car("artisan", 7, CarType.BMW, "2099-12-12"); |
| CarDto cardto = CarMapper.INSTANCE.carToCarDto(car); |
| |
| System.out.println(car); |
| System.out.println(cardto); |
| |
| Assertions.assertEquals(car.getManufactureDate(), cardto.getManufactureDate2().toString()); |
| |
| } |
| |
| |
| } |

Test Case 5 : 集合映射
Entity
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private int numberOfSeats; |
| private CarType type; |
| |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int numberOfSeats; |
| private String type; |
| } |
Mapper
| package com.artisan.mapstruct.entity5; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.factory.Mappers; |
| |
| import java.util.List; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| List<CarDto> carsToCarDtos(List<Car> cars); |
| } |
单元测试
| package com.artisan.mapstruct; |
| |
| import com.artisan.bootbeanutils.BootBeanUtilsApplication; |
| import com.artisan.mapstruct.entity5.Car; |
| import com.artisan.mapstruct.entity5.CarDto; |
| import com.artisan.mapstruct.entity5.CarMapper; |
| import org.junit.jupiter.api.Test; |
| import org.springframework.boot.test.context.SpringBootTest; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| |
| |
| |
| @SpringBootTest(classes = BootBeanUtilsApplication.class) |
| class MapStructApplicationTests5 { |
| |
| |
| @Test |
| public void testCollectionfConvert() { |
| Car car = new Car("artisan", 7, CarType.BMW); |
| Car car2 = new Car("artisan2", 9, CarType.FLL); |
| |
| List carList = new ArrayList(); |
| carList.add(car); |
| carList.add(car2); |
| |
| List<CarDto> cardtos = CarMapper.INSTANCE.carsToCarDtos(carList); |
| cardtos.stream().forEach(System.out::println); |
| } |
| } |

Test Case 6 : 使用依赖注入
MapStruct支持依赖注入,可以在映射器中使用第三方库或框架。这方便了在对象映射过程中使用其他组件.

Entity
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private int numberOfSeats; |
| private CarType type; |
| |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int seatCount; |
| private String type; |
| } |
Mapper
componentModel = MappingConstants.ComponentModel.SPRING
| package com.artisan.bootbeanutils.controller.ms; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.Mapping; |
| import org.mapstruct.MappingConstants; |
| import org.mapstruct.factory.Mappers; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) |
| public interface CarMapper { |
| |
| @Mapping(source = "numberOfSeats", target = "seatCount") |
| CarDto carToCarDto(Car car); |
| } |
单元测试
| package com.artisan.bootbeanutils.controller; |
| |
| import com.artisan.bootbeanutils.controller.ms.Car; |
| import com.artisan.bootbeanutils.controller.ms.CarDto; |
| import com.artisan.bootbeanutils.controller.ms.CarMapper; |
| import com.artisan.bootbeanutils.controller.ms.CarType; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.web.bind.annotation.GetMapping; |
| import org.springframework.web.bind.annotation.RestController; |
| |
| |
| |
| |
| |
| |
| |
| @RestController |
| public class TestController { |
| |
| @Autowired |
| private CarMapper carMapper; |
| |
| @GetMapping("/test") |
| public String test() { |
| CarDto carDto = carMapper.carToCarDto(new Car("artisan", 8, CarType.BMW)); |
| return carDto.toString(); |
| } |
| } |
自动注入 CarMapper

Test Case 7 : 更新现有对象
Entity
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private int numberOfSeats; |
| private CarType type; |
| |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int seatCount; |
| private String type; |
| } |
Mapper
| package com.artisan.mapstruct.entity7; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.Mapping; |
| import org.mapstruct.MappingTarget; |
| import org.mapstruct.factory.Mappers; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| |
| @Mapping(source = "seatCount", target = "numberOfSeats") |
| void updateCarFromDTO(CarDto personDto, @MappingTarget Car car); |
| } |
单元测试
| package com.artisan.mapstruct; |
| |
| import com.artisan.bootbeanutils.BootBeanUtilsApplication; |
| import com.artisan.mapstruct.entity7.Car; |
| import com.artisan.mapstruct.entity7.CarDto; |
| import com.artisan.mapstruct.entity7.CarMapper; |
| import org.junit.jupiter.api.Test; |
| import org.springframework.boot.test.context.SpringBootTest; |
| |
| |
| |
| |
| @SpringBootTest(classes = BootBeanUtilsApplication.class) |
| class MapStructApplicationTests7 { |
| |
| |
| @Test |
| public void testUpdate() { |
| |
| |
| Car car = new Car("artisan", 9, CarType.BMW); |
| |
| |
| CarDto carDto = new CarDto("artisanDto", 100, CarType.FLL.getBrand()); |
| |
| |
| CarMapper.INSTANCE.updateCarFromDTO(carDto, car); |
| |
| |
| System.out.println(carDto); |
| System.out.println(car); |
| |
| } |
| |
| |
| } |

Test Case 8 : 多源映射
MapStruct支持多态映射。通过定义一个映射器接口,可以实现多个子类对象映射到一个父类对象。这在处理继承关系复杂的对象映射时非常有用
Entity
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Car { |
| |
| private String make; |
| private int numberOfSeats; |
| private CarType type; |
| |
| } |
| @Data |
| @NoArgsConstructor |
| @AllArgsConstructor |
| public class AnotherPojo { |
| |
| private String pa; |
| } |
| package com.artisan.mapstruct.entity8; |
| |
| import lombok.AllArgsConstructor; |
| import lombok.Data; |
| import lombok.NoArgsConstructor; |
| |
| |
| |
| |
| |
| |
| |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class CarDto { |
| |
| private String make; |
| private int seatCount; |
| private String type; |
| private String pa; |
| } |
Mapper
| package com.artisan.mapstruct.entity8; |
| |
| import org.mapstruct.Mapper; |
| import org.mapstruct.Mapping; |
| import org.mapstruct.factory.Mappers; |
| |
| |
| |
| |
| |
| |
| |
| @Mapper |
| public interface CarMapper { |
| |
| CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); |
| |
| @Mapping(source = "car.numberOfSeats", target = "seatCount") |
| @Mapping(source = "anotherPojo.pa", target = "pa") |
| CarDto carToCarDto(Car car, AnotherPojo anotherPojo); |
| } |
单元测试
| package com.artisan.mapstruct; |
| |
| import com.artisan.bootbeanutils.BootBeanUtilsApplication; |
| import com.artisan.mapstruct.entity8.AnotherPojo; |
| import com.artisan.mapstruct.entity8.Car; |
| import com.artisan.mapstruct.entity8.CarDto; |
| import com.artisan.mapstruct.entity8.CarMapper; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| import org.springframework.boot.test.context.SpringBootTest; |
| |
| |
| |
| |
| |
| |
| @SpringBootTest(classes = BootBeanUtilsApplication.class) |
| class MapStructApplicationTests8 { |
| |
| |
| @Test |
| public void MultiSourceConvert() { |
| |
| Car car = new Car("artisan", 7, CarType.BMW); |
| AnotherPojo anotherPojo = new AnotherPojo("paValue"); |
| |
| CarDto cardto = CarMapper.INSTANCE.carToCarDto(car, anotherPojo); |
| |
| System.out.println(car); |
| System.out.println(cardto); |
| |
| Assertions.assertEquals(car.getNumberOfSeats(), cardto.getSeatCount()); |
| Assertions.assertEquals(anotherPojo.getPa(), cardto.getPa()); |
| |
| } |
| |
| |
| } |

当然了你也可以点击这里: Quick Guide to MapStruct 还有些例子没有覆盖到的,进去瞅一瞅
Performance of Java Mapping Frameworks
Performance of Java Mapping Frameworks
https://github.com/eugenp/tutorials/tree/master/performance-tests