使用具有继承的MapStruct

2025/04/08

1. 概述

MapStruct是一个Java注解处理器,在为Java Bean类生成类型安全且有效的映射器时非常有用。

在本教程中,我们将具体学习如何将Mapstruct映射器与继承的Java Bean类一起使用

我们将讨论三种方法;第一种方法是实例检查,第二种方法是使用访问者模式,最后一种也是推荐的方法是使用Mapstruct 1.5.0中引入的@SubclassMapping注解。

2. Maven依赖

让我们将以下mapstruct依赖添加到Maven pom.xml中:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.6.0.Beta1</version>
</dependency>

3. 理解问题

默认情况下,Mapstruct不够智能,无法为所有从基类或接口继承的类生成映射器,Mapstruct也不支持在运行时识别对象层次结构下提供的实例。

3.1 创建POJO

假设我们的Car和Bus POJO类扩展了Vehicle POJO类:

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 Mapper接口

让我们使用子类映射器定义基本映射器接口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接口来实现映射器:

@Mapper()
public abstract class VehicleMapperByVisitorPattern implements Visitor {
    public VehicleDTO mapToVehicleDTO(Vehicle vehicle) {
        return vehicle.accept(this);
    }

    @Override
    public VehicleDTO visit(Car car) {
        return map(car);
    }

    @Override
    public VehicleDTO visit(Bus bus) {
        return map(bus);
    }

    abstract CarDTO map(Car car);
    abstract BusDTO map(Bus bus);
}

访问者模式方法比实例检查方法更加优化,因为当深度较高时,不需要检查所有子类,从而节省映射时间。

5.2 测试访问者模式

成功生成实现类之后,我们来验证一下各个Vehicle类型与使用Visitor接口实现的映射器的映射情况:

@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());
}

6. 使用@SubclassMapping实现Mapstruct继承

正如我们前面提到的,Mapstruct 1.5.0引入了@SubclassMapping注解,这使我们能够配置映射以处理源类型的层次结构。source()函数定义要映射的子类,而target()指定要映射到的子类

public @interface SubclassMapping {
    Class<?> source();
    Class<?> target();
    // other methods
}

6.1 应用注解

让我们应用@SubclassMapping注解来实现Vehicle层次结构中的继承:

@Mapper()
public interface VehicleMapperBySubclassMapping {
    @SubclassMapping(source = Car.class, target = CarDTO.class)
    @SubclassMapping(source = Bus.class, target = BusDTO.class)
    VehicleDTO mapToVehicleDTO(Vehicle vehicle);
}

为了了解@SubclassMapping内部是如何工作的,让我们看一下/target/generated-sources/annotations/下生成的实现类:

@Generated
public class VehicleMapperBySubclassMappingImpl implements VehicleMapperBySubclassMapping {
    @Override
    public VehicleDTO mapToVehicleDTO(Vehicle vehicle) {
        if (vehicle == null) {
            return null;
        }

        if (vehicle instanceof Car) {
            return carToCarDTO((Car) vehicle);
        } else if (vehicle instanceof Bus) {
            return busToBusDTO((Bus) vehicle);
        } else {
            VehicleDTO vehicleDTO = new VehicleDTO();

            vehicleDTO.setColor(vehicle.getColor());
            vehicleDTO.setSpeed(vehicle.getSpeed());

            return vehicleDTO;
        }
    }
}

根据实现,我们可以注意到,Mapstruct在内部使用实例检查来动态选择子类。因此,我们可以推断,对于层次结构中的每一层,我们都需要定义一个@SubclassMapping。

6.2 测试注解

我们现在可以使用通过@SubclassMapping注解实现的Mapstruct映射器来验证每种Vehicle类型的映射:

@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());
}

7. 总结

在本文中,我们讨论了如何使用继承的对象类编写Mapstruct映射器。

我们讨论的第一种方法使用了instanceof检查,而第二种方法使用了著名的访问者模式;此外,使用Mapstruct功能@SubclassMapping可以使事情变得更简单。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章