Embedded Glassfish – und der Fisch schwimmt!°°°

Der folgende Artikel ist Teil meines Buches, das Gerade im Entstehen ist und den Arbeitstitel – „Hands on Software“ trägt.

Embedded Glassfish

Die Verwendung von Eclipse WTP ist mit Sicherheit Geschmackssache. Mir ist das ganze System zu fehlerhaft und zu intransparent. Zu oft schlagen Deployments fehl, WTP deployt kaputte Artefakte in den Applicationserver oder Redeployments sind unvollständig.

Im folgenden wird daher ein anderer Ansatz vorgestellt, den ich bevorzuge. Der Glassfish-Applicationserver bietet die Möglichkeit, auch Embedded, also direkt aus dem Code heraus gestartet zu werden.

Zunächst jedoch bauen wir uns eine sehr einfache Webapplikation. Diese muss nicht mehr beinhalten als ein einfaches Servlet.


@WebServlet(
urlPatterns = { "/" })
public class EffectiveServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
super.doGet(req, resp);
resp.getWriter().write("Hello!!");
resp.flushBuffer();
}
}

Damit dieses Servlet kompiliert werden kann, muss die Java-EE-API im Classpath vorhanden sein. Naiver weise deklariere ich folgende Dependency in meinem Gradle- bzw. Maven-Buildfile.

 providedCompile "javax:javaee-api:6.0@jar"

oder als Maven-Dependency

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>6.0</version>
    <scope>provided</scope>
</dependency></pre>

Der embedded Glassfish lässt sich sehr einfach starten.

Hierzu erstellt man eine Klasse GlassfishStart.java im Ordner src/test/java.
Es handelt sich hierbei zwar nicht um einen Test, durch die Platzierung in src/test/java kann jedoch die Klasse innerhalb der IDE verwendet werden, ist aber nicht Teil des generierten War-Files.

public class GlassfishStart {

        public static void main(String[] args) throws Exception {
            GlassFishProperties gfProp = new GlassFishProperties();
            gfProp.setPort("http-listener", 9090);

            GlassFish glassfish = GlassFishRuntime.bootstrap().newGlassFish(gfProp);
            glassfish.start();
        }
}

Durch diese 4 Zeilen wird ein kompletter Glassfish-Applicationserver auf Port 9090 aus dem Code heraus gestartet.

Hierfür benötigt man zwingend die Embedded-Glassfish Klassen im Classpath.
Folgende Dependency im gradle-buildfile.

testCompile "org.glassfish.extras:glassfish-embedded-all:3.1.1"

oder als Maven-Dependency


<dependency>
<groupId>org.glassfish.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>

Durch den Scope testCompile sind die benötigten Klassen zwar in der IDE verfügbar, nicht jedoch zur Laufzeit im war-File.

Wenn man die Klasse startet ist die Enttäuschung jedoch erstmal groß. Folgende nichtssagende Exception erscheint in den Logs:

Exception in thread "main" java.lang.ClassFormatError:
Absent Code attribute in method that is not native or abstract in class file javax/validation/constraints/Pattern$Flag

Kennen Sie die Lösung bereits?

Die Lösung ist komplexer zu finden als gedacht. Das Problem ist folgende Abhängigkeit im Klassenpfad, die ich bereits zuvor definiert hatte.

 providedCompile "javax:javaee-api:6.0@jar"

Die javax:javaee-api ist die von Oracle bereitgestellte API für die Java EE Spezifikiation. Diese API besteht lediglich aus den als Schnittstellen deklarierten Klassen. Alle Klassen, die als Implementierung deklariert sind wurden entfernt. Dies schließt sowohl abstrakte Klassen als auch konkrete Implementierungen mit ein. Mit Hilfe dieser Abhängigkeit kann der Code zwar kompiliert, nicht jedoch zur Laufzeit ausgeführt werden. Wird dies trotzdem versucht, resultiert dies genau in der obigen Fehlermeldung.

Ist der Fehler gefunden ist die Lösung trivial. Statt der Abhängigkeit auf die von Oracle bereitgestellte API verwendet M. die Glassfish-eigene API. Hierfür ersetze ich die problematische Dependency mit der folgenden.

providedCompile 'org.glassfish:javaee-api:3.1'

oder als Maven Dependency

<dependency>
  <groupId>org.glassfish</groupId>
  <artifactId>javaee-api</artifactId>
  <version>3.1</version>
</dependency>

Beim nächsten Start erhalte ich wenigstens eine andere Fehlermeldung als zuvor.

java.lang.IllegalStateException: Provider already mapped glassfish:jsf:faces-servlet

Dies ist weniger schwierig zu lösen als das letzte Problem. Ein Blick auf den Classpath in Eclipse bringt folgendes zu Tage.

Zusammen mit der zuvor deklarierten Embedded-Glassfish Dependency sind die Glassfish-Klassen doppelt im Classpath vorhanden und werden auch doppelt registriert. Das ist zuviel für den Glassfish und er quittiert den Dienst. Diese Bibliothek wurde durch WTP automatisch in den Classpath eingefügt, lässt sich aber einfach über den Build Path entfernen.

Jetzt endlich lässt sich der Embedded-Glassfish starten.

Was jetzt noch getan werden muss ist das Deployable korrekt zu konfigurieren, da die Sourcen hierfür über das komplette Projekt verteilt sind.
Hierfür bietet die Glassfish-API das ScatteredArchive. Mit einem ScatteredArchive kann ein “virtuelles” War-File zur Laufzeit aufgebaut werden. Der Code hierfür kann so aussehen:

Deployer deployer = glassfish.getDeployer();
ScatteredArchive archive = new ScatteredArchive(appName, ScatteredArchive.Type.WAR);
//kompilierte Klassen hinzufügen.
            archive.addClassPath(new File("build", "classes/main"));
            deployer.deploy(archive.toURI());

Über archive.addClassPath können beliebige Dateien in den Classpath aufgenommen werden und stehen somit zur Laufzeit zur Verfügung.
Die Applikation ist nach dem Start direkt unter http://localhost:9090/effective erreichbar. Der Kontext-Root-Pfad kann als Konstruktor des ScatteredArchive angegeben werden und wurde hier mit dem Parameter appName befüllt.

Ein Gedanke zu „Embedded Glassfish – und der Fisch schwimmt!°°°

Hinterlasse eine Antwort zu Brian Antwort abbrechen