EnterpriseTales [Kolumne]

Wofür braucht man in Java EE 7 eigentlich noch EJBs?

Arne Limburg, Lars Röwekamp

© Shutterstock/Maksim Kabakou

Seit Java EE 6 gibt es mit CDI einen zweiten Komponentenstandard neben EJB. Dieser ist dabei auch noch deutlich leichtgewichtiger. Wozu braucht man nach der Einführung von CDI also noch EJBs? „Für das Transaction Handling“ war die schnelle Antwort in Java EE 6, denn in CDI gibt es Transaktionsbehandlung schlicht und einfach nicht out of the box. Dies hat sich allerdings mit Java EE 7 grundlegend geändert. Mit der @Transactional-Annotation, die aus der JTA-Spec kommt, steht deklaratives Transaktionsmanagement nun nahezu allen Java-EE-Komponenten zur Verfügung und damit auch CDI Beans. Wozu braucht man also jetzt noch EJBs? Und reicht das Transaction Handling mit @Transactional aus, um EJBs zu ersetzen? Mit diesen Fragen und deren Antworten werden wir uns in dieser Kolumne näher beschäftigen.

Seit Java EE 7 und JTA 1.2 gibt es die @Transactional-Annotation. Bei ihr handelt es sich um eine Interceptor-Annotation. Die gute Nachricht ist, dass der zugehörige Transactional Interceptor gleich mit spezifiziert wurde. Die Annotation kann 1:1 so eingesetzt werden wie die Annotation @TransactionAttribute, mit dem Unterschied, dass letztere nur an EJBs funktioniert, wohingegen @Transactional an jeder CDI Bean (und weiteren Java-EE-Komponenten) ihren Dienst tut. Mit @Transactional werden also EJBs zur Transaktionsbehandlung tatsächlich überflüssig. Die neue Freiheit im Bereich Transaktionsbehandlung hat allerdings auch ihre Pitfalls, vor allem im Zusammenspiel mit JPA, wie wir später sehen werden. Zudem gibt es weitere Bereiche, in denen der Einsatz von EJBs nach wie vor sinnvoll ist. Zwei davon werden wir jetzt näher betrachten, und zwar die Themen Remoting und Security.

Remoting

Remoting bezieht sich auf die Kommunikation von Applikationen zwischen verschiedenen JVMs. Lässt man dabei die Ansätze loser Kopplung (Web Services, RESTful Services) einmal außen vor, geht eigentlich kein Weg an EJBs vorbei. Zwar ist es prinzipiell möglich, eine RMI-Verbindung auch programmatisch ohne EJB aufzubauen, der Aufwand dafür steht aber in keinem Verhältnis zum Nutzen. Je nachdem, ob es sich um synchrone oder asynchrone Kommunikation handelt, bekommt man mit EJBs die sendende und/oder die empfangende Seite praktisch geschenkt. Bei synchroner Kommunikation reicht es, der EJB ein Remote-Interface zu verpassen und schon können die Methoden des Remote-Interface von einer EJB aus einer anderen JVM heraus aufgerufen werden. Ein Remote-Interface ist ein Interface, das mit @Remote annotiert ist. Möchte oder kann man das Interface nicht mit @Remote annotieren, kann man alternativ @Remote auch als Annotation an der EJB verwendet werden, um das Remote-Interface über den value() der Annotation, also via @Remote(MyRemoteInterface.class) anzugeben. Der Aufrufer in der anderen JVM kann sich die Remote-EJB dann über @EJB injizieren lassen, wobei der JNDI-Name angegeben werden muss. Dies kann entweder direkt an der Annotation geschehen oder über das ejb-ref-Element im Deployment Descriptor. Bei asynchroner Kommunikation empfiehlt sich der Einsatz einer JMS-Queue. Auch hier helfen EJBs erheblich weiter. Mit Message-driven Beans lässt sich das Empfangen von Nachrichten nämlich komplett über Annotationen konfigurieren.

Security

Setzt man auf die Securitymechanismen des Java-EE-Containers, so landet man recht schnell bei rollenbasierter Security. Auch hier gibt es bei EJBs ein (bisher) exklusives Feature, die methodenbasierte Security. In EJBs können Methoden oder Klassen mit @RolesAllowed({„myRole1“, „myRole2“}) annotiert werden, um festzulegen, welche Rollen die besagte Methode aufrufen dürfen. Die Überprüfung, ob der aktuell angemeldete Benutzer eine der benötigten Rollen innehat, übernimmt dann der Container. Gegebenenfalls wirft er eine SecurityException und verhindert so den unauthorisierten Zugriff auf die Methode. Zwar gibt es einen solchen Mechanismus in CDI bisher nicht, er kann aber mit CDI-Bordmitteln recht leicht nachgebaut werden (siehe Listing 1, Interceptor-Eintrag in der beans.xml nicht vergessen!). Praktischerweise kann man sich seit CDI 1.1 den aktuellen HttpServletRequest injizieren lassen, aus welchem die aktuellen Securityinformationen ausgelesen werden können.

@InterceptorBinding
@Target(METHOD)
@Retention(RUNTIME)
public @interface RolesAllowed {
  @NonBinding String[] value();
}

@RolesAllowed({"any"}) @Interceptor
public class RolesAllowedInterceptor {
  @Inject
  private HttpServletRequest request;
  @AroundInvoke
  public Object checkRoles(InvocationContext context) throws Exception {
    RolesAllowed rolesAllowed
      = context.getMethod().getAnnotation(RolesAllowed.class);
    if (rolesAllowed != null) {
      boolean accessDenied = true;
      for (String role: rolesAllowed.value()) {
        if (request.isUserInRole(role)) {
          accessDenied = false;
          break;
        }
      }
      if (accessDenied) {
        throw new SecurityException();
      }
    }
    return context.proceed();
  }
}

