你好,我是看山。
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判断类型。