Welcher JavaBean-Mapper? Die Qual der Wahl…

Ablösung von Dozer

In einem Kundenprojekt setzten wir Dozer ein, um Objekte zwischen Klassen der verschiedenen Anwendungsschichten (Persistenz, Domain Model, Webservice etc.) zu mappen. Wobei im Projekt nicht nur einfaches Mapping durchgeführt wird, sondern teils auch komplexere Konvertierungen gemacht werden müssen.

Dozer kopiert die Attribute per Reflection vom Ursprungs- ins Zielobjekt – je nach Konfiguration wahlweise durch Aufrufen der entsprechenden getter- und setter-Methoden, oder durch direktes lesen und schreiben der Objektattribute. Da Dozer deswegen im Bereich Performance nicht gerade glänzt, begaben wir uns auf die Suche nach einer performanteren Alternative.

Auf dieser Webseite gibt es eine gute Übersicht zu gängigen Mapping-Frameworks inklusive einer Auflistung von deren Vor- und Nachteilen. Da die beiden Frameworks JMapper und MapStruct im Vergleich die beste Performance zeigten, haben wir uns diese beiden näher angesehen. Nachfolgend schildere ich die Erfahrungen, die wir mit den Mappern gemacht haben.

JMapper

JMapper war im Vergleich das schnellste Framework. Es gibt drei Möglichkeiten, das Mapping zu konfigurieren:

  • Mit Annotationen in den Klassen, die es zu mappen gilt. Die Annotationen können sowohl in der Quell-, als auch in der Zielklasse definiert sein.
  • Mittels einer JMapper API, die eine programmatische Konfiguration des Mappers erlaubt.
  • Durch Definition der Mappings in einer XML Datei.

Basierend auf der Konfiguration wird zur Laufzeit Bytecode generiert. Mappings, bei denen die Namen von Ursprungs- und Ziel-Attribut übereinstimmen, sind leicht zu konfigurieren. Es muss „nur“ der Name des Attributs angegeben werden. (Andere Mapping-Frameworks kommen bei gleichlautenden Attributnamen allerdings komplett ohne Konfiguration aus). Wenn Quell- und Ziel-Attribut unterschiedliche Namen haben, müssen beide Namen angegeben werden.

JMapper bietet zudem die Möglichkeit, eigene Konvertierungen zu definieren. Benutzt man dabei jedoch die API oder eine XML-Definition, so muss auf IDE-Features wie Code-Completion verzichtet werden. Auch das Debuggen solcher Konversionen gestaltet sich schwierig, da die Methoden zur Laufzeit generiert werden und keine Sourcedateien davon zur Verfügung stehen.

Die Dokumentation des Frameworks ist leider nicht sehr hilfreich. Nicht nur dass sie schwer verständlich ist (es scheint als wurde sie mit dem Google-Übersetzer ins Englische übersetzt), teilweise fehlen auch wichtige Informationen in den sowieso schon spärlichen Beispielen.

Ebenso kryptisch wie die Dokumentation sind auch die Fehlermeldungen. Die Arbeit mit JMapper ist zudem alles andere als intuitiv. Es kam sogar vor, dass Objekte vom falschen Typ in einer Collection gelandet sind (die beinhalteten Objekte wurden also nicht zum Zieltyp der Collection gemappt, sondern lediglich im Zielobjekt in eine neue Collection geschrieben), so dass es beim Auslesen der Objekte aus der Collection zu einer ClassCastException kam.

JMapper kam somit für einen Einsatz in unserem Projekt nicht in Frage.

MapStruct

MapStruct war nach JMapper das nächstsperformanteste Framework. Die Mapping-Konfiguration wird hier in einem Interface oder einer abstrakten Klasse definiert. Dazu wird einfach eine abstrakte Methode mit entsprechender Signatur deklariert (Ursprungstyp als Parameter sowie Zieltyp für den Rückgabewert). Mappings zwischen verschieden lautenden Attributnamen werden mit Hilfe von Annotationen konfiguriert, in der jeweils die Namen von „source“- und „target“-Attribut angegeben werden, wobei auch Verschachtelung möglich ist. Weiterhin können mittels „Expressions“ beliebige Methoden aufgerufen werden. Während des Kompiliervorgangs wird dann eine Java-Datei generiert, die die Implementierung des Interfaces (bzw. im Fall einer abstrakten Klasse eine abgeleitete Klasse) mit den konkreten Mapping-Methoden enthält. Debuggen ist somit wie gewohnt möglich. Das Generieren der Java-Klassen funktioniert über einen Annotation Processor, der beispielsweise auch in den Maven-Build mit eingehängt werden kann.

Einfache Typkonvertierungen nimmt MapStruct automatisch vor. Für Konvertierungen komplexerer Datenstrukturen muss wiederum jeweils eine Methode mit entsprechender Signatur hinzugefügt werden. Konvertierungen, die eine eigene Logik benötigen, können mit entsprechender Signatur einfach als Default-Methode im Interface (ab Java 8) bzw. als abstrakte Methode einer abstrakten Klasse implementiert werden.

Die Dokumentation ist sehr umfangreich, verständlich und gut strukturiert. Zudem gibt es Plugins für IntelliJ IDEA und Eclipse, die IDE-Features wie Code-Navigation oder Refactoring auf die String-Angaben innerhalb der Konfigurations-Annotationen ausweiten.

Auf Probleme stießen wir bei der Konvertierung vom Typ java.util.Date zu XMLGregorianCalender. Diese funktionierte zwar automatisch, doch mit sehr hohen Einbußen in der Performance, so dass zunächst kein Performancegewinn im Vergleich mit Dozer messbar war. Grund dafür war, dass für jede einzelne Konvertierung jeweils eine neue Instanz von DataTypeFactory erzeugt wurde, was jedoch recht ressourcenintensiv ist. Das Problem konnte mit einem eigens implementierten Mapper gelöst werden, in dem die DataTypeFactory gecached wird.

Ein weiterer Fallstrick war das Hinzufügen einer nicht-abstrakten Methode mit einem String sowohl als Eingabeparameter wie auch als Rückgabewert. Absicht war es, diese Methode an einzelnen Stellen via Expression aufzurufen. Allerdings wurde in der generierten Mapper-Klasse die Methode nicht nur aufgerufen wo durch Defininition in einer Expression beabsichtigt, sondern automatisch überall, wo sowohl Ursprungs- als auch Zielattribut vom Typ String waren. Hier konnten wir uns mit Qualifiern behelfen.

Fazit

Aufgrund der schlechten Dokumentation und der Fehleranfälligkeit fiel JMapper in unserer Evaluierung durch. Die Wahl fiel auf MapStruct, da es dieses Framework gut dokumentiert und einfach anzuwenden ist, und dessen großer Funktionsumfang alle unsere Anforderungen abdeckt.

 

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*Pflichtfelder

*