vert.x: asynchrones und Event-getriebenes Java-Webframework

vert.x ist ein neues Webframework, das sich bei den Ideen von Node.js bedient und darüber hinaus eine Alternative zum Java-EE-Programmiermodell darstellen will, die zudem eine eigene Ablaufumgebung enthält.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen
Lesezeit: 11 Min.
Von
  • Eberhard Wolff
Inhaltsverzeichnis

vert.x ist ein junges Webframework, das sich bei den Ideen von Node.js bedient und darüber hinaus eine Alternative zum Java-EE-Programmiermodell darstellen will, die zudem eine eigene Ablaufumgebung enthält.

Auf den ersten Blick scheint vert.x nur ein neues Webframework zu sein – und davon gibt es ja im Java-Land reichlich. Aber der zweite Blick zeigt: vert.x lässt kaum einen Stein auf dem anderen und bietet einen völlig neuen Ansatz für die Entwicklung – nicht nur von Webanwendungen. Nur das Fundament bleibt: vert.x setzt auf die Java Virtual Machine. Aber schon bei den Programmiersprachen unterscheidet sich vert.x von vielen anderen Ansätzen. Neben Java unterstützt es derzeit Groovy, JavaScript mit dem Rhino-Framework und schließlich Ruby durch JRuby.

Die Nutzung dieser Programmiersprachen ist im Prinzip mit jedem Java-Framework möglich. Aber vert.x bietet darüber hinaus einen Wrapper um das Java-Framework, sodass es in jeder Sprache wie ein natives Framework wirkt, das genau für diese Sprache geschrieben wurde.

Auch zeitgemäßen Anforderungen an ein Webframework stellt sich vert.x: Zunehmend mehr Logik von Webanwendungen wird in JavaScript implementiert und läuft im Browser. Daher liegt der Fokus der Frameworks nicht mehr im Rendern von HTML-Seiten und dem Verarbeiten von Formulardaten. Der Server muss statische Seiten für HTML und JavaScript ausliefern und außerdem Verbindungen zu JavaScript-Clients mit WebSockets verarbeiten können.

vert.x unterstützt zusätzlich noch SockJS, eine weitere Bibliothek für die Kommunikation mit JavaScript. Sie erlaubt die direkte Kommunikation von JavaScript mit dem Server. Zugriff auf Daten erfolgt meistens über eine REST-Schnittstelle, die wiederum Funktionen des HTTP-Protokolls nutzt. Eine Abstraktion von HTTP ist daher für Webframeworks weniger wichtig als in der Vergangenheit.

Protokolle wie WebSockets und SockJS führen zu neuen Problemen. Für klassische Webanwendungen gilt, dass eine Verbindung meistens nur für einen Request und eine Response offen ist. Das ist typischerweise eine recht kurze Zeit, in der die Verbindung aber auch recht aktiv ist. Ihr wird meistens ein Thread zugeordnet. Bei WebSockets und SockJS gibt es für jeden Client immer eine offene Verbindung, über die dann aber nur ab und an kommuniziert wird. Ähnliche Herausforderungen gibt es bei Anwendungen mit vielen mobilen oder eingebetteten Clients. Jeder Verbindung einen Thread zuzuordnen führt zu einer großen Anzahl Threads – die allerdings meistens nicht aktiv sind. Threads benötigen aber beispielsweise einige Daten für das Verwalten, und auch das Umschalten zwischen Threads kostet Zeit. Diesen Overhead gilt es zu vermeiden.

vert.x geht deswegen einen anderen Weg: Es nutzt asynchrone Verarbeitung. Der Programmierer registriert bei bestimmten Events aufgerufene Callbacks. Der eigene Code liest also keine Daten, sondern wird aufgerufen, wenn neue Daten vorliegen. So benötigt das Framework nur einen Thread für viele Verbindungen. Er bearbeitet einfach die Callbacks für alle Verbindungen dann, wenn neue Daten zu verarbeiten sind. Das verdeutlicht das folgende Listing einer Webanwendung:

