使用新的@SubclassMapping 注释时如何忽略 MapStruct 中的某些字段?

How to ignore some fields in MapStruct when using the new @SubclassMapping annotation?

初始情况

使用当前 1.5.0.Beta2 MapStruct 版本 JDK 13.

领域模型

class Wrapper {
    private Fruit fruit;
}
abstract class Fruit {
    private int weight;
    /* ... */
}

class Apple extends Fruit {
    /* ... */
}

class Banana extends Fruit {
    /* ... */
}

(省略相应的1:1 DTO)

映射器

包装器的映射器class

@Mapper(uses = {FruitMapper.class})
public interface WrapperMapper {
    WrapperDto map(Wrapper wrapper)
}

水果映射器 class(es)

MapStruct Mapper for abstract Fruit class with method signature and annotations:

@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION /*...*/)
public interface FruitMapper {
    @SubclassMapping(source = Apple.class, target = AppleDto.class)
    @SubclassMapping(source = Banana.class, target = BananaDto.class)
    FruitDto map(Fruit fruit);
}

问题

以上工作正常,直到需要在引用的摘要 class 上忽略一个字段(例如 Fruitweight)。 将此注释放入 WrapperMapper map 方法...

@Mapping(target = "fruit.weight", ignore = true)
WrapperDto map(Wrapper wrapper)

...导致 The return type FruitDto is an abstract class or interface. Provide a non abstract / non interface result type or a factory method. 编译错误。

问题

有没有办法按照概述跳过 MapStruct 映射中的字段而不会出现此编译错误?

原因

发生的事情是因为你在WrapperMapper上指定它不会直接使用FruitMapper。相反,WrapperMapper 实现将生成一个新的映射方法来映射水果。 MapStruct 在这次尝试中失败并给你编译错误。

解决方案

如果您将忽略添加到 FruitMapper,它将继承它到两个子类映射:

        @SubclassMapping( source = Apple.class, target = AppleDto.class )
        @SubclassMapping( source = Banana.class, target = BananaDto.class )
        @Mapping( target = "weight", ignore = true )
        FruitDto map( Fruit fruit );

如果您想要一个有权重的映射和一个没有权重的映射,您可以将 @org.mapstruct.Named 注释添加到没有权重的映射,然后在 WrappedMapper 中使用它。

像这样:

    @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION /*...*/)
    public interface FruitMapper {
        @SubclassMapping( source = Apple.class, target = AppleDto.class )
        @SubclassMapping( source = Banana.class, target = BananaDto.class )
        FruitDto map( Fruit fruit );

        @SubclassMapping( source = Apple.class, target = AppleDto.class )
        @SubclassMapping( source = Banana.class, target = BananaDto.class )
        @Mapping( target = "weight", ignore = true )
        @Named( "NoWeights" )
        FruitDto map( Fruit fruit );
    }

    @Mapper(uses = {FruitMapper.class})
    public interface WrapperMapper {
        @Mapping( target = "fruit", qualifiedByName = "NoWeights" )
        WrapperDto map(Wrapper wrapper)
    }