WER ERZÄHLT DIE NÄCHSTEN 55+ MINUTEN ETWAS? Gerrit Brehmer, Karlsruhe Auf der Arbeit: Software-Entwickler/Architekt ( / Projektleiter) bei der inovex GmbH In der Freizeit: Smart Home BEAN-MAPPING: EINSATZ Wann müssen Beans gemappt werden? Entities zu DTOs interne zu generierten Klassen (JAXB etc.) zur Einbindung externer Dienste standardisierten Schnittstellen eigenen REST/SOAP Schnittstellen Allgemein Austauschformate zwischen Schichten und Applikationen BEAN MAPPING: UMSETZUNG Was benutzen wir dafür bisher? von Hand geschriebener Mapping-Code typsicher, schnell fehleranfällig, aufwendig Dozer wenig Aufwand, Rekursives Auto-Mapping Reflection & XML: langsam Apache Commons BeanUtils wenig Aufwand Reflection, nur nicht komplexe Properties, langsam GEHT ES BESSER? Wunschliste schnell typsicher wenig Aufwand automatisches Mapping Null-Prüfungen Konvertierungen von "ähnlichen" Typen Fehlendes Mapping schnell erkennen nachvollziehbar MAPSTRUCT mapstruct.org / github.com/mapstruct OpenSource (Lizenz: Apache 2.0) Initator & Maintainer: Gunnar Morling (RedHat, Hibernate Team) Aktuelle Version: 1.0.0.CR2 (Final Release in Kürze) Weiterentwicklung/Support mehrere Stamm-Entwickler schnelles Feedback kontinuierliche Weiterentwicklung WIE FUNKTIONIERT MAPSTRUCT Annotation Processor Verarbeitet Annotations am eigenen Quellcode siehe andere bekannte Libs: JPA MetaModel, Lombok, QueryDSL z.B. mit Hilfe maven-processor-plugin Generiert Java Quellcode Zusammenspiel verschiedener FreeMarker Templates DEMO INTEGRATION IN ECLIPSE #1 MapStruct eclipse Plugin noch work-in-progress Hauptsächlich noch fehlende Features, keine Bugs Code Completion: JAXB Listen, Nested Properties Quick Fix: Collection Mappings INTEGRATION IN ECLIPSE #2 m2e APT Plugin Aktivierung nicht vergessen! Inkompatibilitäten mit Eclipse Compiler pre Mars (ab 1.0.0.CR2) Manchmal 'Clean Project' notwendig Maven: build-helpermaven-plugin automatische Build-Path Anpassung GENERIERTE ABHÄNGIGKEITEN Zu mappende Klassen sind ebenfalls generiert (z.B. JAXB per Schema) Resultat: Compiler Fehler bei Generierung MapStruct Mapper Lösung: Weiteres Maven Modul/Artefakt für abhängige Klassen Maven Plugins in unterschiedliche Phasen verlegen AUTOMAPPING #1 Primitive & Wrapper-Typen Datums-Typen Joda / Java 8 DateTime API Klassen Date Calendar dest.setLocalDateTime(java.time.LocalDateTime.ofInstant( src.getDate().toInstant(), java.time.ZoneId.systemDefault()) ); AUTOMAPPING #2 Collection- & Array-Typen Mapping nicht nur per Setter Adder (ohne Null Prüfung!) Getter (ohne Null Prüfung!) Setter Mapping-Methoden zwischen Collection/Array Typen müssen nicht definiert werden Maps JAXB JAXBElement XMLGregorianCalendar VERSCHACHTELTE MAPPINGS @Mapping(source = "source.nested.param" target = "param") public Target toTarget(Source source); inkl. Null-Prüfungen Bisher nur für Quell-Parameter Feature Request für Ziel-Parameter vorhanden MAPPING VON ENUM-TYPEN @Mapping(source = "FINAL", target = "LAST") TargetEnum map(SourceEnum enum); String-Match oder explizites Mapping fehlende Mappings zu Zielwerten werden signalisiert MAPPING DEFAULTS UND KONSTANTEN @Mapping(source = "src", target = "dest", defaultValue = "-1") Default-Werte als Ersatz bei null-Werten in QuellInstanzen @Mapping(constant = "CONST", target = "dest") String-Konstanten, auf die bei Bedarf StandardConverter oder andere Mapping Methoden angewendet werden QUALIFIER Unterscheidung bei identischem Quell- und Zieltyp Ohne Qualifier Zuordnung nicht eindeutig = Compiler Fehler spezielle Mappings für ansonsten automatisch generierten MappingCode @Qualifier @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Marker { } @Marker public String specialStringMapping(String source) { ... } @Mapping(source = "src", target = "dest", qualifiedBy = Marker.class) public Target map(Source source); BEAN-FACTORIES Als Ersatz für Standard-Konstruktor Selbst geschriebene Factory-Methoden (leere Parameterliste, Rückgabetyp = Zieltyp) Generische Factories möglich Verwendung bereits vorhandener (z.B. ObjectFactory von JAXB) @Mapper(uses = CustomFactory.class) public interface Mapper { ... } public class CustomFactory { public TargetDto createTargetDto() { .... } public <T extends AbstractEntity> T createEntity(@TargetType Class<T> entityClass) { ... } } MAPPING PER EXPRESSION @Mapping( expression = "java(java.util.UUID.randomUUID().toString())", target = "uniqueId") TargetDto mapTo(SourceEntity source); Aktuell nur Unterstützung für Java Code echte Skriptsprachen für zukünftige Versionen geplant Notwendige Imports können am @Mapper konfiguriert werden UNTERSCHIEDLICHE QUELLEN @Mappings({ @Mapping(source = "param1.src", target = "dest1"), @Mapping(source = "param2.src", target = "dest2") }) Target map(SourceOne param1, SourceTwo param2); Aber: Sind nur im Custom-Code / Expressions wiedervendbar MAPPING ALS UPDATE @Mapper public interface Mapper { Target update(Source src, @MappingTarget Target target); } mehrere Quellen möglich Return Type: voidoder identisch mit Ziel-Typ BIDIREKTIONALES MAPPING @Mapper public interface Mapper { @Mapping(source = "special", target = "destParam") Dto map(Entity source); @InheritInverseConfiguration Entity map(Dto source); } Ausnahmen geschachtelte Quell-Property Konstanten Expressions CUSTOM MAPPER #1 @Mapper(uses = CustomMapper.class) public interface Mapper { ... } public class CustomMapper { public TargetDto toTarget(Source source) { .... } } Mapper Klassen notwendig, wenn automatische Mapping-Methoden nicht generiert werden können Einzelnes Element gemappt auf Liste Liste mit Inhalten aus verschiedenen Quellen Liste komplexer Elemente gemappt auf Liste primitiver Werte CUSTOM MAPPER #2 @Mapper(uses = CustomMapper.class) public interface Mapper { ... } @Mapper public abstract class CustomMapper { public TargetDto toTarget(Source source) { .... } @Mappings(...) public abstract NestingTargetDto toNestingTarget(NestingSource source); } Abstrakte Klassen Ähnlich wie Mapper per Interface abstrakte Methoden werden generiert public Methoden enthalten den nicht generierbaren Mapper-Code KOMPONENTENMODELLE Konfiguration pro Mapper Mehrere werden bereits unterstützt Spring (@Component, @Autowired) CDI (@ApplicationScoped, @Inject) JSR 330 (@Named, @Inject) Alle miteinander verbundenen Mapper müssen dasselbe Komponentenmodell verwenden Keine Abhängigkeit zu MapStruct zur Laufzeit @Mapper(componentModel = "spring") public interface Mapper { ... } @Component public class MapperImpl { ... } KONFIGURATION @MapperConfig(componentModel = ..., uses = ..., ...) public interface DefaultMapperConfig { ... } @Mapper(config = "DefaultMapperConfig") public interface Mapper { ... } MAPPING ASPEKTE #1 Decorator angepasster Code für ausgewählte Methoden Aber: greift nur, wenn Methode vom eigenen Code oder anderen Mapper-Klassen aufgerufen wird @Mapper @DecoratedWith(MapperDecorator.class) public interface Mapper { ... } public abstract class MapperDecorator implements Mapper { private final Mapper delegate; public MapperDecorator(Mapper delegate) { this.delegate = delegate; } @Override public Target toTarget(Source source) { Target target = delegate.toTarget(source); // ... custom mapping ... } MAPPING ASPEKTE #2 @BeforeMappingund @AfterMapping Matching auf Mapping-Methoden anhand Quell- und Zieltypen @Mapper public abstract class Mapper { @BeforeMapping void flushEntity(AbstractEntity entity) { // flush entity to init all fields } @AfterMapping void manuelUpdateMissing(Source src, @MappingTarget Target target) { // e.g. as alternative for expressions / custom mapper } } EXCEPTIONHANDLING Checked Exceptions nur wenn an Mapping Methode deklariert ansonsten gewrapped in RuntimeException RuntimeExceptions ohne Anpassung FAZIT & AUSBLICK MapStruct ist ready-to-use umfangreiche Dokumentation lebendige Community keine Bugs, die den Einsatz verhndern Große Funktionsauswahl: Viele Wege führen zum Ziel! So viel wie möglich automatisch mappen Damit verbundene Checks durch MapStruct minimieren Mapping Fehler Das Feature-Set ist noch nicht komplett: Weitere nützliche Features werden sicher folgen! VIELEN DANK FÜR EURE AUFMERKSAMKEIT!