2016-08-04 8 views
7

Мы используем immutables framework для генерации всех DTO. Теперь мы хотим сопоставить эти объекты друг другу с помощью mapstruct. Но сгенерированные DTO неизменяемы и не имеют сеттеров и никакого конструктора, соответствующего шаблону строителя. Они заполняются только через соответствующий строитель, к которому обращается статический метод builder().Сопоставление объекта с неизменяемым объектом с помощью построителя (с использованием обработчика аннотаций неизменяемости) в mapstruct

Вместо этого мы попытались отобразить DTO1 в DTO2.Builder, который будет работать, если mapstruct будет распознавать setter в Builder, но они не имеют типа возвращаемого типа, но возвращают сам Builder для свободного конкатенации.

Так вот код примера.

У нас есть два интерфейса

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

и

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

Тогда мы имеем интерфейс Mapper для mapstruct:

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

    ImmutableMammalEntity.Builder toTarget(MammalDto source); 
} 

Для mapstruct найти Строителя нам нужен завод:

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, потому что они находились в пути к классам до запуска обработчиков аннотаций. Эта ошибка исправлена.

Это работает как шарм. Сначала генерируются Неизменяемые реализации межфактур, а 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 показывает, что он явно не нашел каких-либо сеттеры:

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

Пустое строитель возвращается. Я думаю, что причина заключается в сеттер реализации вырабатываемого строителя, потому что она возвращает себя, чтобы создать свободно API:

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

Есть ли способ, чтобы позволить mapstruct найти эти сеттер? Или даже лучший способ справиться с такими непреложными объектами со строителями?

EDIT: Как я уже говорил в комментарии, я столкнулся с Issue #782. В версии 1.2.0.Beta3 сборщики по-прежнему не поддерживаются. Но есть несколько дискуссий по этой теме, поэтому было бы интересно следить за проблемой, если у вас есть одна и та же проблема.

+0

Как заявил Андреас Gudian я столкнулся с известной проблемой. Запрос функции № 782 «Сопоставление неизменяемых объектов с строителями» открыт с версии 1.1.0.Beta2. Расширение можно найти в интеграционном тестовом модуле mapstruct. Я буду адаптировать этот экстракт к конкретным потребностям на следующей итерации. Любые предложения по конкретному внедрению приветствуются. Java-Model-API кажется немного сложным ... –

ответ

1

У нас была такая же проблема в нашем проекте. В качестве обходного решения мы использовали Modifiable реализацию нашего неизменяемого dto.

Вы также можете попробовать. Лучше прямое использование строителей и объектов-заводов.

@Value.Modifiable создает реализацию с сеттерами.

@Value.Style(create = "new") генерирует открытый конструктор 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

Использование такого картографа воли выглядит

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

Хорошо, спасибо, что поделились своей идеей! Это хороший выбор, если вы используете неизменяемый код как простой генератор кода для getter/setter, equals, hashCode и toString, который является допустимым прецедентом. (Кстати, непреложный инструмент для этого тоже замечательный инструмент!) Но для нас неизменность является центральной ценностью, для которой мы выбрали неизменяемую структуру. Мы не хотели бы жертвовать этой выгодой. –

0

Вы можете настроить Immutables генерировать сеттеры в строитель:

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

И вам не нужен ObjectBuilder, вы можете напрямую использовать ген создававшие Неизменное класс

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