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

假设我们有CarBus类继承自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接口来实现映射器: