2016-08-04 54 views
7

Używamy immutables framework do generowania wszystkich DTO. Teraz chcielibyśmy odwzorować te obiekty jeden na drugi z mapstruct. Jednak wygenerowane DTO są niezmienne i nie mają ustawników ani konstruktora, co odpowiada wzorowi budowniczego. Są one wypełniane tylko przez odpowiedni program budujący, do którego dostęp ma statyczna metoda.Mapowanie obiektu na niezmienny obiekt za pomocą konstruktora (za pomocą procesora adnotacji niezmiennych) w mapstruct

Zamiast tego próbowaliśmy odwzorować DTO1 na DTO2.Builder, który zadziałałby, gdyby system mapctct rozpoznał narzędzie ustawiające w Builderze, ale nie ma on pustego typu zwracania, ale zwraca Builderowi w celu płynnej konkatenacji.

Oto kod przykładu.

Mamy dwa interfejsy

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

i

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

Następnie mamy interfejs Mapper dla mapstruct:

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

    ImmutableMammalEntity.Builder toTarget(MammalDto source); 
} 

Dla mapstruct znalezienia Builder musimy fabryczne:

public class ObjectFactory { 

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

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

W celu wygenerowania kodu wtyczki kompilatora został poinstruowany, aby korzystać z obu procesorów dopiskiem

<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> 

Uwaga: to będzie działać tylko z wersji mapstruct> 1.2.x. Starsze wersje mają problem z czystą kompilacją (mvn clean compile), że nie znajdują źródeł, które tylko wbudowane zostały niezmienione. W drugiej wersji (bez czystej) znajdziemy implementacje immutables, ponieważ znajdują się one w ścieżce klas przed uruchomieniem procesorów adnotacji. Ten błąd został naprawiony teraz.

Działa to jak urok. Najpierw generowane są Niezmienne implementacje interfactes i mapstruct używa ich do generowania budowniczego.

ale test pokazuje, że nie ma właściwości są ustawione:

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

twierdzi niepowodzeniem. Jedno spojrzenie na odwzorowującego generowanego przez mapstruct pokazuje, że to oczywiście nie znaleziono żadnych ustawiające:

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

Pusty budowniczy jest zwracana. Myślę, że powodem jest seter realizacja wygenerowanego konstruktora, ponieważ zwraca się do stworzenia biegle API:

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

Czy istnieje sposób, aby pozwolić mapstruct znaleźć te ustawiające? A może nawet lepszy sposób radzenia sobie z takimi niezmiennymi obiektami z budowniczymi?

EDYCJA: Jak stwierdziłem w komentarzu wpadłem na Issue #782. W wersji 1.2.0. Builder beta 3 nadal nie jest obsługiwany. Ale jest kilka dyskusji na ten temat, więc może być interesujące śledzenie problemu, jeśli ma się ten sam problem.

+0

jak stwierdził Andreas Gudian Pobiegłem do znanego problemu. Żądanie funkcji # 782 "Mapowanie niezmiennych obiektów za pomocą Builderów" jest otwarte od wersji 1.1.0.Beta2. Przykład można znaleźć w module testowym integracji programu mapstruct. Będę dostosowywać ten ekwipunek do konkretnych potrzeb w następnej iteracji. Wszelkie sugestie dotyczące konkretnej implementacji są mile widziane. Model Java-Model-API wydaje się być nieco trudny ... –

Odpowiedz

1

Mieliśmy ten sam problem w naszym projekcie. Jako obejście używaliśmy implementacji naszego niezmiennego dto w postaci Modifiable.

Możesz także spróbować. Lepsze jest bezpośrednie wykorzystanie budowniczych i fabryk obiektów.

Generuje implementację za pomocą ustawiaczy.

@Value.Style(create = "new") generuje publiczny brak konstruktora args.

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

Wtedy twój odwzorowanie będzie prostsze, bez potrzeby w fabryce obiektów.

@Mapper 
public interface SourceTargetMapper { 

    ModifiableMammalEntity toTarget(MammalDto source); 
} 

W tym przypadku MapStruct widać ustawiające w ModifiableMammalEntity

Wykorzystanie takiego elementu odwzorowującego woli wygląda

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

Dobrze, dziękuję za podzielenie się swoim pomysłem! Jest to dobry wybór, jeśli używasz niezmiennych jako prosty generator kodu dla getter/setter, equals, hashCode i toString, który jest prawidłowym przypadkiem użycia. (Nawiasem mówiąc, niezmienne są dla tego również wspaniałym narzędziem!) Ale dla nas niezmienność jest główną wartością, dla której wybraliśmy ramy niezmienne. Nie chcielibyśmy rezygnować z tej korzyści. –

0

Można skonfigurować Immutables wygenerować ustawiające w konstruktora:

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

I nie potrzebujesz ObjectBuilder, możesz bezpośrednio użyć gen erated Niezmienne klasy

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

    ImmutableMammalEntity.Builder toTarget(MammalDto source); 
} 

Można nawet zdefiniować te ustawienia w swoim własnym adnotacji

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

i stosowania, że ​​zamiast

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