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

问题描述 投票:8回答:3

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

我们试图将DTO1映射到DTO2.Builder,如果mapstruct能识别Builder中的setter但这些没有void返回类型但返回Builder本身以进行流畅的连接,那么它将起作用。

所以这是示例的代码。

我们有两个接口

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

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

然后我们有了maptruct的Mapper接口:

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

  ImmutableMammalEntity.Builder toTarget(MammalDto source);
}

要使用mapstruct来查找Builder,我们需要一个Factory:

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)中存在问题,即它们找不到不可变构建的源。在第二个构建(没有干净)中,他们会找到immutables实现,因为它们在注释处理器运行之前位于类路径中。这个bug现在已修复。

这就像一个魅力。首先生成interfactes的Immutable实现,并且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);
}

断言失败。一看maptruct生成的mapper就会发现它显然没有找到任何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找到这些setter?或者甚至是用构建器处理这些不可变对象的更好方法?

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

java mapping immutability builder mapstruct
3个回答
1
投票

由于1.3 MapStruct支持Immutables。查看here了解更多详情。


2
投票

我们的项目也有同样的问题。作为解决方法,我们一直在使用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);

1
投票

您可以配置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();
}
© www.soinside.com 2019 - 2024. All rights reserved.