Transaktionsbehandlung und JPA

Wie bereits geschrieben, werden mit @Transactional annotierte Methoden von allen möglichen Java-EE-Komponenten (und nicht nur von EJBs) innerhalb einer Transaktion ausgeführt. Wie spielt das aber mit JPA zusammen? Bei EJBs war es ja so, dass ein EntityManager, der über @PersistenceContext injiziert wurde, immer nur innerhalb der aktuellen EJB-Methode (=Transaktionsklammer) geöffnet war. Außerhalb führte ein Zugriff auf den EntityManager zu einer Exception. Die gute (oder schlechte) Nachricht ist: Das bleibt auch ohne EJBs so. „Schuld“ ist ein neu eingeführter CDI-Scope: @TransactionScoped. Dieser Scope wird, wie die @Transactional-Annotation, von der JTA-Spec spezifiziert. Er ist, wie der Name schon sagt, aktiv, solange eine Transaktion aktiv ist. Ein EntityManager vom Typ TRANSACTIONAL befindet sich im Transaction Scope und wird daher geschlossen, sobald die Transaktion beendet wird. Benötigt man einen EntityManager, der das Transaktionsende überlebt, muss man diesen selbst erzeugen und über die CDI-Annotation @Produces in einem anderen Scope (z. B. @RequestScoped) zur Verfügung stellen. Möglich wird dies, indem man sich über @PersistenceUnit eine EntityManagerFactory injizieren lässt und dort programmatisch createEntityManager aufruft.

Verwendet man einen solchen EntityManager in seiner Applikation, kann es allerdings zu seltsamen Effekten kommen und zwar immer dann, wenn es darum geht, Daten zu speichern. Die Frage ist: Wann ist der EntityManager mit der aktiven Transaktion verbunden und wann nicht? Bei dem oben beschriebenen @TransactionScoped EntityManager ist die Frage leicht zu beantworten: Er ist immer verbunden. Bei dem EXTENDED EntityManager, der auf die beschriebene Weise über die EntityManagerFactory programmatisch erzeugt wird, ist das komplizierter. Er verbindet sich nämlich nur dann mit einer Transaktion, wenn diese bereits zum Zeitpunkt des Öffnens des EntityManagers aktiv ist. Ist zu diesem Zeitpunkt keine Transaktion aktiv, verbindet sich der EntityManager nie automatisch mit einer Transaktion. Dies muss dann immer explizit über die Methode joinTransaction() des EntityManagers geschehen. Um sicherzugehen, dass dies innerhalb einer Transaktion automatisch geschieht, muss man also selbst Hand anlegen, indem man einen zweiten Interceptor für die @Transactional-Annotation definiert, der den aktuellen EntityManager mit der aktiven Transaktion verbindet (siehe Listing 2 und hier). Wichtig ist dabei, dass dieser Interceptor hinter dem, vom Server bereitgestellten Transactional Interceptor aufgerufen wird, damit die Transaction bereits aktiv wird. Erreichen kann man dies durch die @Priority-Annotation, die auch mit Java EE 7 neu eingeführt wurde.

@Transactional(REQUIRED)
@Interceptor
@Priority(Interceptor.Priority.PLATFORM_BEFORE + 201)
public class JoinTransactionInterceptor {
  @Inject
  private EntityManager entityManager;
  @AroundInvoke
  public Object joinTransaction(InvocationContext context) {
    if (!entityManager.isJoinedToTransaction()) {
      entityManager.joinTransaction();
    }
    return context.proceed();
  }
}

Fazit

Es gibt nach wie vor Anwendungsfälle, in denen EJBs auch in Java EE 7 sinnvoll sind. Die hier vorgestellten sind Remoting und Security. Am Beispiel von Security haben wir gezeigt, dass es aber häufig recht leicht möglich ist, diese durch generellere Ansätze (die auch für CDI Beans gelten) zu ersetzen. Im Fall der Transaktionsdeklaration ist dies mit @Transactional bereits geschehen und auch in den Standard eingeflossen. Dass die Integration mit CDI und JPA teilweise noch etwas holprig ist, haben wir aber auch am Beispiel des EXTENDED EntityManagers gezeigt. Hier muss auch Hand angelegt werden. In solchen Situationen zeigt sich, dass EJBs einen Standard darstellen, der bereits ausgereift ist und sich etabliert hat. Neue Standards, wie @Transactional benötigen oft ein paar Iterationen, bis sie komplett rund sind. Aber die Spezifikationskomitees sind fleißig, und mit der Spezifikation von Java EE 8 wurde bereits begonnen. Bis dahin hilft „Enterprise Tales“, um auf Pitfalls hinzuweisen und Möglichkeiten aufzuzeigen. In diesem Sinne: „Stay Tuned“!

Aufmacherbild: Pixeled word Enterprise on digital screen 3d render von Shutterstock / Urheberrecht: Maksim Kabakou

Geschrieben von
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Kommentare
  1. Frank Seidinger2015-11-26 13:51:40

    Es gibt noch einen weiteren Grund. CDI und EJB laufen in zwei Containern, die unabhängig voneinander konfiguriert werden können. Ich habe damit z.B. zwei Skalierungsebenen. Eine für die Präsentationsschicht und eine für die Fachschicht einer Anwendung.

  2. Markus2016-01-15 08:44:25

    Gibt es denn einen Grund unbedingt KEINE EJBs zu verwenden. Letztendlich ist es nur eine andere Annotation (@Stateless) und der Unterschied im Resource-Overhead ist in der Regel vernachlässigbar.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.