MapStruct是一个代码生成库,旨在简化Java Bean之间的映射。它允许开发者在定义了映射规则后,通过注解处理器在编译时自动生成映射代码。MapStruct遵循“约定优于配置”的原则,大多数情况下,它能够智能地处理常见的映射场景,而无需开发者编写繁琐的映射逻辑。
MapStruct基于Java的JSR 269规范,该规范允许在编译期处理注解。MapStruct通过定义的注解处理器,在编译期读取映射接口,并生成相应的实现类。这个过程中,它会解析接口中声明的映射方法,并创建对应的getters和setters调用。
@Mapper
注解,声明需要映射的方法。@Mapping
注解指定属性映射规则。Mappers.getMapper()
方法获取映射器的实例,并调用映射方法。优点:
缺点:
MapStruct因其简单、高效、类型安全的特点,在Java社区中得到了广泛的应用和认可。通过减少重复的样板代码,它让开发者能够更加专注于业务逻辑的实现,提高开发效率。
<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>
<!-- IntelliJ does not pick up the processor if it is not in the dependencies.
There is already an open issue for IntelliJ see https://youtrack.jetbrains.com/issue/IDEA-150621
-->
<scope>provided</scope>
</dependency>
基本映射 使用MapStruct,可以轻松实现两个Java Bean对象之间的基本映射。只需定义一个映射器接口,并使用注解指定源类和目标类,MapStruct会在编译期生成实现类。
Entity
package com.artisan.mapstruct.entity;
import com.artisan.mapstruct.CarType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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;
/**
* @author artisan
*/
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;
}
// 根据code获取brand的方法
public static String getBrandByCode(int code) {
for (CarType carType : CarType.values()) {
if (carType.getCode() == code) {
return carType.getBrand();
}
}
return null; // 如果code不存在,则返回null或其他默认值
}
}
Mapper
package com.artisan.mapstruct.entity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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());
}
}
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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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());
}
}
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);
// 可能需要在映射过程中使用自定义逻辑。MapStruct 允许你使用 Java 表达式来实现这一点
@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());
}
}
MapStruct允许在映射器中定义自定义方法,实现复杂的映射逻辑。例如,可以定义一个方法,将源对象中的某个字段进行转换后赋值给目标对象
Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
// 出厂日期 , String类型
private String manufactureDate;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CarDto {
private String make;
private int seatCount;
private String type;
// 出厂日期 , LocalDate类型
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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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());
}
}
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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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);
}
}
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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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
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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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;
/**
* MapStruct 也可以用于更新现有对象,而不是创建新的
*/
@SpringBootTest(classes = BootBeanUtilsApplication.class)
class MapStructApplicationTests7 {
@Test
public void testUpdate() {
// 模拟存在一个car对象
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);
}
}
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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@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;
/**
* 多源映射
* <p>
* 可以从多个源对象映射到一个目标对象
*/
@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
https://github.com/eugenp/tutorials/tree/master/performance-tests