public class HttpServerExample extends Verticle {
public void start() {
vertx.createHttpServer().requestHandler(
new Handler<HttpServerRequest>() {
public void handle(HttpServerRequest req) {
req.response.headers().
put("Content-Type", "text/html; charset=UTF-8");
req.response.
end("<html><body><h1>Hallo!</h1></body></html>");
}
}).listen(8080);
}
}

Verticle ist eine Oberklasse. Der Name "Verticle" bezeichnet eine Anwendung in vert.x. Bei ihrem Start registriert die Anwendung ein Callback, der bei jedem HTTP Request aufgerufen wird. Als Antwort liefert der Callback ein HTML-Dokument aus. Schließlich wird ein HTTP-Server mit dieser Logik auf Port 8080 gestartet.

Dieser Ansatz zieht ein Problem nach sich: Wenn der Thread für die Verarbeitung eines Request länger benötigt, weil er beispielsweise auf I/O warten muss, beeinträchtigt das auch die anderen durch denselben Thread verwalteten Verbindungen. Daher ist vert.x vollständig asynchron: Die genutzten Methoden schreiben nicht die Ausgabe in den Socket, sondern das passiert später – wie eben auch der Handler irgendwann nach dem Vorliegen des HttpRequest aufgerufen wird. Dadurch lässt sich ein Blockieren solcher I/O-Funktionen ausschließen, sodass keine Verbindung übermäßig lange warten muss, wenn neue Daten anliegen.

vert.x verwaltet solche Aktionen als Events, die das Framework in einem Event Loop genannten Thread abarbeitet. Außerdem kann vert.x mehrere Event Loops parallel laufen lassen. Ihre Anzahl sollte auf die Anzahl der Prozessor-Kerne im System beschränkt bleiben, da sie praktisch ständig aktiv sind und daher einen Core auslasten.

vert.x muss aufgrund dieser Eigenschaften mit Java-Standards wie Servlets brechen, die diesem Programmiermodell entgegenstehen. Es gibt allerdings die Möglichkeit, mit sogenannten Worker Verticles Aufgaben auszuführen, die längere Zeit in Anspruch nehmen und so einen Event Loop blockieren würden. Dazu zählt vor allem die Nutzung blockierender APIs, die dem asynchronen Modell von vert.x nicht entsprechen – beispielsweise Datenbank-APIs. Die Worker Verticles werden mit Threads aus einem eigenen Thread Pool ausgeführt und sind dadurch von den Event Loops weitgehend getrennt.

Für das Programmiermodell hat der Ansatz wesentliche Auswirkungen: Der Entwickler nutzt viel mehr Callbacks, was zumindest bei Java-Code nicht immer einfach zu verstehen ist. In anderen Sprachen kommen hier Closures oder Funktionen zum Einsatz, was wesentlich einfacher ist.

Außerdem ist ein Verticle immer einem Thread zugeordnet, sodass in einem Verticle immer nur ein Thread aktiv ist. Dadurch muss man wesentlich weniger nebenläufig programmieren, wodurch der Code wesentlich einfacher ist.

Die erwähnten WebSockets funktionieren genauso wie der gezeigte HTTP-Ansatz:

public class WebSocketsEcho extends Verticle {

public void start() {
vertx.createHttpServer()
.websocketHandler(new Handler<ServerWebSocket>() {
public void handle(final ServerWebSocket ws) {
ws.dataHandler(new Handler<Buffer>() {
public void handle(Buffer data) {
ws.writeTextFrame(data.toString());
}
});
}
}).requestHandler(new Handler<HttpServerRequest>() {
public void handle(HttpServerRequest req) {
if (req.path.equals("/"))
req.response.sendFile("websockets/ws.html");
}
}).listen(8080);
}
}

Im Wesentlichen registriert der Entwickler auch hier entsprechende Handler – für HTTP Requests und WebSockets-Anfragen. So können JavaScript-Clients mit der Logik auf dem Server problemlos kommunizieren. Die in vert.x implementierten Konzepte funktionieren also bei vielfältigen Kommunikationsszenarien. Außerdem kann vert.x für JavaScript-Clients die Daten als JSON (JavaScript Object Notation) ausliefern und bietet dafür eine Bibliothek.