mapstruct - der neue stern am bean-mapping himmel?!
TRANSCRIPT
WER ERZÄHLT DIE NÄCHSTEN 55+ MINUTENETWAS?
Gerrit Brehmer, Karlsruhe
Auf der Arbeit: Software-Entwickler/Architekt ( / Projektleiter) bei derinovex GmbH
In der Freizeit: Smart Home
BEAN-MAPPING: EINSATZWann müssen Beans gemappt werden?
Entities zu DTOsinterne zu generierten Klassen (JAXB etc.)
zur Einbindung externer Dienstestandardisierten Schnittstelleneigenen REST/SOAP Schnittstellen
Allgemein Austauschformate zwischen Schichten und Applikationen
BEAN MAPPING: UMSETZUNGWas benutzen wir dafür bisher?
von Hand geschriebener Mapping-Codetypsicher, schnellfehleranfällig, aufwendig
Dozerwenig Aufwand, Rekursives Auto-MappingReflection & XML: langsam
Apache Commons BeanUtilswenig AufwandReflection, nur nicht komplexe Properties, langsam
GEHT ES BESSER?Wunschliste
schnelltypsicherwenig Aufwand
automatisches MappingNull-PrüfungenKonvertierungen von "ähnlichen" Typen
Fehlendes Mapping schnell erkennennachvollziehbar
MAPSTRUCTmapstruct.org / github.com/mapstructOpenSource (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-Entwicklerschnelles Feedbackkontinuierliche Weiterentwicklung
WIE FUNKTIONIERT MAPSTRUCTAnnotation Processor
Verarbeitet Annotations am eigenen Quellcodesiehe andere bekannte Libs: JPA MetaModel, Lombok, QueryDSLz.B. mit Hilfe maven-processor-plugin
Generiert Java QuellcodeZusammenspiel verschiedener FreeMarker Templates
INTEGRATION IN ECLIPSE #1MapStruct eclipse Plugin
noch work-in-progressHauptsächlich noch fehlende Features, keine Bugs
Code Completion: JAXB Listen, Nested PropertiesQuick Fix: Collection Mappings
INTEGRATION IN ECLIPSE #2
m2e APT PluginAktivierung nichtvergessen!Inkompatibilitäten mitEclipse Compiler pre Mars(ab 1.0.0.CR2)Manchmal 'Clean Project'notwendig
Maven: build-helper-maven-plugin
automatische Build-PathAnpassung
GENERIERTE ABHÄNGIGKEITENZu mappende Klassen sind ebenfalls generiert (z.B. JAXB per Schema)
Resultat: Compiler Fehler bei Generierung MapStruct MapperLösung:
Weiteres Maven Modul/Artefakt für abhängige KlassenMaven Plugins in unterschiedliche Phasen verlegen
AUTOMAPPING #1Primitive & Wrapper-TypenDatums-Typen
Joda / Java 8 DateTime API KlassenDateCalendar
dest.setLocalDateTime(java.time.LocalDateTime.ofInstant( src.getDate().toInstant(), java.time.ZoneId.systemDefault()) );
AUTOMAPPING #2Collection- & Array-Typen
Mapping nicht nur per SetterAdder (ohne Null Prüfung!)Getter (ohne Null Prüfung!)Setter
Mapping-Methoden zwischen Collection/Array Typen müssen nichtdefiniert werden
MapsJAXB
JAXBElementXMLGregorianCalendar
VERSCHACHTELTE MAPPINGS@Mapping(source = "source.nested.param" target = "param") public Target toTarget(Source source);
inkl. Null-PrüfungenBisher 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 Mappingfehlende Mappings zu Zielwerten werden signalisiert
MAPPING DEFAULTS UND KONSTANTEN@Mapping(source = "src", target = "dest", defaultValue = "-1")
Default-Werte als Ersatz bei null-Werten in Quell-Instanzen
@Mapping(constant = "CONST", target = "dest")
String-Konstanten, auf die bei Bedarf Standard-Converter oder andere Mapping Methodenangewendet werden
QUALIFIERUnterscheidung bei identischem Quell- und Zieltyp
Ohne Qualifier Zuordnung nicht eindeutig = Compiler Fehlerspezielle Mappings für ansonsten automatisch generierten Mapping-Code
@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-FACTORIESAls Ersatz für Standard-KonstruktorSelbst geschriebene Factory-Methoden (leere Parameterliste,Rückgabetyp = Zieltyp)Generische Factories möglichVerwendung 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 Codeechte 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öglichReturn Type: void oder identisch mit Ziel-Typ
BIDIREKTIONALES MAPPING@Mapper public interface Mapper {
@Mapping(source = "special", target = "destParam") Dto map(Entity source); @InheritInverseConfiguration Entity map(Dto source); }
Ausnahmengeschachtelte Quell-PropertyKonstantenExpressions
CUSTOM MAPPER #1@Mapper(uses = CustomMapper.class) public interface Mapper { ... }
public class CustomMapper { public TargetDto toTarget(Source source) { .... } }
Mapper Klassennotwendig, wenn automatische Mapping-Methoden nichtgeneriert werden können
Einzelnes Element gemappt auf ListeListe mit Inhalten aus verschiedenen QuellenListe komplexer Elemente gemappt auf Liste primitiverWerte
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 generiertpublic Methoden enthalten den nicht generierbaren Mapper-Code
KOMPONENTENMODELLEKonfiguration pro MapperMehrere werden bereits unterstützt
Spring (@Component, @Autowired)CDI (@ApplicationScoped, @Inject)JSR 330 (@Named, @Inject)
Alle miteinander verbundenen Mapper müssen dasselbeKomponentenmodell verwendenKeine 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 #1Decorator
angepasster Code für ausgewählte MethodenAber: greift nur, wenn Methode vom eigenen Code oder anderenMapper-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@BeforeMapping und @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 } }
EXCEPTIONHANDLINGChecked Exceptions
nur wenn an Mapping Methode deklariertansonsten gewrapped in RuntimeException
RuntimeExceptions ohne Anpassung
FAZIT & AUSBLICKMapStruct ist ready-to-use
umfangreiche Dokumentationlebendige Communitykeine 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 MappingFehler
Das Feature-Set ist noch nicht komplett: Weitere nützliche Featureswerden sicher folgen!