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/

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.

Apache Maven – Launch4j Integration

Im 5ten Teil der Serie über Maven (Teil 1, Teil 2, Teil 3, Teil 4 – Teil 4 wird für diesen Teil allerdings nicht benötigt, kann also überlesen werden) soll gezeigt werden, wie wiederum Maven genutzt werden kann, um automatisiert im Build-Prozess direkt ausführbare Dateien für Windows (exe) erzeugt werden können.

Launch4j

Launch4j ist ein plattform-übergreifendes Werkzeug zur Erstellung von ausführbaren EXE-Dateien für Windows. Für einmale Aktionen und zum Üben bietet Launch4j eine GUI an, diese eignet sich aber nicht zur Integration in einen Build-Prozess. Hier kann uns Maven wieder helfen.

Erweiterung der pom.xml

Zunächst muss ein (zusätzliches) pluginRepository eingebunden werden. Das Tag pluginRepositories wird nur benötigt, wenn es noch nicht vorhanden war.

<pluginRepositories>
    <pluginRepository>
        <id>launch4j-xml-plugin-repo</id>
        <name>launch4j-xml-plugin Repository for Maven</name>
        <url>
            https://launch4j-xml-plugin.googlecode.com/svn/repo
        </url>
    </pluginRepository>
</pluginRepositories>

Anschließend wird wie in Teil 3 der Build-Prozess angepasst. Dazu wird unter /project/build/plugins (dem XPath folgen) folgendes zusätzliche Plugin eingetragen. Es wird davon ausgegangen, dass

  • ein ausführbares JAR-File vorliegt
  • und dieses muss unter plugin/executions/execution/configuration/jar (XPath) angegeben werden

Die gewünschte Ergebnisdatei wird unter plugin/executions/execution/configuration/outfile (XPath) angegeben.

<plugin>
    <groupId>org.bluestemsoftware.open.maven.plugin</groupId>
    <artifactId>launch4j-plugin</artifactId>
    <version>1.5.0.0</version>
    <executions>
        <execution>
            <id>l4j-gui</id>
            <phase>package</phase>
            <goals>
                <goal>launch4j</goal>
            </goals>
            <configuration>
                <headerType>gui</headerType>
                <outfile>target/MouseMover.exe</outfile>
                <jar>target/MouseMover-0.0.1.jar</jar>
                <errTitle>MouseMover Error</errTitle>
                <icon>src/main/resources/mm.ico</icon>
                <jre>
                    <minVersion>1.5.0</minVersion>
                    <maxVersion>1.6.0</maxVersion>
                    <initialHeapSize>128</initialHeapSize>
                    <maxHeapSize>1024</maxHeapSize>
                </jre>
                <versionInfo>
                    <fileVersion>1.0</fileVersion>
                    <txtFileVersion>1.0</txtFileVersion>
                    <fileDescription>Desc</fileDescription>
                    <copyright>C</copyright>
                    <productVersion>1.0</productVersion>
                    <txtProductVersion>1.0</txtProductVersion>
                    <productName>MouseMover</productName>
                    <internalName>MouseMover</internalName>
                    <originalFilename>
                        MouseMover.exe
                    </originalFilename>
                </versionInfo>
            </configuration>
        </execution>
    </executions>
</plugin>

Nach dem nächsten Build steht euch dann an der angegebenen Stelle eine EXE (hier MouseMover.exe) zur Verfügung. Das klappt zum Glück nicht nur unter Windows, sondern auch unter Linux und Mac OS X. Aber das war auch auf der Homepage von Launch4j so angegeben.

Résumé

Launch4j ermöglicht in Verbindung mit Maven das Erstellen von EXE-Dateien ohne große Probleme. So kommen Windows-Nutzer in den Genuss direkt ausführbarer Java-Programme.

Downloads

Das Testprojekt findet sich hier und die enstandene EXE hier.

Apache Maven – ANTLR Integration

Im 4ten Teil der Serie über Maven (Teil 1, Teil 2, Teil 3) soll gezeigt werden, wie Maven genutzt werden kann, um im Build-Prozess ANTLR-Dateien zu integrieren.

 ANTLR

