UML mit PlantUML

PlantUML ist ein ein GPL Tool zur Erstellung von folgenden UMLDiagramm Typen aus reinen Text-Dateien:

  • Klassendiagramm (class diagram)
  • Sequenzdiagramm (sequence diagram)
  • Objektdiagramm (object diagram)
  • Anwendungsfalldiagramm (use case diagram)
  • Aktivitätsdiagramm (activity diagram)
  • Komponentendiagramm (component diagram)
  • Zustandsdiagramm (state diagram)
  • Deploymentdiagramm (deployment diagram) BETA (Stand 1. April 2017)
  • Zeitverlaufsdiagramm (timing diagram) BETA (Stand 1. April 2017)

Verwendbar ist PlantUML entweder online über einen Demo-Bereich oder lokal über eine JAR-Datei. Für ein lokales Ausführen braucht man Java und die Graphviz-Software.

Zusätzlich bietet das Plugin PlantUML integration von Eugene Steinberg (erhältlich für alle IntelliJ IDEA basierenden Produkte) eine Integration in meine Lieblings-IDE. Features sind unter anderem:

  • Syntax Highlighting
  • Autocompletion bereits definierter Elemente
  • Parallele Darstellung von Definition und Ergebnisgrafik

Kleines Beispiel aus meinem aktuellen Projekt:

@startuml

package modes {
    interface Mode {
     + handle_input(code): Mode
     + display(device)
     + handle_mpd(data)
     + activate()
    }

    class ModeProxy {
      current_mode
     + handle_input(code): Mode
     + display(device)
     + handle_mpd(data)
    }

    class MenuMode <<Singleton>> {

    }

    ModeProxy ..|> Mode

    MenuMode ..|> Mode
    MenuMode -->"*" Mode : "topics"
}
@enduml

und das zugehörige Bild:

Ergebnis der Demo Datei
Ergebnis der Demo Datei

Fazit: Ein gutes Tool mit einigen Vorteilen (z.B. einfache Versionierung der Eingabedateien) unter einer der richtigen Lizenzen, welches nicht nur in IntelliJ und Co verwendet werden kann sondern in einer Vielzahl weiterer Tools.

Open Source Hausautomatisierung im Vergleich

Inzwischen sind alle Heizkörper und auch fast alle Lichter in der heimischen Wohnung über die Weboberfläche der eQ-3 CCU2 fernbedienbar und auch die @Home App funktioniert soweit gut. Die Produktpflege und Dokumentation von eQ3 würde ich vorbildlich nennen: auf der Downloads-Seite findet man sowohl die jeweils aktuelle CCU2 Firmware (inklusive Change Log) als auch die Dokumentation der Schnittstellen (HomeMatic XML-RPC).

Vorhandene Komponenten

Folgende Homematic und Homematic  IP Produkte sind aktuell im Einsatz:

  • HM-CC-RT-DN – Funk-Heizkörperthermostat
  • HM-LC-SW1-FM – Schalt-Aktor 1-fach Unterputz (ohne Taster)
  • HM-LC-SW2-FM – Schalt-Aktor 2-fach Unterputz (ohne Taster)
  • HM-LC-Sw1PBU-FM – Schalt-Aktor 1-fach Unterputz (für Markenschalter)
  • HM-PB-2-WM55 – 2-fach-Funk-Wandtaster
  • HmIP-BSM – Schalt-Aktor 1-fach Unterputz (Homematic IP, für Markenschalter)
  • HMIP-PSM – Schalt- und Messsteckdose (Homematic IP)

Allerdings kann man (ohne Kritik an eQ-3 zu üben) naturgegeben nur deren Produkte verknüpfen und steuern. Weitere bereits vorhandene Geräte sollten ebenso angesprochen werden können:

Zum Glück gibt es ausreichend Softwarelösungen für dieses Problem. Vor dem Aufzählen der Kandidaten sollen allerdings Vergleichskriterien festgelegt werden.

Vergleichskriterien

  1. Die Software muss quell-offen und frei sein (OpenSource)
  2. Dokumentation auf Englisch bzw. Deutsch und in ausreichenden Qualität und Detailtiefe vorhanden (Dokumentation)
  3. Größe der Community
  4. Support für Komponenten von eQ-3 für Homematic und Homematic IP (Hardware-Unterstützung), da diese bereits vorhanden ist.
  5. Apps für iOS bzw. Android (Apps)
  6. Verwendete Programmiersprachen & Technologien (Technologie)
  7. Die Software muss Graphen zur historischen Analyse von Messwerten bieten (Graphen)
  8. Die Software muss über Regeln erweiterbar sein, z.B. Verknüpfung von Schalter, Reaktion aufs Verlassen der Wohnung (Regeln)

openHAB

openHAB basiert auf Java und OSGi (Equinox) und ist somit auf allen Plattformen lauffähig, auf denen Java angeboten wird (Linux, Windows, OS X,…). Laut Wiki sind auch ARM SBCs ausreichend.

openHAB verwendet intern einen Event Bus, um alle anderen Komponenten zu verbinden. Weitere wichtige Komponenten sind das openHAB Repository, welches den aktuell bekannten Zustand aller bekannten Items vorhält, und die Bindings, die als Abstraktionsschicht zwischen openHAB und den anzubindenden Geräten fungiert.

Über das openHAB Repository werden sowohl die diversen User Interfaces (WebUI, Apps für iOS, Android, Windows Phone, …) als auch die Verarbeitung der Automatisierungsregeln angebunden.

