在 mapstruct 中使用构建器(使用不可变注释处理器)将对象映射到不可变对象

Mapping an object to an immutable object with builder (using immutables annotation processor) in mapstruct

我们正在使用 immutables framework to generate all DTOs. Now we would like to map these objects one to another with mapstruct。但是生成的 DTO 是不可变的,没有 setters 也没有构造函数,对应于构建器模式。它们仅通过静态 builder() 方法访问的相应构建器填充。

我们改为尝试将 DTO1 映射到 DTO2.Builder,如果 mapstruct 可以识别 Builder 中的 setter,这将起作用,但它们没有 void return 类型,但 return Builder 本身用于流畅的连接。

所以这里是示例的代码。

我们有两个接口

@Value.Immutable
public interface MammalDto {
  public Integer getNumberOfLegs();
  public Long getNumberOfStomachs();
}

@Value.Immutable
public interface MammalEntity {
  public Long getNumberOfLegs();
  public Long getNumberOfStomachs();
}

然后我们有mapstruct的Mapper接口:

@Mapper(uses = ObjectFactory.class)
public interface SourceTargetMapper {
  SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

  ImmutableMammalEntity.Builder toTarget(MammalDto source);
}

为了让 mapstruct 找到 Builder,我们需要一个工厂:

public class ObjectFactory {

  public ImmutableMammalDto.Builder createMammalDto() {
    return ImmutableMammalDto.builder();
  }

  public ImmutableMammalEntity.Builder createMammalEntity() {
    return ImmutableMammalEntity.builder();
  }
}

为了生成代码,指示编译器插件使用两个注释处理器:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.immutables</groupId>
                <artifactId>value</artifactId>
                <version>2.2.8</version>
            </path>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.2.0.Beta3</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

注意:这将 用于 mapstruct 版本 > 1.2.x。旧版本在干净构建 (mvn clean compile) 中存在问题,即它们找不到刚刚构建的不可变源。在第二个构建(没有清理)中,他们会找到不可变的实现,因为它们在注释处理器 运行 之前就在类路径上。此错误现已修复。

这很有魅力。首先生成接口的不可变实现,mapstruct 使用它们生成构建器。

但是测试显示没有设置任何属性:

@Test
public void test() {
  MammalDto s = ImmutableMammalDto.builder().numberOfLegs(4).numberOfStomachs(3l).build();
  MammalEntity t = SourceTargetMapper.MAPPER.toTarget(s).build();
    assertThat(t.getNumberOfLegs()).isEqualTo(4);
    assertThat(t.getNumberOfStomachs()).isEqualTo(3);
}

断言失败。一看 mapstruct 生成的映射器,它显然没有找到任何 setters:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    //...
)
public class SourceTargetMapperImpl implements SourceTargetMapper {
    private final ObjectFactory objectFactory = new ObjectFactory();

    @Override
    public Builder toTarget(MammalDto source) {
        if ( source == null ) {
            return null;
        }

        Builder builder = objectFactory.createMammalEntity();
        return builder;
    }
}

空构建器​​已 returned。我认为原因是生成的生成器的 setter 实现,因为它 return 本身可以创建流畅的 API:

public final Builder numberOfLegs(Long numberOfLegs) {
  this.numberOfLegs = Objects.requireNonNull(numberOfLegs, "numberOfLegs");
  return this;
}

有没有办法让mapstruct找到这些setter?或者更好的方法来处理与构建器的此类不可变对象?

编辑:正如我在评论中所说,我 运行 变成了 Issue #782。在版本 1.2.0.Beta3 中仍然不支持构建器。但是有几个关于这个话题的讨论,所以如果有人有同样的问题,关注这个问题可能会很有趣。

我们的项目遇到了同样的问题。 作为解决方法,我们一直在使用不可变 dto 的 Modifiable 实现。

你也可以试试。最好直接使用构建器和对象工厂。

@Value.Modifiable 使用 setter 生成实现。

@Value.Style(create = "new") 生成 public 无参数构造函数。

@Value.Immutable
@Value.Modifiable
@Value.Style(create = "new")
public interface MammalEntity {
    public Long getNumberOfLegs();
    public Long getNumberOfStomachs();
}

那么你的映射器会更简单,不需要对象工厂。

@Mapper
public interface SourceTargetMapper {

  ModifiableMammalEntity toTarget(MammalDto source);
}

在这种情况下,MapStruct 可以在 ModifiableMammalEntity

中看到设置器

这种映射器的用法类似于

// Here you don't need to worry about implementation of MammalEntity is. The interface `MammalEntity` is immutable.
MammalEntity mammalEntity = sourceTargetMapper.toTarget(source);

您可以配置 Immutables 以在构建器中生成 setter:

@Value.Immutable
@Value.Style(init = "set*")
public interface MammalEntity {
    public Long getNumberOfLegs();
    public Long getNumberOfStomachs();
}

而且不需要ObjectBuilder,直接使用生成的Immutable即可class

@Mapper(uses = ImmutableMammalEntity.class)
public interface SourceTargetMapper {
    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    ImmutableMammalEntity.Builder toTarget(MammalDto source);
}

您甚至可以在自己的注释中定义这些设置

@Value.Style(init = "set*")
public @interface SharedData {}

并改用它

@SharedData
@Value.Immutable
public interface MammalEntity {
    public Long getNumberOfLegs();
    public Long getNumberOfStomachs();
}

从 1.3 开始,MapStruct 支持不可变对象。查看 here 了解更多详情。