Mapstruct抽象目标类字段映射问题:自定义name映射报错
代码结构
源类
public abstract class Vehicle { private final String name; // 带getter方法 } public class Car extends Vehicle { private final int numOfDoors; // 带getter方法 } public class Motorbike extends Vehicle { private final boolean allowedForMinor; // 带getter方法 }
目标类
public abstract class VehicleDto { private final String name; public VehicleDto(String name) { this.name = name; } } public class CarDto extends VehicleDto { private final int numOfDoors; public CarDto(String name, int numOfDoors) { super(name); this.numOfDoors = numOfDoors; } } public class MotorbikeDto extends VehicleDto { private final boolean allowedForMinor; public MotorbikeDto(String name, boolean allowedForMinor) { super(name); this.allowedForMinor = allowedForMinor; } }
初始Mapper配置
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface VehicleMapper { @SubclassMapping(target = CarDto.class, source = Car.class) @SubclassMapping(target = MotorbikeDto.class, source = Motorbike.class) VehicleDto mapToDto(Vehicle vehicle); }
问题现象
初始配置下Mapper可正常生成并正确映射所有字段,但添加针对name字段的自定义映射配置@Mapping(target = "name", constant = "noname")到mapToDto方法后,出现编译错误:
Unknown property "name" in result type VehicleDto. Did you mean "null"?
问题原因
MapStruct默认映射时,会自动处理子类构造器中的父类参数:它能识别到CarDto、MotorbikeDto的构造器里需要传递name给父类,所以会自动从源类Vehicle的getName()方法取值填充。
但当你显式在父级映射方法mapToDto上添加@Mapping(target = "name")时,MapStruct会直接在目标类VehicleDto的当前层级查找可设置的属性:由于VehicleDto的name是私有final字段,没有setter方法,也不是当前类的可直接赋值的属性(构造参数属于初始化逻辑,MapStruct在处理显式@Mapping时不会自动关联到父类构造器),所以会报错说找不到该属性。
解决方案(不修改源/目标类)
方法1:在子类专属的映射方法上添加自定义映射
MapStruct的子类映射最终会生成对应子类的实现方法,因此把@Mapping配置放到子类的映射方法上即可,示例如下:
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface VehicleMapper { @SubclassMapping(target = CarDto.class, source = Car.class) @SubclassMapping(target = MotorbikeDto.class, source = Motorbike.class) VehicleDto mapToDto(Vehicle vehicle); // 给Car的映射方法添加name的自定义常量配置 @Mapping(target = "name", constant = "noname") CarDto mapCarToDto(Car car); // 给Motorbike的映射方法添加name的自定义常量配置 @Mapping(target = "name", constant = "noname") MotorbikeDto mapMotorbikeToDto(Motorbike motorbike); }
这样MapStruct在生成子类映射代码时,会自动把常量值传递给父类构造器,既满足自定义映射需求,又不会报错。
方法2:使用@Mapping的constructorArgs参数(可选)
也可以通过指定构造参数的方式来配置,但方法1更符合MapStruct子类映射的设计规范,可读性更强:
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface VehicleMapper { @SubclassMapping(target = CarDto.class, source = Car.class) @SubclassMapping(target = MotorbikeDto.class, source = Motorbike.class) @Mapping(target = "name", constant = "noname", constructorArgs = @ConstructorArg(index = 0)) VehicleDto mapToDto(Vehicle vehicle); }
不过这种方式需要明确构造参数的索引,当构造器参数顺序变化时容易出错,不如方法1稳定。
内容的提问来源于stack exchange,提问作者xKrasusX

