【问题标题】:Mapping an object to an immutable object with builder (using immutables annotation processor) in mapstruct在 mapstruct 中使用构建器(使用不可变注释处理器)将对象映射到不可变对象
【发布时间】:2016-12-10 18:45:54
【问题描述】:

我们正在使用immutables framework 来生成所有 DTO。现在我们想用mapstruct 将这些对象映射到另一个对象。但是生成的 DTO 是不可变的,没有设置器和构造器,对应于构建器模式。它们仅通过静态builder()-方法访问的相应构建器填充。

我们尝试将 DTO1 映射到 DTO2.Builder,如果 mapstruct 能够识别 Builder 中的设置器,但它们没有 void 返回类型但返回 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 生成的映射器,显然没有找到任何 setter:

@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;
    }
}

返回空的构建器。我认为原因是生成的构建器的 setter 实现,因为它返回自身以创建流畅的 API:

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

有没有办法让 mapstruct 找到这些设置器?或者甚至是一种更好的方法来使用构建器来处理此类不可变对象?

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

【问题讨论】:

  • 正如 Andreas Gudian 所说,我遇到了一个已知问题。从版本 1.1.0.Beta2 开始,功能请求 #782“使用构建器映射不可变对象”已开放。可以在 mapstruct 的集成测试模块中找到一个示例。我将在下一次迭代中根据具体需求调整这个例子。欢迎对具体实施提出任何建议。 Java-Model-API 似乎有点棘手……

标签: java mapping immutability builder mapstruct


【解决方案1】:

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

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

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

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

@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 中看到 setter

这种映射器的用法看起来像

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

【讨论】:

  • 好点,感谢您分享您的想法!如果您将不可变对象用作 getter/setter、equals、hashCode 和 toString 的简单代码生成器,这是一个不错的选择,这是一个有效的用例。 (顺便说一下,immutables 也是一个很好的工具!)但对我们来说,不变性是我们选择 immutables 框架的核心价值。我们不想牺牲这个好处。
  • 迟到了,但如果你也使用@Value.Immutable,生成的Modifiable 类包含一个toImmutable() 方法,它为你提供不可变版本(包括运行验证),那么你'实际上只是将 Modifiable 类用作笨拙的 Builder。我正在使用这种模式,所以我可以将 Immutables 与 MyBatis 一起使用。
  • 重新读取Immutables docs,使用@Value.Style(builder = "new", init = "set*") 可能会更好,但您仍然需要稍后在对象上调用.build() 才能使用Immutable 实例。是时候重构了... :-)
【解决方案2】:

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

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

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

@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();
}

【讨论】:

    【解决方案3】:

    从 1.3 开始 MapStruct 支持不可变对象。更多详情请关注here

    【讨论】:

      猜你喜欢
      • 2020-10-29
      • 2010-11-21
      • 2018-01-18
      • 1970-01-01
      • 1970-01-01
      • 2021-02-17
      • 2016-10-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多