URLhttp://www.openhab.org/
OpenSourcehttps://github.com/openhab/openhab (EPL)
Dokumentationhttps://github.com/openhab/openhab/wiki
Dokumentation ausführlich für alle Module auf Englisch vorhanden, über github Wiki gut erweiterbar.
CommunityHohe Aktivität
Hardware-
Unterstützung
Breite Unterstützung für diverse Hardware und externe Software
Appsverfügbar für iOS, Android, Windows Phone und sogar Pebble
TechnologieJava / OSGi
GraphenCharts sind über Persistenzen für fast alle messbaren Größen möglich.
RegelnopenHAB ist über Regeln steuerbar. Diese sind in einer DSL zu erstellen und integrieren Java.

Home Assistant

Python 3 bildet die Basis für Home Assistant. Entsprechend bietet auch Home Assistant einen breiten Support für die gängigsten Betriebssysteme (Video-Tutorials existieren für Windows 10, OS X und Ubuntu). Auch Hinweise zum Durchführen einer Aktualisierung sind vorhanden. Installationshinweise für Raspberry Pi, Docker, Vagrant und auch Synology NAS sind vorhanden.

Auch Home Assistent nutzt architektonisch einen Bus, um die einzelnen Komponenten zu verbinden, eine Service Registry und eine State Machine zum Verwalten der Zustände der einzelnen Komponenten.

URLhttps://home-assistant.io/
OpenSourcehttps://github.com/home-assistant/home-assistant (MIT)
DokumentationCookbook für Anwender und Dokumentation für Entwickler
CommunitySehr hohe Aktivität
Hardware-Unterstützungüber 470 (Stand Version 0.33.0) verschiedene Komponenten (Hard- und Software), Homematic ist unterstützt, Homematic IP jedoch nicht
Appskeine, iOS App in Vorbereitung
TechnologiePython
GraphenGraphen sind an messenden Objekten vorhanden
Regelnmöglich, können in YAML definiert werden

fhem

fhem wird in Perl entwickelt und schont daher die benötigten Ressourcen  (sogar auf Fritz!Boxen ist ein Betrieb möglich). Eine Installation ist auch hier wieder auf den 3 großen Betriebssystemen möglich.

Für die Freunde eines Handbuchs hält die Community eine ausführliche Einführung bereit.

URLhttp://fhem.de//
OpenSourcehttps://sourceforge.net/p/fhem/code/HEAD/tree/trunk/fhem/ (GPL v2)
DokumentationAusführliches deutschsprachiges PDF, ausführliches gemischtsprachiges Wiki
CommunitySehr hohe Aktivität
Hardware-UnterstützungÜbersicht über unterstützte Hardware HmIP-Geräte noch nicht offiziell gelistet aber laut Forum möglich
Appsu.a. FHEMobile für iOS und andFHEM für Android
TechnologiePerl
GraphenGraphen sind über gnuplot möglich
RegelnRegeln in Perl geschrieben möglich

calaos

calaos ist ein französisches Projekt und setzt für die Steuerung auf KNX und DMX. Als Hardware wird hauptsächlich Wago unterstützt. Leider ist die Dokumentation hauptsächlich in Französisch und auch das Datum des letzten Eintrags aus dem Entwickler-Blog aus dem Februar 2015 lässt nichts gutes hoffen.

URLhttps://calaos.fr
OpenSourcehttps://github.com/calaos (GPL v3)
Dokumentationfranzösischsprachiges Forum, Wiki in Französisch und Englisch
Communitymoderate Aktivität
Hardware-UnterstützungEingeschränkt, lediglich WAGO, OneWire Komponenten, Zibase, GPIO und IoT Devices werden unterstützt (Quelle)
AppsCalaos Mobile für iOS, Calaos Home für Android
TechnologieC/C++
Graphenunbekannt
Regelnintegrierter LUA Support

domoticz

domoticz ist in C/C++ geschrieben und nativ verfügbar für Raspberry Pis, Windows, Linux, OS X und für einige NAS-Syteme. Als Scripting-Engine wird Lua verwendet, über die mit Blockly die Automatisierung vorgenommen wird. Leider Homematic nicht unterstützt, jedoch ist die Liste an unterstützter Hardware recht umfangreich.

URLhttps://domoticz.com/
OpenSourcehttps://github.com/domoticz/domoticz (GPL v3)
Dokumentationenglischsprachiges Handbuch und Wiki
Communityhohe Aktivität
Hardware-UnterstützungModerat, leider kein Homematic (Quelle)
AppsImperiHome für iOS und Android
TechnologieC/C++
GraphenJa, als Log in der UI
Regelnintegrierter Support für LUA, Bash, Perl, PHP und Python, Support für Blockly

openmotics

openmotics vereint ein Angebot von Soft- und Hardware, die unter eine OpenSource Lizenz gestellt wurde. Die Hardware wird auch im Shop verkauft. Allerdings existieren nur Module zum Einbau in Schaltschränke auf Hutschienen. openmotics eignet sich somit kaum zum nachträglichen Einbau sondern eher für Neubauten. Aber auch dann ist keine Integration weitere, fremder Komponenten vorgesehen.

URLhttps://www.openmotics.com/
OpenSourcehttps://github.com/openmotics (MIT)
Dokumentationenglischsprachiges Wiki
Communitykeine Aktivität (Quelle)
Hardware-Unterstützungnur spezielle openmotics Hardware (siehe Shop)
Appskeine bekannt
TechnologiePython
GraphenJa
RegelnJa

freedomotic

freedomotic bezeichnet sich als Open Iot Framework, versteht sich also nicht nur zur Heimautomatisierung, sondern versucht weiter zu gehen. Technologisch ist wie bei openHAB in Java Grundlage, es wird aber wohl auf OSGi verzichtet. Die verfügbaren Plugins sind im Marketplace verzeichnet.

Installationsanleitungen sind leider bisher nicht wirklich vorhanden, lediglich eine kurze Seite zum Raspberry Pi und Docker (Zugang über guest/guest, nur REST API) enthalten Text.

