Donnerstag, 30. April 2015

Bean-Mapping mit MapStruct

Am vergangenen Wochenende habe ich auf dem MATHEMA Campus einen Vortrag über MapStruct gehalten. MapStruct ist ein Annotations-basierter Code-Generator für Mappings zwischen beliebigen Java-Beans und kann damit hervorragend zum Umkopieren der Daten zwischen den diversen Domänen-Modellen einer Anwendung (JPA, JAXB, REST, sonstige DTOs/POJOs ... ) genutzt werden.

Der generierte Mapping-Code nutzt keine Reflection, sondern Getter- und Setter-Aufrufe – und erreicht damit die Geschwindigkeit von handgeschriebenem Mapping-Code. Ganz nebenbei ist der generierte Code lesbar und verständlich. Das ist wichtig, falls der Projektverantwortliche Angst hat, ein weiteres Werkzeug ins Projekt einzubinden – zur Not stellt man den generierten Code unter Versionskontrolle und pflegt ihn später von Hand weiter (wer einmal MapStruct verwendet hat, wird das aber nicht wollen ;-).

MapStruct wird als Annotations-Prozessor eingebunden, d.h. der Mapping-Code wird beim Aufruf von javac (bzw. bei jedem Speichern z.B. in Eclipse) generiert. Das Prozessor-JAR wird dabei nur zur Compile-Zeit benötigt. Zur Laufzeit ist bei einem Komponentenmodell wie CDI gar kein JAR als Abhängigkeit nötig, ohne Komponentenmodell nur ein winziges JAR < 20 K.

Folgender Code reicht, um den Mapping-Code zwischen einer Kunde-Entity und einem Kunde-DTO generieren zu lassen:

@Mapper(componentModel = "cdi")
public interface KundeMapper {

  @Mapping(target = "geburtsdatum", format = "dd.MM.yyyy")
  KundeDTO jpaEntity2RestDto(Kunde kunde);
}

MapStruct erzeugt daraus in etwa folgenden Code:

@Generated("org.mapstruct.ap.MappingProcessor")
@ApplicationScoped
public class KundeMapperImpl implements KundeMapper {

  public KundeDTO jpaEntity2RestDto(Kunde kunde) {
    // null-Checks
    // Ziel-Objekt-Erzeugung
    // Getter-/Setter-Aufrufe
    // ...
  }
}

Der standardmäßig generierte Code ist oft schon genau das, was man benötigt. Ansonsten ist so ziemlich alles konfigurierbar: Unterschiedliche Property-Namen in Quell- und Ziel-Bean, unterschiedliche Typen, die Objekt-Fabriken zur Ziel-Objekt-Erzeugung... Zudem können vorhandene, handgeschriebene Mapper eingebunden werden. Und Objekt-Graphen werden selbstverständlich genauso gemappt wie Collections, Maps, Arrays... Für JAXB, Joda-Time und Java 8 Date&Time stehen automatische Mappings zur Verfügung.

Man merkt, dass dieser Mapper mit vielen Praxis-Problemen im Hinterkopf entwickelt wurde. Und man fragt sich relativ schnell, warum man nur so lange das Bean-Mapping von Hand oder mit Reflection-Bibliotheken durchgeführt hat ;-)

Mindestvoraussetzung für MapStruct ist Java 6 – aber da heute der letzte Tag der kostenfreien Java-7-Updates (und ab morgen nur noch Java 8 "aktuell") ist, sollte das kein Problem darstellen.

Die Folien zu meinem Vortrag finden sich hier.

Freitag, 13. März 2015

Scope-Events in CDI 1.1

Wer in einer Java-Webanwendung mitbekommen möchte, wann die Anwendung fertig bereitgestellt ist (z.B. um den Deployment-Timestamp zu ermitteln und anzuzeigen), hat dazu diverse Möglichkeiten. Eine lang verfügbare Lösung ist ein ServletContextListener, seit Java EE 6 kann man auch eine @Singleton-@Startup-EJB verwenden. Mit CDI 1.1 in Java EE 7 haben wir nun eine weitere Variante, ein @Initialized-Event für den Application-Scope (d.h. für den ServletContext):

import javax.inject.Named;
import javax.enterprise.event.Observes;
import javax.enterprise.context.*;

@Named
@ApplicationScoped
public class DeploymentInfoBean {

  private Date deploymentTimestamp;

  public Date getDeploymentTimestamp() {
    return deploymentTimestamp;
  }

  void deploymentFinished(
    @Observes
    @Initialized(ApplicationScoped.class)
    ServletContext context) {

    deploymentTimestamp = new Date();
  }
}

Wer nur am Zeitpunkt interessiert ist und keine Werte aus dem ServletContext benötigt, kann sich als Parameter auch einfach ein Object übergeben lassen und beseitigt damit die Abhängigkeit zu javax.servlet.

Man kann sich nicht nur über die fertige Initialisierung eines Scopes informieren lassen, sondern auch über die Beendigung eines Scopes – und so beispielsweise Anfang und Ende von HTTP-Sessions überwachen:

public class SessionWatcher {

  void sessionCreated(
    @Observes
    @Initialized(SessionScoped.class)
    HttpSession session) {
      ...
  }

  void sessionDestroyed(
    @Observes
    @Destroyed(SessionScoped.class)
    HttpSession session) {
      ...
  }
}

Freitag, 6. März 2015

Java in OS X 10.10 "Yosemite"

Wie auch schon die Vorgängerversion wird OS X 10.10 ohne vorinstalliertes Java 6 ausgeliefert. Bei einem Update wird eine vorhandene Java-6-Installation aus /System/Library/Java/JavaVirtualMachines gelöscht. Das alles ist eigentlich kein Problem, denn Java 6 ist eh veraltet, seit Oracle vor zwei Jahren den kostenlosen Support für Version 6 eingestellt hat – und in /Library/Java/JavaVirtualMachines installierte Versionen von Java 7 und Java 8 bleiben bei einem Update auf OS X 10.10 erhalten.

Manche Programme benötigen aber noch Java 6 zum Starten. Yosemite weist mit einem Dialog darauf hin:
Mit der Schaltfläche "Weitere Infos" gelangt man zur Download-Seite von Java für OS X 2014-001, mit dem in Mac OS X 10.7 bis OS X 10.10 Java 1.6.0_65 installiert werden kann. Dies ist dieselbe Java-Version, die Apple auch schon mit "Java für OS X 2013-005" ausgeliefert hat – nur ein geringfügig aktuellerer Build.