1. 概述
MapStruct 是一个方便用于生成类型安全且高效的 Java 对象映射器的Java注解处理器。
在这个教程中,我们将专门学习如何使用 Mapstruct 映射器处理继承自基类的 Java 对象bean。
我们将讨论三种方法:第一种是实例检查,第二种是使用访问者模式,最后是推荐的使用Mapstruct 1.5.0引入的@SubclassMapping
注解的方法。
2. Maven 依赖
请在我们的Maven pom.xml
文件中添加以下Mapstruct依赖:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.0.Beta1</version>
</dependency>
3. 问题理解
Mapstruct默认情况下,并不足够智能,无法为所有继承自基类或接口的类生成映射器。Mapstruct也不支持在运行时识别对象层次结构下的提供的实例。
3.1. 创建POJO
假设我们有Car
和Bus
类继承自Vehicle
类:
public abstract class Vehicle {
private String color;
private String speed;
// standard constructors, getters and setters
}
public class Car extends Vehicle {
private Integer tires;
// standard constructors, getters and setters
}
public class Bus extends Vehicle {
private Integer capacity;
// standard constructors, getters and setters
}
3.2. 创建DTO
让我们有CarDTO
类和BusDTO
类继承自VehicleDTO
类:
public class VehicleDTO {
private String color;
private String speed;
// standard constructors, getters and setters
}
public class CarDTO extends VehicleDTO {
private Integer tires;
// standard constructors, getters and setters
}
public class BusDTO extends VehicleDTO {
private Integer capacity;
// standard constructors, getters and setters
}
3.3. 映射器接口
让我们使用子类映射器定义基础映射器接口VehicleMapper
:
@Mapper(uses = { CarMapper.class, BusMapper.class })
public interface VehicleMapper {
VehicleDTO vehicleToDTO(Vehicle vehicle);
}
@Mapper()
public interface CarMapper {
CarDTO carToDTO(Car car);
}
@Mapper()
public interface BusMapper {
BusDTO busToDTO(Bus bus);
}
在这里,我们分别定义了所有子类映射器并在生成基础映射器时使用它们。这些子类映射器可以手动编写或由Mapstruct生成。在我们的用例中,我们将使用Mapstruct生成的子类映射器。
3.4. 识别问题
在生成实现类到/target/generated-sources/annotations/
目录后,让我们编写一个测试用例来验证基础映射器VehicleMapper
是否可以根据提供的子类POJO实例动态映射到正确的子类DTO。
我们将通过提供一个Car
对象并验证生成的DTO实例是否为CarDTO
类型来进行测试:
@Test
void whenVehicleTypeIsCar_thenBaseMapperNotMappingToSubclass() {
Car car = getCarInstance();
VehicleDTO vehicleDTO = vehicleMapper.vehicleToDTO(car);
Assertions.assertFalse(vehicleDTO instanceof CarDTO);
Assertions.assertTrue(vehicleDTO instanceof VehicleDTO);
VehicleDTO carDTO = carMapper.carToDTO(car);
Assertions.assertTrue(carDTO instanceof CarDTO);
}
因此,我们可以看到基础映射器无法识别提供的POJO对象为Car
实例。此外,它也无法动态选择相关的子类映射器CarMapper
。所以,基础映射器只能映射到VehicleDTO
对象,而不考虑提供的子类实例。
4. MapStruct 继承与实例检查
第一种方法是指示Mapstruct为每种Vehicle
类型生成映射器方法。然后,我们可以在基类的通用转换方法中通过使用Java的instanceof
操作符进行适当的子类转换方法调用来实现实例检查:
@Mapper()
public interface VehicleMapperByInstanceChecks {
CarDTO map(Car car);
BusDTO map(Bus bus);
default VehicleDTO mapToVehicleDTO(Vehicle vehicle) {
if (vehicle instanceof Bus) {
return map((Bus) vehicle);
} else if (vehicle instanceof Car) {
return map((Car) vehicle);
} else {
return null;
}
}
}
成功生成实现类后,我们可以通过使用通用方法验证每个子类类型的映射:
@Test
void whenVehicleTypeIsCar_thenMappedToCarDTOCorrectly() {
Car car = getCarInstance();
VehicleDTO vehicleDTO = vehicleMapper.mapToVehicleDTO(car);
Assertions.assertTrue(vehicleDTO instanceof CarDTO);
Assertions.assertEquals(car.getTires(), ((CarDTO) vehicleDTO).getTires());
Assertions.assertEquals(car.getSpeed(), vehicleDTO.getSpeed());
Assertions.assertEquals(car.getColor(), vehicleDTO.getColor());
}
@Test
void whenVehicleTypeIsBus_thenMappedToBusDTOCorrectly() {
Bus bus = getBusInstance();
VehicleDTO vehicleDTO = vehicleMapper.mapToVehicleDTO(bus);
Assertions.assertTrue(vehicleDTO instanceof BusDTO);
Assertions.assertEquals(bus.getCapacity(), ((BusDTO) vehicleDTO).getCapacity());
Assertions.assertEquals(bus.getSpeed(), vehicleDTO.getSpeed());
Assertions.assertEquals(bus.getColor(), vehicleDTO.getColor());
}
我们可以使用这种方法处理任意深度的继承。我们只需要为每个层级提供一个使用实例检查的方法。
5. MapStruct 继承与访问者模式
第二种方法是使用访问者模式。当我们使用访问者模式时,可以跳过实例检查,因为Java使用多态性在运行时确定确切应该调用哪个方法。
5.1. 应用访问者模式
首先,在抽象类Vehicle
中定义抽象方法accept()
以欢迎任何Visitor
对象:
public abstract class Vehicle {
public abstract VehicleDTO accept(Visitor visitor);
}
public interface Visitor {
VehicleDTO visit(Car car);
VehicleDTO visit(Bus bus);
}
接下来,我们需要为每个Vehicle
类型实现accept()
方法:
public class Bus extends Vehicle {
@Override
VehicleDTO accept(Visitor visitor) {
return visitor.visit(this);
}
}
public class Car extends Vehicle {
@Override
VehicleDTO accept(Visitor visitor) {
return visitor.visit(this);
}
}
最后,我们可以通过实现Visitor
接口来实现映射器: