Startseite > Techblog > Artikel von
sdi

Das Bauen und Ausliefern von normalen und eigenständigen Java Anwendungen die nicht in einem Webcontainer oder Applikationserver laufen sollen kann sich als schwierig erweisen. Die größten Probleme beim Erzeugen der Releases sind dabei die folgenden Punkte:

  • es müssen die benötigten Bibliotheken aufgelöst und mit in des Release gepackt werden
  • die Java Applikation muss in den meisten Fällen auf dem Zielsystem als Dienst laufen (Deamon)
  • das Erstellen und Packen des Releases sollte automatisiert erfolgen

Das Maven 2 Plugin appassembler zielt auf diesen Anwendungsfall ab und ermöglicht

  • das automatisierte Erstellen von Skripten zur einfachen Ausführung der Applikation
  • das Erzeugen von Releases die es ermöglichen, die Java Anwendung als Dienst zu betreiben
  • die Erzeugung von Repositories die alle nötigen Bibliotheken für die Anwendung enthalten.

Die Konfiguration des Plugins ist aber leider nicht ganz so einfach da die Dokumentation eher schlecht ist und ich auch auf Bugs im Plugin selber gestoßen bin.

Im Folgenden ist  eine Konfiguration als Beispiel aufgelistet die eine Deamonapplikation erzeugt.  Um die Applikation als Dienst betreiben zu können wird vom Plugin  der weit verbreitete Java Service Wrapper eingesetzt.

Beispielkonfiguration

<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>appassembler-maven-plugin</artifactId>
 <configuration>
   <repositoryLayout>flat</repositoryLayout>
   <includeConfigurationDirectoryInClasspath>true</includeConfigurationDirectoryInClasspath>
   <target>${project.build.directory}/appassembler</target>
     <daemons>
       <daemon>
         <id>daemon-app</id>
         <mainClass>de.communardo.appassembler.Launcher</mainClass>
         <commandLineArguments>
           <commandLineArgument>start</commandLineArgument>
         </commandLineArguments>
         <platforms>
           <platform>jsw</platform>
         </platforms>
         <jvmSettings>
           <extraArguments>
             <extraArgument>-server</extraArgument>
           </extraArguments>
           <initialMemorySize>64M</initialMemorySize>
           <maxMemorySize>512M</maxMemorySize>
           <systemProperties>
             <systemProperty>property=value</systemProperty>
           </systemProperties>
         </jvmSettings>
         <generatorConfigurations>
           <generatorConfiguration>
             <generator>jsw</generator>
             <includes>
               <include>linux-x86-32</include>
               <include>linux-x86-64</include>
             </includes>
             <configuration>
               <property>
                 <name>configuration.directory.in.classpath.first</name>
                 <value>conf</value>
               </property>
               <property>
                 <name>set.default.REPO_DIR</name>
                 <value>lib</value>
               </property>
               <property>
                 <name>wrapper.logfile</name>
                 <value>logs/wrapper.log</value>
               </property>
               <property>
                 <name>wrapper.logfile.maxsize</name>
                 <value>10m</value>
               </property>
               <property>
                 <name>wrapper.logfile.maxfiles</name>
                 <value>5</value>
               </property>
               <property>
                 <name>wrapper.console.title</name>
                 <value>appassembler application</value>
               </property>
               <property>
                 <name>run.as.user.envvar</name>
                 <value>root</value>
               </property>
               <property>
                 <name>wrapper.working.dir</name>
                 <value>d:\</value>
               </property>
               <property>
                 <name>set.default.APP_BASE</name>
                 <value>d:\</value>
               </property>
             </configuration>
           </generatorConfiguration>
         </generatorConfigurations>
       </daemon>
     </daemons>
   </configuration>
   <executions>
     <execution>
       <id>create-release</id>
       <goals>
         <goal>create-repository</goal>
         <goal>generate-daemons</goal>
       </goals>
       <configuration>
         <assembleDirectory>${project.build.directory}/appassembler/jsw/daemon-app</assembleDirectory>
         <repoPath>lib</repoPath>
       </configuration>
       <phase>package</phase>
     </execution>
   </executions>
</plugin>

Parameter

mainClass Die auszuführende Klasse.
commandLineArguments Parameter die beim Aufruf mit übergeben werden sollen.
platforms Die Liste der Plattformen für die ein Release erzeugt werden soll, hier “jsw” (Java Service Wrapper).
jvmSettings Faßt Konfigurationsparameter für die Java VM zusammen wie z.B. Speichereinstellungen, zusätzliche System Properties die gesetzt werden sollen oder Parameter für den Aufruf der VM
includes Ermöglicht die optionale Definition der Zielumgebungen, hier linux 32 und 64 bit, kann auch weggelassen werden.
configuration.directory.in.classpath.first Legt fest, dass ein bestimmtes Verzeichnis mit in den Classpath der VM aufgenommen wird. Darin lassen sich gut Konfigurationsdateien ablegen die von der Applikation beim Betrieb gebraucht werden.
set.default.REPO_DIR Das Verzeichnis in denen sich die Bibliotheken befinden.
run.as.user.envvar Der Nutzer mit dem die Applikation ausgeführt werden soll, wichtig beim Betrieb in Linuxumgebungen.
wrapper.xxx Einstellungen für den Java Service Wrapper wie z.b. das Arbeitsverzeichnis, Anzahl der Logfiles usw.
assembleDirectory, repoPath Diese Parameter konfigurieren das Zielverzeichnis für das zu erstellende Repository. Leider wird bei den Standardwerten das Repository in einem anderen Verzeichnis erzeugt als der Java Service Wrapper, daher ist eine Anpassung nötig.
<goals>
<goal>create-repository</goal>
<goal>generate-daemons</goal>
</goals>
Erzeugt die definierten Deamons und ein Library Repository mit allen Abhängigkeiten des Projekts.

Kommentar Feed Trackback URL
sdi

Asynchrone Prozesse werden sehr häufig in Enterprise Applikationen eingesetzt um Abläufe zu beschleunigen. Um diese mit EJB 3.0 zu realisieren kam man um den Einsatz von Message Driven Beans und JMS nicht herum, da diese die einzige Möglichkeit darstellten asynchrone Aufrufe zu realisieren. Mit der Einführung von EJB 3.1 und der neuen Annotation @Asynchronous wird es nun möglich, Methoden direkt als asynchron zu deklarieren.

Was bedeutet das?

Bei einem Aufruf einer asynchronen Methode wird die Kontrolle sofort an den Client zurückgegeben, noch bevor der EJB Container den Aufruf an die Session Bean delegiert hat. Der Client ist somit nicht blockiert und kann mit der Ausführung fortfahren. (fire and forget Prinzip)

Wie werden Methoden als asynchron markiert?

Für die asynchrone Ausführung wurde die Annotation @Asynchronous eingeführt.

Die Annotation kann angewendet werden auf

  • eine Methode einer Beanklasse
  • eine Methode eines Local/Remote Interfaces
  • alle Methoden einer Klasse
  • alle Methoden eines Local/Remote Interfaces

Alternativ können Methoden auch per deployment Descriptor als asynchron definiert werden.

Welche Signatur haben asynchrone Methoden?

Der Rückgabewert asynchroner Methoden ist entweder void oder Future<V>, wobei V dem entsprechenden Rückgabetyp entspricht.

Beim Rückgabewert void darf die Methode keinerlei anwendungsspezifische Ausnahmen deklarieren.

Bei Rückgabe eines Futureobjektes hat der Client die Möglichkeit, das Ergebnis des Aufrufes abzufragen. Dabei kann es sich um ein reguläres Ergebnis oder um eine Ausnahme handeln.

Wie verhält es sich mit Transaktionen?

Bei Aufrufen von asynchronen Methoden ist zu beachten, dass der Transaktionskontext des Clients nicht übernommen wird.

  • Ist die Methode mit dem Transaktionsattribut REQUIRED markiert, wird immer eine neue Transaktion erzeugt. Sie verhält sich somit genauso als wenn sie mit REQUIRES_NEW markiert wäre.
  • Ist die Methode mit dem Transaktionsattribut MANDATORY markiert wird immer eine TransactionRequiredException Exception  erzeugt.
  • Ist die Methode mit dem Transaktionsattribut SUUPORTS markiert wird die Methode immer ohne Transaktion ausgeführt.

Mit der neuen Annotation wird es für den Entwickler einfacher asynchrone Prozesse zu realisieren. Er muss dafür nicht mehr auf JMS zurückgreifen und kann sich auf das Wesentliche konzentrieren.

Kommentar Feed Trackback URL
sdi

Die Realisierung einer Schnittstelle zu einem Ldap Respository ist nach wie vor mit einem erheblichen Implementierungsaufwand verbunden und gestaltet sich oft als sehr unflexibel z.B. gegenüber Änderungen am Ldap Schema.