ANTLR steht für „ANother Tool for Language Recognition“. ANTLR generiert aus den geeigneten Definitionen Lexer, Parser oder TreeParser. Als Zielsprache ist ANTLR nicht auf Java festgelegt, sondern generiert auch Code für C, C++, Python, JavaScript und weitere. Für uns ist hier die Ausgabe natürlich nur in Java interessant.

Erweiterung der pom.xml

Um ANTLR in einem Projekt verwenden zu können, muss die pom.xml an 2 Stellen geändert werden. Zum einen muss eine neue Abhängigkeit zur ANTLR-Runtime definiert werden. Dies passiert im unteren Abschnitt. Damit stehen die ANTLR-Klassen wie bei jeder anderen Dependency bereit.

Soll Maven jedoch noch automatisch aus ANTLR-Grammatikdateien (*.g) entsprechende Java-Klassen erstellen, so ist eine Erweiterung des Build-Vorgangs notwendig. Dazu wird im oben Bereich das antlr3-maven-plugin eingebunden.

<project ...>
    ...
    <build>
        <plugins>
            ...
            <plugin>
                <groupId>org.antlr</groupId>
                <artifactId>
                    antlr3-maven-plugin
                </artifactId>
                <version>3.1.3-1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>antlr</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            ...
        </plugins>
    </build>
    ...
    <dependencies>
        ...
        <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr-runtime</artifactId>
            <version>3.2</version>
            <type>jar</type>
       </dependency>
        ...
    </dependencies>
</project>

Grammatik für Ausdrücke als Beispiel

Die folgende Beispielgrammatik (b1.g) muss im Verzeichnis src/main/antlr3/package/b1 abgelegt werden. das ANTLR-Plugin durchsucht das Verzeichnis src/main/antlr3/package. Alle .g-Dateien innerhalb des Verzeichnisbaums werden von ANTLR übersetzt und im Verzeichnisbaum target/generated-sources/antlr3/package gespeichert. Im weiteren Verlauf des Build-Prozesses sind diese Klassen dann im Classpath und so normal verwendbar.

// Name der Grammatik
grammar b1;

// einfache, feste Tokens
tokens {
	PLUS 	= '+' ;
	MINUS	= '-' ;
	MULT	= '*' ;
	DIV	= '/' ;
}

@header {
        package b1;
}

@lexer::header {
        package b1;
}

ausdruck:
        term ( ( PLUS | MINUS )  term )* ;

term	:
        faktor ( ( MULT | DIV ) faktor )* ;

faktor	:
        ZAHL ;

ZAHL	:
        (DIGIT)+ ;

WHITESPACE :
        ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+
                { $channel = HIDDEN; } ;

fragment DIGIT	:
        '0'..'9' ;

Aus dieser Grammatik-Datei entstehen 2 Klassen:

  • b1Lexer.java
  • b1Parser.java

Der Lexer zerlegt die Eingabe in sog. Tokens, von denen der Parser prüft, ob sie einen gültigen Satz ergeben. In unserem Fall würde „5 * 4 + 3“ im Lexer zu „ZAHL MULT ZAHL PLUS ZAHL“ und im Parser zu einem sog. Parsebaum „((ZAHL MULT ZAHL) PLUS ZAHL)“.

Résumé

Eine Integration von ANTLR in den Build-Prozess über Maven ermöglicht eine wesentlich einfachere Entwicklung, da händische Zwischenschritte (Erzeugen von Lexer, Parser und/oder Baumparser) entfallen können. Durch anschließende Tests wird dabei automatisch überprüft, ob ein Build erfolgreich ist oder nicht.

Update 23. Juli 2013

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

Apache Maven – Ausführbare JAR-Dateien

Im dritten Teil unserer Serie über Apache Maven (Teil 1, Teil 2) soll das Programm nicht nur mit mvn exec:java -Dexec.mainClass=“b1.Main“ gestartet werden können sondern auch mit java -jar Datei.jar.

Maven Shade PlugIn

