MapStruct教程-三種方式處理繼承關系
你好,我是看山。
MapStruct是一個效率工具,可以在處理Java Bean映射時,幫助我們盡量減少樣板代碼,只需要定義接口,它會自動生成映射邏輯。本文中,我們一起看下如何通過MapStruct處理集成關系。
我們將討論三種方法:
- 通過實例檢查;
- 使用訪問者模式;
- 【推薦】使用@SubclassMapping注解。
一、背景
默認情況下,MapStruct無法為所有從基類或接口繼承的類生成映射器,不支持運行時識別實例和對象層次結構。
(一)基礎定義
先定義POJO類:
public abstract class Vehicle {
private String color;
private String speed;
}
public class Car extends Vehicle{
private Integer tires;
}
public class Bus extends Vehicle {
private Integer capacity;
}
再定義對應的DTO類:
public class VehicleDTO {
private String color;
private String speed;
}
public class CarDTO extends VehicleDTO{
private Integer tires;
}
public class BusDTO extends VehicleDTO{
private Integer capacity;
}
(二)定義映射器
這里我們定義三個映射器:CarMapper、BusMapper、VehicleMapper:
@Mapper
public interface CarMapper {
CarDTO carToDTO(Car car);
}
@Mapper
public interface BusMapper {
BusDTO busToDTO(Bus bus);
}
@Mapper(uses = {BusMapper.class, CarMapper.class})
public interface VehicleMapper {
VehicleDTO vehicleToDTO(Vehicle vehicle);
}
在這里,我們分別定義了所有子類的映射器,并在基類映射器通過uses配置使用它們。
(三)識別問題
我們先看下VehicleMapper的生成類:
public class VehicleMapperImpl implements VehicleMapper {
@Override
public VehicleDTO vehicleToDTO(Vehicle vehicle) {
if ( vehicle == null ) {
return null;
}
VehicleDTO vehicleDTO = new VehicleDTO();
vehicleDTO.setColor( vehicle.getColor() );
vehicleDTO.setSpeed( vehicle.getSpeed() );
return vehicleDTO;
}
}
可以看到,代碼中是直接使用了VehicleDTO,并沒有用到子類的任何定義。我們可以在入口傳入Car或Bus的實例對象,但是最終得到的只能是VehicleDTO的實例。
二、通過實例檢查實現
我們一起看看如何通過實例檢查實現映射邏輯,因為比較簡單,直接代碼:
@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;
}
}
}
從上面代碼來說,其實完全是靠人工智能實現的,通過我們HardCode,借助instanceof實現類型判斷,轉換為需要的類型實例。
三、通過訪問者模式實現
第二種方式是借助訪問者模式實現,通過Java的多態,可以精準的調用到需要的方法。
(一)應用訪問者模式
首先在抽象類Vehicle中定義抽象方法accept(),以接受任何訪問者對象:
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);
}
}
最后,我們可以通過實現訪問者接口來實現映射器:
@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);
}
從性能來說,訪問者模式方法比實例檢查方法更優化,借助Java的多態實現,快速定位轉換方法。從代碼量來說,實例檢查會由于訪問者模式,因為寫的少。
其實這里會引出一個問題,設計模式是好是壞?
四、使用@SubclassMapping實現
MapStruct提供了@SubclassMapping注解,允許我們配置的映射器,可以處理有繼承關系的類實例轉換。
@SubclassMapping注解中有source和target兩個配置,source定義要映射的源子類,target定義要映射到的目標子類:
(一)定義
我們使用@SubclassMapping注解指定繼承對應關系:
@Mapper(uses = {BusMapper.class, CarMapper.class})
public interface VehicleMapperBySubclassMapping {
@SubclassMapping(source = Car.class, target = CarDTO.class)
@SubclassMapping(source = Bus.class, target = BusDTO.class)
VehicleDTO mapToVehicleDTO(Vehicle vehicle);
}
生成的映射代碼為:
public class VehicleMapperBySubclassMappingImpl implements VehicleMapperBySubclassMapping {
private final BusMapper busMapper = Mappers.getMapper( BusMapper.class );
private final CarMapper carMapper = Mappers.getMapper( CarMapper.class );
@Override
public VehicleDTO mapToVehicleDTO(Vehicle vehicle) {
if ( vehicle == null ) {
return null;
}
if (vehicle instanceof Car) {
return carMapper.carToDTO( (Car) vehicle );
}
else if (vehicle instanceof Bus) {
return busMapper.busToDTO( (Bus) vehicle );
}
else {
VehicleDTO vehicleDTO = new VehicleDTO();
vehicleDTO.setColor( vehicle.getColor() );
vehicleDTO.setSpeed( vehicle.getSpeed() );
return vehicleDTO;
}
}
}
可以看到,MapStruct自動實現了實例檢查,通過instanceof判斷類型。