Beispiel für ein Ldap Schema und einen Ldap Eintrag:

Im Folgenden wird eine Lösung vorgestellt, wie sich mit Hilfe eines Jdbc-Ldap Treibers und einem Persistenzframework relativ schnell eine flexible Ldap Schnittstelle realisieren lässt.

Kern der hier vorgestellten Lösung ist der Jdbc-Ldap Treiber. Dieser Treiber simuliert der Anwendung einen Jdbc konformen Treiber und erlaubt SQL ähnliche Anfragen bei der Abfrage des Ldap Servers. Basierend auf dem Treiber können anschließend Persistenzframeworks eingesetzt werden, welche den Treiber für die Abfragen nutzen und die zurückgegebenen Daten in Java Objekte übersetzen.

Verwendete Bibliotheken

Jdbc-Ldap Treiber 2.1 Download

iBatis Persistenzframework 2.3.4 Download

Springframework 2.5.6

Quellcode

Für das Auslesen der Nutzer aus dem Ldap Repository benötigen wir zunächst einmal eine Klasse die unseren Nutzer repräsentiert.

Als nächstes implementieren wir für das Auslesen der Nutzer ein Data Access Objekt (DAO).

Das Interface UserDao definiert nur eine Methode listAllUser. Diese gibt alle User als Liste zurück.

Die Klasse UserDaoImpl nutzt für den Zugriff auf die Nutzerdaten das iBatis Framework. Das Springframework bietet dafür Hilfsklassen an, wovon eine hier als Oberklasse verwendet wird (SqlMapClientDaoSupport).

Für die Konfiguration des iBatis Frameworks benötigen wir zwei Konfigurationsdateien (SqlMapConfig.xml und SqlMap.xml) in denen die SQL Statements und Objektmappings hinterlegt werden.

SqlMapConfig.xml

SqlMap.xml

Die Datei SqlMap.xml enthält die Definition des LDAP-SQL Statements mit dem die Nutzerdaten aus dem Ldap Repository ausgelesen werden und legt unsere User Klasse als Ergebnistyp fest.

Am Ende benötigen wir noch die Spring- und Datenbank Konfiguration um unsere Demo Applikation ausführen zu können.

Spring Application Context

database.properties

In der Datei database.properties werden für den Jdbc-Ldap Treiber die notwendigen Parameter hinterlegt.

Führt man nun die Anwendung aus und ruft die Methode listAllUser des UserDao aus wird eine Liste aller Nutzer des Ldap Repositories zurückgegeben.

Vorteile der Lösung:

  • Änderungen am Ldap Schema lassen sich einfach in den iBatis Konfigurationsdateien nachziehen
  • Einfaches Mapping der Ldap Einträge auf Java Objekte
  • SQL ähnliche Ldap Abfragen
  • Java Quellcode ist von dem zugrunde liegendem Ldap Repository unabhängig, d.h. ein Schwenk von einem Ldap Repository hin zu einer Datenbank wäre ohne großen Aufwand problemlos möglich

Nachteile:

Neben den aufgeführten Vorteilen der Lösung darf ein gravierender Nachteil der hier aufgezeigten Lösung aber nicht unerwähnt bleiben. Nach genauerem Test des hier verwendeten Jdbc-Ldap Treibers erwies er sich leider nicht als fehlerfrei. Base64 kodierte Ldap Attribute werden von dem hier verwendetem Treiber nicht korrekt dekodiert so dass er sich leider nicht für einen produktiven Einsatz verwenden lies.

Kompletter Quellcode der Demoanwendung: Demo Quellcode

Kommentar Feed Trackback URL

Tag Cloud

Unsere Themen

Kommentare

  • SharePoint_Team: Rückblick zum Treffen der .NET Usergroup Dresden am 24.02.2010: im #Communardo #Techblog...
  • TorstenHu: Rückblick zum Treffen der .NET Usergroup Dresden am 24.02.2010: im #Communardo #Techblog...
  • SharePoint_Team: Neuer Blogpost zur #BastaCon im #Communardo #TechBlog: http://tinyurl.com/yjqyqpb This comment was...
  • SharePoint_Team: Nur noch etwa 1 Stunde, dann beginnt die .NET Usergroup… http://bit.ly/dxDoKg This comment was...
  • SharePoint_Team: RT @TorstenHu: ViS is waiting for an operation oder Warum Copy & Paste schlecht ist: #Communardo...

Twitter