Das Maven Shade PlugIn erlaubt das Erstellen einer JAR-Datei, welches alle Abhängigkeiten des Projektes beinhaltet und die Manifest-Datei entsprechend anpasst.

Zum Einsatz muss die pom.xml erneut erweitert werden:

<project ...>
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>1.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                  implementation="org.apache.maven.
                                  plugins.shade.resource.
                                  ManifestResourceTransformer">
                                    <mainClass>b1.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            ...
        </plugins>
    </build>
    ...
</project>

Führt man nun ein mvn package aus, so werden alle abhängigen JAR-Dateien in ein temporäres Verzeichnis ausgepackt, eine entsprechend dem Eintrag Main-Class in der Manifest-Datei.

Anschließend kann über java -jar target/basics1-0.1.jar das Programm gestartet werden.

Résumé

Natürlich ist die so erstellte JAR-Datei größer als die aus dem letzten Teil, da die komplette JLine-Bibliothek mit eingepackt wurde. Dies ist aber notwendig um die JAR-Datei ausführen zu können. Weitere Informationen zum Maven Shade Plugin findet man hier.

Update 23. Juli 2013

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

Apache Maven – Dependencies

Im zweiten Teil der Serie über Apache Maven (hier geht es zum ersten Teil) geht es diesmal um Repositories und Dependencies.

Dependencies

Als Beispiel einer Dependency war im ersten Teil bereits JUnit aufgefallen (Ausschnitt pom.xml):

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.8.2</version>
</dependency>

Das reicht aus, um Maven zu sagen: bitte binde doch mal JUnit in Version 4.8.2 in den ClassPath ein. Maven lädt die notwendigen JARs dabei automatisch aus dem Netz herunter. Dazu dienen eine Reihe von Standardrepositories.

Repositories

Nicht alle Dependencies sind in den Standardrepositories vorhanden. Ist für eine Bibliothek ein eigenes Repository notwendig, so kann das wie folgt in der pom.xml angegeben werden:

<repositories>
    <repository>
        <id>jline</id>
        <name>JLine Project Repository</name>
        <url>http://jline.sourceforge.net/m2repo</url>
    </repository>
</repositories>

Hat man die notwendigen Repositories aktiviert kann man weitere Abhängigkeiten festlegen:

<dependency>
    <groupId>jline</groupId>
    <artifactId>jline</artifactId>
    <version>0.9.9</version>
</dependency>

Beispielprogramm

Ergänzen wir das Projekt aus Teil 1 um die Datei src/main/java/b1/Main.java:

package b1;

import java.io.IOException;
import java.io.PrintWriter;

import jline.ConsoleReader;

public class Main {
    public static void main(String[] args) throws IOException{
        ConsoleReader reader = new ConsoleReader();
        String line;
        PrintWriter out = new PrintWriter(System.out);
        while ((line = reader.readLine("number or quit> ")) != null) {
            if (line.equalsIgnoreCase("quit"))
                break;
            int i = Integer.parseInt(line);
            System.out.println(
                Math.square(i)+" ist " + i + " quadriert");
            out.flush();
        }
    }
}

Übersetzen und in ein JAR-File einpacken mit

mvn package

Testweise ausführen kann man das Programm nun einfach mit:

mvn exec:java -Dexec.mainClass="b1.Main"

Résumé

Dieser Teil der Serie hat das Einbinden von Abhängigkeiten vorgestellt. Sucht man für eine Bibliothek die notwendigen Angaben helfen Seiten wie http://mvnrepository.com. So hilft einem Maven durch das automatische Herunterladen schnell zu einer Menge von mächtigen Bibliotheken.

Update 23. Juli 2013

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

Apache Maven – Basics

Einführung

Apache Maven ist laut Wikipedia ein Build-Management-Tool, welches insbesondere geeignet ist, Java-Projekte zu standardisiert zu erstellen und zu verwalten.

Installation

Um mit Maven arbeiten zu können, muss man unter Gnu Debian 6.0 mindestens diese beiden Pakete installieren:

  • maven2
  • openjdk-6-jdk

Erstes Projekt

Maven verwendet ein standardisiertes Layout für die Projektverzeichnisse.