Der Einsatz von 2 getrennten Messaging Bussen ermöglicht hier zusätzlich noch das Clustering mehrerer freedomotic-Instanzen. Die verschiedenen Hardwaretypen und externen Services werden über Module angebunden.

URLhttp://freedomotic.com/
OpenSourcehttps://github.com/freedomotic/freedomotic (GPL v2)
Dokumentationenglischsprachiges Benutzerhandbuch
Communityunbekannt (Quelle)
Hardware-Unterstützungdiverse Hardware (siehe Shop), keine Hinweise auf Homematic
Appsunbekannt
TechnologieJava
Graphenunbekannt
RegelnÜber XML möglich

Bewertung

Eine abschließende Bewertung fällt schwer, jedoch werde ich freedomotic (fehlende Unterstützung für Homematic), openmotics (nur spezielle Hardware), domoticz (fehlende Unterstützung für Homematic, verwendete Programmiersprache) und calaos (stark eingeschränkte Hardwareunterstützung) aus den jeweilig genannten Gründen nicht weiter verfolgen.

Für

  • openHAB,
  • Home Assistant und
  • fhem

wird es weitere Artikel geben. Diese werden dann von hier verlinkt.

Ergänzung 7. Januar

Inhaltsverzeichnis eingefügt.

REST-Services mit Spring und Maven (Teil 1)

Dieser Teil der Serie über Webprogrammierung mit dem Spring-Framework beschäftigt sich mit Representational State Transfer oder kurz REST. Bei REST handelt es sich um ein Programmierparadigma für Webanwendungen, welches von Roy Fielding in seiner Dissertation im Jahr 2000 geprägt wurde.

Design-Prinzipien bei REST

Wichtiges Element einer REST-Schnittstelle sind Ressourcen. Eine Ressource ist hierbei jedes Element der API, welches sinnvollerweise einzeln angesprochen werden kann.

  • Adressierbarkeit der einzelnen Ressourcen. Die Adressen bleiben idealerweise über Lebensspanne der Resource konstant.
  • Darstellung der abgefragten Ressourcen in verschiedenen Repräsentationen, beispielsweise JSON für Javascript, HTML oder als Grafikdatei zur Anzeige im Browser
  • REST ist Zustandslos.Die Zustandslosigkeit einer REST-Schnittstelle bedingt sich durch das verwendete HTTP(s)-Protokoll. Viele der Vorteile wie das Verwenden von Caches und die Skalierbarkeit ergeben sich aus diese Zustandslosigkeit.
  • REST verwendet die durch HTTP bekannten Operationen wie GET, POST, PUT und weiteren um Manipulationen an den Ressourcen vorzunehmen.

Basisoperationen zur Umsetzung von CRUD sind:

  • POST zum Anlegen (Create) neuer Ressourcen. Die Daten zum Anlegen der Ressource sind hierbei im Body des Requests enthalten, als Antwort erhält man die angelegte Ressource. Diese Funktion hat Seiteneffekte, wiederholtes Ausführen erzeugt neue Ressourcen mit gleichen Eigenschaften.
  • GET zum Abfragen (Read) von Ressourcen. Die Daten werden in der gewählten Repräsentation als Antwortet geliefert. Diese Funktion hat keine Seiteneffekte und kann beliebig wiederholt werden.
  • PUT zum Aktualisieren (Update) von Resourcen. Die aktualisierten Werte befinden sich im Body des Requests und die Antwort zeigt den neuen Zustand der Ressource. Diese Funktion hat Seiteneffekte, wiederholte Ausführung ist möglich.
  • DELETE zum Löschen einer Ressource. Diese Funktion hat Seiteneffekte, wiederholte Ausführung ist nicht möglich.

REST mit Spring

Ausgehend vom ersten Teil der Serie seien nun hier die Unterschiede erläutert.

1) An der Datei spring-servlet.xml ergeben sich folgende Änderungen:

  • InternalResourceViewResolver wird nicht mehr benötigt, da wir keine JSP-Dateien mehr als Views verwenden, sondern MessageConverter für XML und JSON
  • MappingJacksonHttpMessageConverter wird neu eingeführt, um Ergebnisse von Methoden in JSON zu konvertieren. Das benötigt die Abhängkeit  org.codehaus.jackson:jackson-mapper-asl (siehe pom.xml)
  • Jaxb2RootElementHttpMessageConverter ermöglicht die Wandlung von Ergebnistypen in XML. Eine Abhängigkeit wird hier nicht benötigt.
  • AnnotationMethodHandlerAdapter wird durch die Angabe einer Property messageConverters  ergänzt. Dadurch wird die automatische Wandlung in entsprechende Representationen ermöglicht.

Insgesamt ergibt sich folgender neuer Block:

<bean id="jacksonMessageConverter" 
    class="...MappingJacksonHttpMessageConverter" />
<bean id="jaxbMessageConverter" 
    class="...Jaxb2RootElementHttpMessageConverter" />
<bean class="...AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <ref bean="jacksonMessageConverter"/>
            <ref bean="jaxbMessageConverter"/>
        </list>
    </property>
</bean>

2) In der Datei web.xml wird das url-pattern von *.html auf /rest/* geändert. Dadurch ergeben sich URL der Form http://HOST:PORT/CONTEXT/rest/persons/1, d.h. es wird klar, das es sich um eine REST-API handelt und gleichzeitig entfällt das Postfix .html.

3) Die Klassen PersonCreateDto und PersonDto sind Klassen zum Datentransfer. Die Annotation @XmlRootElement dient dem Übersetzen der Instanzen der Klassen in XML und umgekehrt. Wird als Repräsentationen lediglich JSON benötigt,  so ist diese Annotation unnötig.

4) Die Klasse RestController exponiert die einzelnen Ressourcen und Methoden. Über Annotationen wird das Spring Framework genutzt um einzelne Anfragen auf Methoden der Klassen abzubilden.
Die Annotation @Controller markiert die Klasse als für das Spring Framework.
Die Annotation @RequestMapping legt dabei den letzten Teil der URL und HTTP Methode fest. Wird dabei eine Variable („persons/{id}“) in die URL eingebaut, so kann diese als Parameter mit der Annotation @PathVariable verwendet werden.
Soll eine Methode im Erfolgsfall nicht den Statuscode 200 liefern, kann dies über die Annotation @ResponseStatus festgelegt werden.
Die Annotationen @RequestBody und @ResponseBody ermöglicht den Transfer von Daten über den jeweiligen Body von Anfrage und Antwort.

5) Die Exception ResourceNotFoundException wird geworfen, wenn eine angefragte Resource nicht existiert.

Beispiel:

@RequestMapping(value = "persons/{id}", method = RequestMethod.DELETE)
@ResponseBody
@ResponseStatus(HttpStatus.GONE)
public boolean deletePerson(@PathVariable(value = "id") int id) {
    PersonDto toDelete = null;
    for (PersonDto person : persons) {
        if (id == person.getId()) {
            toDelete = person;
        }
    }
    if (toDelete != null) {
        return persons.remove(toDelete);
    } else {
        throw new ResourceNotFoundException();
    }
}

Nachdem Start der Anwendung beispielsweise in Tomcat können die Ressourcen unter der Wurzel-URL http://localhost:8080/RestBasics1/rest/ abgerufen werden. Mögliche URLs sind:

  • http://localhost:8080/RestBasics1/rest/persons mit Methoden GET und POST
  • http://localhost:8080/RestBasics1/rest/persons/{id} mit Methoden GET, DELETE und PUT

Diese URLs und Methoden können mit REST Clients wie SoapUI oder RESTClient verwendet werden.

Résumé

In diesem Teil werden noch keine Verknüpfungen zwischen den einzelnen Resourcen in den unterschiedlichen Representationen vorgenommen. Beispielsweise enthält die Resource /persons keine direkten Links auf die einzelnen Personen. Der Nutzer der API muss also den Link zu Jack (/persons/1) anderweitig erfahren.

<Response>
   <e>
      <email>foo@bar.com</email>
      <id>1</id>
      <name>Jack</name>
   </e>
</Response>

Unter dem Stichwort Hypermedia as the Engine of Application State versteht man in diesem Fall die Einbettung von Links direkt in die Resource. Besser wäre für obiges Beispiel also

<Response>
   <e>
      <email>foo@bar.com</email>      
      <name>Jack</name>
      <link xlink:href="../persons/1" />
   </e>
</Response>

Es bleiben also noch einige Sachen zu tun, aber die Basisarbeiten sind getan.

RestBasics1/

Apache Tomcat, nginx und Apple Safari

Für mein neues Projekt WebDrive auf Java-Basis musste natürlich Servlet-Container her. Dieser wurde auf meinem Root-Server in einem eigenen virtuellen Server installiert. Normalerweise läuft der Tomcat auf Port 8080, so auch hier. Der nginx lauscht auf IPv4 (allerdings nur eine interne IP) und  IPv6 (global erreichbar). Auf der zweiten Maschine läuft ein klassischer Apache, der auf IPv4 lauscht (global erreichbar) und dort unter anderem dieses Blog hostet. WebDrive soll nun auch über IPv4 erreichbar sein, also spielt der Apache Proxy für den nginx.

Setup für Webdrive

Das Problem in diesem Setup fiel auf, als Safari auf OS X Probleme mit dem JavaScript in der Seite bekam. Es stellte sich heraus, das es sich hierbei um Probleme mit dem Content-Length Header handelte. Interessanterweise trat das Problem nachvollziehbar nur mit Safari auf. Den Hinweis auf Probleme bei nginx lieferte Jim Neath in seinem Blog.

Unter Debian 6 (Squeeze) wird nur nginx in Version 0.7.67-3+squeeze2 angeboten. Auf nginx.org wird beschrieben, wie man eine spezielle Quelle für Apt einträgt. Mit der dort vorhandenen Version 1.2.4 von nginx hat sich das Problem dann in Luft aufgelöst.

Zeitzonenprobleme mit Tomcat 6 auf Debian 6

Beim Einrichten einer Webapplikation auf einem Tomcat 6 auf einem Debian 6 Host fiel mir auf, dass die Zeitstempel in den Tomcat Log-Dateien „falsch“ waren. Falsch meint hier, dass sie nicht mit der Rechneruhrzeit übereinstimmten. Dies deutete auf ein Zeitzonenproblem hin.

Kurzes googeln ergab, man muss in der Datei /etc/default/tomcat6 der Variablen JAVA_OPTS die Angabe der lokalen Zeitzone hinzufügen. In meinem Fall war dies -Duser.timezone=Europe/Berlin. Nach anschließendem Neustart des Tomcat waren die Zeitstempel in den Log-Dateien „richtig“.

Eine Erklärung liefert Debian Bug #645221.

Ersatz für Cronjobs: Spring Framework und Quartz Scheduler

Manche Funktionen von Webapplikationen sollen regelmässig ausgeführt werden. Das könnte ein täglicher Report per Mail über die Anzahl der neuen Kunden, das Mahnen von unbezahlten Rechnungen und vieles weiteres. Auf unixoiden Betriebsystemen erledigt klassischerweise Cron solche Aufgaben.

Die Alternative: Spring Framework und Quartz Scheduler

Quartz Scheduler ist ein in Java geschriebener Ersatz für Cron und kann einfach in beliebige Java-Anwendungen integriert werden. Das Spring Framework bietet Unterstützung für Quartz an.

Hier soll nun gezeigt werden, wie eine einfache Integration aussehen kann.

Änderungen an der pom.xml

Folgende Abhängigkeiten müssen eingefügt werden.

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>1.8.6</version>
</dependency>

Wichtig ist hierbei, das zur Zeit (Ende September 2012) Spring 3.x und Quartz 2.x zueinander inkompatibel sind. Dies ist auch in folgendem Bug einsehbar: SPR-858.

Schreiben der ausgeführten Funktion

Hier reicht, wie für das Spring Framework üblich, ein klassisches POJO.

package de.lusiardi.quartzdemo;
public class CalledService {
   public void calledMethod() {
       System.out.println("Executed by Quartz");
   }
}

In dieser POJO können natürlich weitere Beans über Autowiring angesprochen werden wie in jeder Spring Anwendung auch.

Definition im ApplicationContext

Wie gewohnt werden im ApplicationContext alle notwendigen Beans deklariert und verschaltet.

<bean id="calledObject" class="de.lusiardi.quartzdemo.CalledService"/>
<bean id="pollerJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="pollerObject"/>
    <property name="targetMethod" value="calledMethod"/>
</bean>
<bean id="pollerTrigger20s" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    <property name="jobDetail" ref="pollerJob"/>
    <property name="startDelay" value="10000"/>
    <property name="repeatInterval" value="20000"/>
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="pollerTrigger20s"/>
        </list>
    </property>
</bean>

Zunächst wird unter der Id calledObject eine Instanz der Klasse angelegt, die unsere aufzurufende Methode beinhaltet. Anschließend wird ein Job unter der Id pollerJob vom Typ MethodInvokingJobDetailFactoryBean angelegt. Hier wird festgelegt, welche Methode der Bean mit der Id calledObject aufgerufen wird. Die Bean mit Id pollerTrigger20s vom Typ SimpleTriggerBean wird nun festgelegt, dass der pollerJob nach einer Verzögerung von 10s alle 20s laufen soll. Die Angaben der Zeiten sind in Milisekunden. Abschließend werden über eine SchedulerFactoryBean alle Trigger aktiviert. Hier ist es nur der pollerTrigger20s.

Resumé

Hat man die Klippe der inkompatiblen Funktionen umschifft, so erhält man eine saubere Methode um regelmässige Aufgaben innerhalb der Java-Applikation zu lösen. Dabei  spielt sich der größte Teil der Konfiguration im ApplicationContext ab.

Dieser Ansatz bietet gegenüber den klassischen Cronjobs einige Vorteile:

  • Die Aufgaben können direkt in der Applikation entwickelt werden
  • Keine Konfiguration für Cron ausserhalb der Applikation, die vergessen werden könnte
  • Keine zusätzliche Startup-Time für JVM zum Ausführen von Java-Jobs

Java Webprogrammierung mit Spring und Maven (Teil 2)

Ausgehend vom ersten Teil der Serie soll nun noch Persistenz, also die dauerhafte Speicherung von Daten, hinzukommen.

Das Spring-Framework bietet hierzu ein Modul (spring-orm) an. Spring-orm bietet die Integration von verschiedenen ORM Produkten, wie Hibernate, JPA, JDO und iBATIS SQL Maps, an. Dieses Beispiel verwendet Hibernate. Die Auswahl der unterstützten Datenbanksystemen ist bei Hibernate durchaus ansehnlich. Hier wird die weit verbreitete  MySQL in Version 5.5 eingesetzt.

Voraussetzung

Wie oben angesprochen, setzt dieses Beispiel eine existierende Installation von MySQL voraus. Wichtig ist ein leeres Schema, welches sonst ungenutzt ist, ein Nutzer samt Passwort, welcher in diesem Schema alle Berechtigungen hat. Dies ist wichtig, um während der Entwicklung die Struktur der Tabellen anpassen zu können.

pom.xml

In der pom.xml werden die Abhängigkeiten für die neuen Bibliotheken hinzugefügt.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>4.1.6.Final</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.21</version>
</dependency>

Weitere Änderungen sind nicht erforderlich.

Java Entity Klasse Counter

In Entitäten werden die zu persistierenden Daten auf Java-Seite abgelegt. Es handelt sich dabei um POJOs, die über Annotations zu Entities werden.

package de.lusiardi.testprojekt.entity;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@NamedQueries({
    @NamedQuery(name="counter.findByPage",
        query="from Counter c where c.page=:page")
})
public class Counter implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false, unique = true)
    private String page;

    @Column(nullable = false, name = "counter")
    private int count;

    // notwendige Getter und Setter
}

Hier werden folgende Annotations verwendet:

  • @Entity: Legt fest, das diese Klasse eine Entität ist.
  • @Id: Legt den Primärschlüssel einer Entität fest, dieser dient auch für Zugriffe über die Id.
  • @GeneratedValue: Legt für die Id fest, dass sie automatisch erzeugt wird. Dies entspricht dem Zusatz AUTO_INCREMENT in SQL.
  • @Column: Erlaubt die verfeinerte Definition der Spalten in der resultierenden Datenbanktabelle. Hierbei legt nullable fest, ob zwingend ein Wert angeben sein muss oder nicht. unique legt fest, dass jeder Wert in dieser Spalte eindeutig sein muss. Über name lassen sich die Spalten umbenennen, beispielsweise wenn der Java-Name des Feldes mit einem HQL/SQL-Keyword kollidiert. Dies ist bei count der Fall.
  • @NamedQueries und @NamedQuery: Diese Annotationen erlauben es, benannte Queries direkt bei der Entität anzugeben. Dies hat den Vorteil, dass Daten und Abfragen in einer Datei zusammen liegen und nicht wild über den Code verteilt sind. Die Namen der Queries sind global eindeutig zu halten.
Weitere Informationen zu den einzelnen Annotationen findet man in den verlinkten APIs und unter der Dokumentation zu javax.persistence.

Java Dao Klasse CounterDao

Data Access Objects oder kurz DAOs übernehmen die eigentliche Kommunikation mit der Datenquelle. In diesem Fall über Hibernate. Auch hier werden mit Annotationen zusätzliche Informationen transportiert.

package de.lusiardi.testprojekt.dao;

import de.lusiardi.testprojekt.entity.Counter;
import org.hibernate.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
public class CounterDao {

    @Autowired
    SessionFactory sessionFactory;

    @Transactional(readOnly = true)
    public Counter findByPage(String page) {
        Query q = getCurrentSession().
            getNamedQuery("counter.findByPage");
        q.setString("page", page);
        return (Counter) q.uniqueResult();
    }

    @Transactional
    public void save(final Counter entity) {
        getCurrentSession().persist(entity);
    }

    @Transactional
    public void update(final Counter entity) {
        getCurrentSession().merge(entity);
    }

    @Transactional
    public void delete(final Counter entity) {
        getCurrentSession().delete(entity);
    }

    private Session getCurrentSession() throws HibernateException {
        return sessionFactory.getCurrentSession();
    }
}

Der Zugriff auf Datenbanken geschieht in Hibernate über Session-Objekte. Diese ermöglichen das Speichern (persist), Aktualisieren (merge) und Löschen (delete) von Entitäten. Über getNamedQuery können dieses abgerufen und eingesetzt werden.

Wichtigste Annotation in dieser Klasse ist @Autowired. Autowired dient der Dependeny Injection, einer wichtigen Technik im Spring Framework. Das Feld sessionFactory ist deshalb auch protected um Spring die Möglichkeit zu geben, die in der spring-servlet.xml definierte SessionFactory einzufügen. Dies geschieht in diesem Fall anhand des Typs des Feldes.

@Repository markiert das DAO für Spring als solches und legt fest, das es sich um eine Bean handelt, die über ihren Typ ansprechbar ist.

@Transactional legt fest, welche Methoden innerhalb einer Datenbanktransaktion ablaufen. Das Attribute readOnly gibt hier Hilfestellungen bei Optimierungen für rein lesenende Transaktionen.

Änderungen an der Controller Klasse

Nun wird der Controller aus Teil 1 so erweitert, das er für die angezeigte View zählt, wie oft diese aufgerufen wurde.

package de.lusiardi.testprojekt.controller;

import de.lusiardi.testprojekt.dao.CounterDao;
import de.lusiardi.testprojekt.entity.Counter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {

    @Autowired
    CounterDao counterDao;

    @RequestMapping("view")
    public ModelAndView getTest() {
        Counter c = counterDao.findByPage("test");
        if (c == null) {
            c = new Counter();
            c.setPage("test");
            c.setCount(0);
            counterDao.save(c);
        }
        int count = c.getCount() + 1;
        c.setCount(count);
        counterDao.update(c);

        return new ModelAndView("view", "key", "Page view number: "
            + count);
    }
}

Auch hier wird über  @Autowired eine Abhängigkeit, hier das DAO, automatisch eingefügt. In der Methode getTest wird nun dieses DAO verwendet um die Anzahl der Pageviews zu zählen. Dabei muss der Fall abgedeckt sein, dass für die gewünschte Seite noch kein Datenbankeintrag besteht.

spring-servlet.xml

Damit Instanzen der SessionFactory und des CounterDao jeweils automatisch eingefügt werden können, muss die spring-servlet.xml entsprechend angepasst werden. Dazu fügt man folgende Definition hinzu:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" 
        value="/WEB-INF/config/jdbc/jdbc.properties"></property>
</bean>

<bean id="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" 
        value="${jdbc.driverClassName}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>  
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource"></property>
    <property name="packagesToScan" 
         value="de.lusiardi.testprojekt" ></property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${jdbc.dialect}</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">create-drop</prop> 
        </props>
    </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="dataSource" ref="myDataSource" />
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven transaction-manager="txManager" />

Zunächst wird eine Instanz von PropertyPlaceholderConfigurer definiert, die es ermöglicht Konfigurationsdaten für die Datenbank in einer Property-Datei abzulegen. Die Bean mit der Id myDataSource ist eine Instanz von DriverManagerDataSource und wird über Werte aus der Property-Datei passend konfiguriert. Die SessionFactory, welche  in das DOA injiziert wurde, wird ebenfalls hier definiert. Sie bekommt selbst die Bean namens myDataSource eigefügt und wird ebenfalls passend konfiguriert. Wichtig hier bei ist die Eigentschaft hibernate.hbm2ddl.auto und der Wert create-drop. Mit diesen Einstellungen wird bei jedem Start der Applikation die Datenbank neu angelegt und ist anschließend leer!  Diese Einstellung ist nur für die Entwicklung sinnvoll und sollte im Produktivbetrieb auf validate umgestellt werden.

web-inf/config/jdbc.properties

In dieser Properties-Datei werden die ensprechenden Schlüssel-Wert-Paare zur Konfiguration der Datenbank abgelegt.

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=URL wie jdbc:mysql://server:3306/schema eintragen
jdbc.username=Nutzernamen eintragen
jdbc.password=Passwort eintragen
jdbc.dialect=org.hibernate.dialect.MySQL5Dialect

Die Konfiguration ist für MySQL 5 ausgelegt und muss noch mit entsprechenden Werten für Verbindungs-URL, Nutzername und Passwort versehen werden.

Übersetzen und Deployen

Dieser Schritt verläuft analog zu Teil 1, nur wird die entstehende war-Datei durch die zusätzlichen Abhängigkeiten größer.

Résumé

Die Integration einer Datenbankanbindung in ein Demoprojekt setzt bereits einiges an Hintergrundwissen voraus und macht deutlich, das man mit anderen Techniken sicher schneller ans Ziel kommt. Allerdings sorgt die deutliche Trennung der Schichten und das Verwenden etablierter Techniken für wartbaren und übersichtlichen Code.

Update 26. Juli 2013

Die Quellen zu diesem Artikel kann man nun auch bei GitHub herunterladen oder forken.

Guava: Google Core Libraries for Java

Google bietet in der Bibliothek Guava einige interessante Klassen an. Diese lösen Probleme, die mit der Standard-API nur auf Umwegen lösbar sind.

com.google.common.base.Joiner und com.google.common.base.Splitter

Joiner bietet das häufig vermisste Äquivalent von PHPs implode an. Allerdings ist der Funktionsumfang höher. Konfigurierbar ist welches Zeichen oder welche Zeichenkette zum Verbinden eingefügt werden soll, wie mit null-Werten verfahren wird (Ersetzen durch feste Zeichenkette oder überspringen). Weiterhin können nicht nur Arrays sondern auch Iteratoren als Eingabe verwendet werden. Über die Funktion withKeyValueSeparator und den gelieferten MapJoiner können auch Maps in Strings verwandelt werden.

Splitter ist das Gegenstück und das Äquivalent zu PHPs explode. Aufgeteilt werden kann nach einer festen Länge, an einzelnen Zeichen, Zeichenketten und Mustern. Weiterhin gibt es auch den MapSplitter und eine Funktion um die Ergebnisse automatisch zu trimmen.

Unverständlicherweise bietet die Klasse keine Möglichkeit an, direkt ein Array als Resultat eines Splits zu bekommen. Geliefert wird ein Iterable<String> welches dann über Iterables.toArray(iterable, String.class) in ein String[] gewandelt wird.

com.google.common.base.Preconditions

Die Methoden dieser Klasse erlauben das vereinfachte, übersichtlichere Prüfen von Vorbedingungen der aufrufenden Methode. Beispielsweise kann der Code

if ( input == null ) {
    throw 
        new NullPointerException("The parameter input was null!");
}

ersetzt werden durch

checkNotNull(input, "The parameter input was null!");

Weitere Methoden überprüfen die Gültigkeit von Indices in Felder und Listen. Im Fehlerfall wird hier eine IndexOutOfBoundsException geworfen. Mit den Funktionen checkState wird der interne Zustand der Instanz geprüft, unabhängig von den Argumenten der Methode. Weitere Informationen findet man in der Api zu den Preconditions.

com.google.common.base.Stopwatch

Die Klasse Stopwatch bietet die Funktionalität einer klassischen Stoppuhr mit einer Genauigkeit von Millisekunden.

Stopwatch stopwatch = new Stopwatch().start();
// zu messender Bereich 
System.out.println("Time elapsed in seconds: " + 
    stopwatch.stop().elapsedTime(TimeUnit.SECONDS));

Sieht eleganter aus und ist flexibler als der Klassiker mit System.getTimeInMillis() und ähnlichen. Weitere Informationen findet man in der  Api.

com.google.common.base.Strings

Die Methoden der Klasse Strings geben dem Entwickler einige Funktionen an die Hand, die im praktischen Programmieren im Umgang mit Zeichenketten hilfreich sein können.

Häufig muss man bei Strings prüfen, ob überhaupt eine Zeichenkette vorhanden ist, also weder null noch ein leerer String vorliegt, oder man will einen leeren String in null wandeln oder umgekehrt (Funktionen emptyToNullisNullOrEmpty und nullToEmpty).

Viele Sprachen bieten direkt die Wiederholung von Strings (Beispielsweise 1000 * „Danke“) an, hier wird dies über die Funktion repeat nachgeliefert.

Weitere Funktionen füllen Strings von rechts oder links mit Zeichen auf, bis diese eine feste Länge besitzen (padStart und padEnd) oder identifizieren gemeinsame Prä- und Suffixe (commonPrefix und commonSuffix).

Guava und Maven

Nichts einfacher als das, einfach folgende Dependency einfügen:

<dependency>  
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>13.0.1</version>
</dependency>

Java Webprogrammierung mit Spring und Maven (Teil 1)

20. September 2012:
Update auf Spring 3.1.2-RELEASE

Dieser Artikel führt knapp in die Webprogrammierung mit Java ein. Wir setzen dabei auf einen Model-View-Controller-Ansatz unterstützt durch das Spring-Framework, speziell das Web MVC Framework.

Dabei baut dieser Artikel auf den Grundlagenartikeln zu Maven auf. Die ersten beiden Artikel der sind hier verlinkt: Teil 1, Teil 2. Alle Artikel zum Thema Maven finden sich in der entsprechenden Kategorie.

Nachfolgend sieht man die Verzeichnisstruktur und die Dateien eines Maven-Projektes, welches eine simple Webapplikation auf Spring-Basis darstellt.

projekt                                             ( 1)
 + pom.xml                                          ( 2)
 + src                                              (  )
 |  + main                                          (  )
 |  |  + java                                       (  )
 |  |  |  + de/lusiardi/testprojekt/controller/     (  )
 |  |  |     + MainController.java                  ( 3)
 |  |  + webapp                                     ( 4)
 |  |     + META-INF                                (  )
 |  |     |  + context.xml                          ( 5)
 |  |     + WEB-INF                                 (  )
 |  |     |  + web.xml                              ( 6)
 |  |     |  + spring-servlet.xml                   ( 7)
 |  |     + views                                   ( 8)
 |  |     |  + view.jsp                             ( 9)
 |  |     + index.jsp                               (10)
 |  + test                                          (  )
 |     + java                                       (  )
 + target                                           (  )

Nun sollen die einzelnen Teile des Projektes beschrieben werden.

1) Wurzelverzeichnis des Projekts

Hier beginnt der ganze Spass und alles, was man zum bauen benötigt liegt in diesem Verzeichnis.

2) pom.xml

Die aus Teil 1 und Teil 2 bekannte Verwaltungsdatei für Maven. Die klassische POM für Abhängigkeiten und Build-Prozess.

<project 
    xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
            http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>de.lusiardi</groupId>
    <artifactId>testprojekt</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>testprojekt</name>

    <properties>
        <project.build.sourceEncoding>
            UTF-8
        </project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.1.2.RELEASE</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Wichtig sind hier lediglich die beiden Dependencies für spring-webmvc und javax.servlet-api. provided bedeutet in diesem Zusammenhang, dass diese Dependency nicht mit in das entstehende Artefakt gepackt werden soll, da sie vom Servlet Container bereit gestellt wird. Über das maven-compiler-plugin legt man die verwendeten Java-Versionen fest, da sonst Annotationen nicht unterstützt werden.

3) Controller Java Klasse

Das eingesetzte Spring-Framework Web MVC ermöglicht durch einfache Annotationen das Ausweisen von Controllern.

package de.lusiardi.testprojekt.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {

    @RequestMapping("view")
    public ModelAndView getTest() {
        return new ModelAndView("view", "key", "value");
    }
}

In diesem Beispiel-Controller (ausgezeichnet durch die @Controller Annotation) existiert ein einzelnes Mapping für Anfragen (@RequestMapping Annotation). Die Funktion getTest wird für alle Anfragen an die URL

http://localhost:8080/testprojekt-1.0-SNAPSHOT/main/view.html

aufgerufen. Über die Rückgabe der ModelAndView-Instanz wird die View mit Namen view aufgerufen und das Modell an die View. Das Modell ist in diesem Fall ein String, der unter „key“ zugreifbar ist.

4) Konfigurationen für Webapplikation

Webapplikationen werden in der Java-Welt über war-Dateien verteilt. In der Spezifikation ist die Struktur der Daten festgelegt. Der Inhalt des Verzeichnisses webapp wird in die war-Datei direkt eingefügt.

5) context.xml

Dies ist der Tomcat Context Descriptor und legt den ersten Teil der URL nach dem Server fest. Dieser muss eindeutig sein.

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/HelloMaven"/>

6) web.xml

In der Datei web.xml können einige grundlegende Einstellungen der Web-Applikation getätigt werden.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <display-name>Spring3MVC</display-name>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>   

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

</web-app>

In diesem Beispiel legen wir den Anzeigenamen (display-name) der Applikation fest. Die welcome-file-list gibt an welche Dateien verwendet werden sollen, wenn Anfragen direkt auf ein Verzeichnis gehen. Als nächstes wird ein servlet vom Spring-Typ DispatcherServlet und ein entsprechendes servlet-mapping auf alle html-Dateien.

7) spring-servlet.xml

Der Name der Konfigurationsdatei spring-servlet.xml ergibt sich aus dem Namen des Servlets in der web.xml. Es existiert also für jedes Servlet eine entsprechende Datei.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" />

    <context:component-scan base-package="de.lusiardi.testprojekt" />

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

    <mvc:annotation-driven />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

</beans>

Durch die Definition eine Bean vom Spring-Typ ControllerClassNameHandlerMapping wird festgelegt, das per Konvention aus MainController der Pfad /main/ wird.

Über context:component-scan aktiviert man die Unterstützung für die verwendeten Annotationen im angegeben Package. Die ermöglicht in Kombination mit mvc:annotation-driven die Einführung des Controllers rein über Annotationen.

Die Bean viewResolver vom Typ InternalResourceViewResolver ermöglicht es, Views wie im Controller getan, einfach nur über den Namen anzusprechen. Hier sieht man die Möglichkeit, Beans mit speziellen Eigenschaften (Properties) zu versorgen. Prefix und Suffix kompletieren die Namen der Views und ergeben den Dateinamen.

8) Ordner für die Views

In diesem Ordner werden die Views gespeichert. Der Name des Ordners ist wie in der spring-servlet.xml (7) beim ViewResolver als Prefix definiert.

9) view.jsp

Views dienen zur tatsächlichen Darstellung des Models in Model-View-Controler. Hier sind die VIews in JSP geschrieben.

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <title>Lusiardi.de - Tutorial</title>
    </head>
    <body>
        <a href="../">${key}</a><br />
    </body>
</html>

Hier wird über ${key} auf das Model (den einzelnen String) zugegriffen, und dieser ausgegeben.

10) index.jsp

Die in der web.xml (6) festgelegte Welcome-Page

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <title>Lusiardi.de - Tutorial</title>
    </head>
    <body>
    <a href="main/view.html">Controller</a><br />
    </body>
</html>

Bauen

In diesem Fall ist das Ergebnis der Bemühungen keine JAR-Datei, sondern eine WAR-Datei. WAR steht für WebARchive. Aufgrund des in der pom.xml festegelegten packaging war, wird durch folgenden Aufruf die fertige WAR-Datei erzeugt.

mvn package

Dabei entsteht im target-Verzeichnis die Datei testprojekt-1.0-SNAPSHOT.war. Diese kann dann in einem Tomcat oder anderen Servlet-Container ausgeführt werden.

Résumé

Java und Webentwicklung haben höhere Einstiegshürden als andere Techniken. Das stimmt zweifelsfrei. Inzwischen kann aber über entsprechende Frameworks viel dieser Komplexität abgefangen werden, wenn man sich klar gemacht hat, was diese Frameworks exakt tun.

Dieser Artikel war lediglich eine kurze Einführung in dieses Thema und lässt auch vieles, was IDEs leisten können, bewusst aussen vor, um ein wenig Licht in diesen Bereich zu bringen.

Update 26. Juli 2013

Die Quellen zu diesem Artikel kann man nun auch bei GitHub herunterladen oder forken.