\projekt                     - Wurzelverzeichnis des Projekts
    \pom.xml                 - Verwaltungdatei
    \src
    |   \main
    |       \java            - Anwendungscode in Java
    |   \test
    |       \java            - Testcode in Java
    \target                  - alle erzeugten Dateien landen hier

Das target-Verzeichnis legt Maven dabei bei Bedarf neu an.

Die Datei „pom.xml“ beschreibt dabei notwendige Informationen über das Projekt.

<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.maven</groupId>
        <artifactId>basics1</artifactId>
        <packaging>jar</packaging>
        <version>0.1</version>
        <name>Einfuehrung in Maven</name>
        <url>http://wp.lusiardi.de/?p=197</url>
        <build>
                <plugins>
                        <plugin>
                                <groupId>
                                        org.apache.maven.plugins
                                </groupId>
                                <artifactId>
                                        maven-compiler-plugin
                                </artifactId>
                                <version>2.1</version>
                                <configuration>
                                        <source>
                                                1.5
                                        </source>
                                        <target>
                                                1.5
                                        </target>
                                        <encoding>UTF-8</encoding>
                                </configuration>
                        </plugin>
                </plugins>
       </build>
       <dependencies>
                <dependency>
                        <groupId>junit</groupId>
                        <artifactId>junit</artifactId>
                        <version>4.8.2</version>
                        <scope>test</scope>
                </dependency>
        </dependencies>
</project>

Im Einzelnen bedeuten die Einträge folgendes:

  • modelVersion: aktuell immer 4.0.0
  • groupId: id für die Gruppe, zu der das Artefakt (das Projekt) gehört
  • artifactId: id des Artefakts (des Projekts)
  • packaging: jar legt fest, dass das Projekt als jar-Datei zusammengepackt werden soll
  • version: die Version des Projekts, -SNAPSHOT sagt aus, dass das Projekt noch im Entwicklungszustand ist und regelmässige Änderungen erwartet werden
  • name: der ausführliche Name des Projekts
  • url: URL zur Projektseite
  • build: Maven erlaubt es durch Plugins den Build-Prozess zu erweitern oder zu erweitern, hier wird der Java-Compiler konfiguriert (Java 5 für Annotations).
  • dependencies: gibt die Abhängigkeiten des Projekts zu anderen Projekten an. Hier wird beispielsweise JUnit in Version 4.8.2 eingebunden. Auf das Einbinden von Abhängigkeiten wird später noch eingegangen.

Unser eigentlicher Source-Code besteht nur aus einer Datei in src/main/java/b1/Math.java (keine Kommentare zum Sinn des Codes bitte):

package b1;                                                          

public class Math {                                                  
        public static int square(int a) {                            
                return a*a;                                          
        }                                                            
}

Unsere Testdateien sind ähnlich hochwertig (zu finden in: src/test/java/b1/MathTest.java):

 package b1;

import org.junit.Assert;
import org.junit.Test;
import b1.Math;

public class MathTest {
        @Test
        public void testSqure() {
                Assert.assertEquals(16, Math.square(4));
        }
}

Wichtige Maven-Kommandos

mvn clean

clean löscht alle durch Maven erzeugten Dateien (inklusive des target-Verzeichnisses)

mvn compile

kompiliert den Quellcode unter src/main

mvn test

kompiliert die Test-Dateien und führt die Tests aus. Die Ergebnisse der Tests landen im Verzeichniss target/surefire-reports.

mvn package

erzeugt ein Paket im angegebenen Format (siehe pom.xml)

mvn install

erzeugt das Paket und installiert es im lokalen Repository. So kann es von anderen Projekten als Abhängigkeit verwendet werde.

Résumé

Natürlich kann man Maven nicht ausführlich auf einer solch kurzen Seite beschreiben und komplett erschöpfen, doch sollte dies ein kleiner Einstieg sein. Weitere Artikel zu Maven werden den Umgang mit Abhängigkeiten, ausführbaren Jar-Dateien und dem Einbinden von ANTLR in den Build-Prozess behandeln.

Update 23. Juli 2013

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