Hibernate 4.x mit Spring 3.x

Veröffentlicht in: Hibernate, Java, spring | 0

Der nachfolgende Artikel beschreibt die Verwendung des ORM – Object-Relational Mapping – Frameworks Hibernate 4 inklusive der Hibernate Integration von Spring 3, wobei letztere auch die Vorgängerversion Hibernate 3 unterstützt. Als Persistenz-Schicht wird die In-Memory Datenbank HSQLDB verwendet. Es entfällt also die Installation einer Datenbank, da für den Einsatz der HSQLDB lediglich die entsprechende Library eingebunden werden muss.

In diesem Beispiel sollen Benutzer- und Adressdaten persistiert werden, wobei einem Benutzer-Eintrag beliebig viele Adress-Informationen zugewiesen werden können. Es gibt also ein 1..n Mapping zwischen Benutzer und Adressen. Weiterhin wird ein DAO – Data Access Object – implementiert, welches die CRUD – Create/Read/Update/Delete – Datenbank Operationen für die Haupt-Entität (Benutzer) zur Verfügung stellt.

Im ersten Schritt wird ein Maven-Projekt erstellt, in dessen pom.xml die folgenden Dependencys ergänzt werden:

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.7.Final</version>
</dependency>

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.9</version>
</dependency>

<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

Zuerst werden die Entitäten erstellt, welche die Tabellen der relationalen Datenbank als Java-Klassen repräsentieren. Da der Benutzer Kenntnis über die zu ihm gehörenden Adressen benötigt, umgekehrt aber keine Verknüpfung notwendig ist, kennt die Adress-Entität lediglich sich selber. Ein guter Grund mit dieser zu beginnen:

@Entity
public class Address {

@Id
@GeneratedValue
private Long id;
private String street;
private String city;

// getter setter methods

}

Die Klasse Address wird mit der Annotation Entity versehen, um sie als solche zu kennzeichnen. Weiterhin erhält sie neben den benötigten Eigenschaften (Straße, Stadt, …) einen Identifier. Die entsprechende Annotation definiert diesen als Primär-Schlüssel, während GeneratedValue dafür sorgt, dass sein Wert automatisch von der Datenbank vergeben werden soll.

Nun kann auch die Klasse User mit Zugriff auf die Adress-Datensätze erzeugt werden:

@Entity
public class User {

@Id
@GeneratedValue
private Long id;
private String forname;
private String lastname;

@OneToMany
@Cascade(value=CascadeType.ALL)
private List<Address> addresses;

// getter setter methods

}

Da ein User eine beliebige Anzahl an Adressen erhalten kann, wird hier als Mapping die OneToMany-Annotation verwendet. Weiterhin wird der CascadeType ALL gewählt, der unter anderem die CascadeTypen SAVE_UPDATE und DELETE beinhaltet. Hiermit wird erreicht, dass beim Speichern des Users gleichzeitig seine Adress-Daten persistiert werden. Wäre der CascadeTyp SAVE_UPDATE nicht gewählt, müssten Adress- und Benutzerdaten separat gespeichert werden, wobei ein Benutzer nur auf Adressen verweisen darf, die bereits in der Datenbank existieren. Der CascadeType DELETE sorgt dafür, dass beim Löschen eines Benutzers auch seine Adressen aus der Datenbank entfernt werden.

Wie man an obigem Beispiel erkennt, gibt es Attribute die sich in den Entitäten wiederholen. In diesem Beispiel ist es nur der Identifier, der sowohl beim User als auch bei der Adresse vorkommt. Aber auch das Erstellungs- und letztes Änderungsdatum einer Entität wären Kandidaten, die zu Code-Wiederholung führen könnten. Doppelte Attribute können in eine gemeinsame Oberklasse ausgelagert werden, sofern die Oberklasse mit MappedSuperclass annotiert wird:

@MappedSuperclass
public abstract class AbstractEntity {

@Id
@GeneratedValue
private Long id;

// getter setter methods
}

Nachdem die Entitäten erstellt wurden, soll nun der UserDAO erzeugt werden, der sukzessive um die CRUD-Operationen erweitert wird. Hierfür benötigt er lediglich die SessionFactory. Wer bislang das HibernateTemplate verwendete, wird feststellen, dass dies unter Hibernate 4 nicht mehr unterstützt wird. Das ist nicht weiter verwunderlich, da dessen Verwendung bereits seit Hibernate 3.0.1 obsolet ist. Stattdessen kann bei gültigem Transaktions-Management über die SessionFactory auf die aktuelle Session zugegriffen und auf dieser Datenbank-Operationen ausgeführt werden. Auf die Konfiguration des Transaktions-Management wird später noch eingegangen. Aber zurück zum UserDAO, der zunächst nur aus folgendem Rumpf besteht:

public class UserDAO {

private SessionFactory sessionFactory;

// methods for crud operations
// and a setter method for the sessionFactory
}

Wenden wir uns nun der Spring Konfiguration zu, die in der Datei src/main/resources/applicationContext.xml erstellt wird. Zunächst muss die Verbindung zur Datenbank in einer Datasource konfiguriert werden. Es werden Details zum Datenbanktreiber, URL zur Datenbank, Benutzername und Passwort benötigt.

<bean id=“dataSource“ class=“org.apache.commons.dbcp.BasicDataSource“ destroy-method=“close“>
<property name=“driverClassName“ value=“org.hsqldb.jdbcDriver“/>
<property name=“url“ value=“jdbc:hsqldb:mem:myDB“/>
<property name=“username“ value=“sa“/>
<property name=“password“ value=““/>
</bean>

Bei Verwendung einer HSQLDB kommt der Treiber org.hsqldb.jdbcDriver zum Einsatz. Die URL besteht aus drei Teilen, wobei der erste für alle HSQLDBs gleich ist und jdbc:hsqldb: lauten muss. Der Prefix mem: signalisiert, dass es sich um eine In-Memory Datenbank handelt, während der letzte Teil den Namen der zu verwendeten Datenbank – myDB – bestimmt.

Will man später von einer In-Memory Datenhaltung auf eine dauerhafte Persistierung wechseln, kann die Datasource ersetzt und hier die Verbindungsdaten an die neue Datenbank angepasst werden.

Nun soll die SessionFactory definiert werden, die der UserDAO für die Datenbank-Operationen benötigt:

<bean id=“sessionFactory“ class=“org.springframework.orm.hibernate4.LocalSessionFactoryBean“>
<property name=“dataSource“ ref=“dataSource“/>
<property name=“packagesToScan“>
<list>
<value>de.mz.entity</value>
</list>
</property>
<property name=“hibernateProperties“>
<props>
<prop key=“hibernate.dialect“>org.hibernate.dialect.HSQLDialect</prop>
<prop key=“hibernate.hbm2ddl.auto“>create</prop>
<prop key=“hibernate.show_sql“>true</prop>
</props>
</property>
</bean>

Die SessionFactory braucht Zugriff auf die bereits definierte Datasource und verwaltet alle verfügbaren Entitäten. Hierzu muss entweder eine Liste aller annotierten Klassen – User und Address – über das Property annotatedClasses angegeben werden oder es wird, wie in diesem Beispiel, eine Liste von zu scannenden Packages übergeben. Anschließend können noch Hibernate-Eigenschaften konfiguriert werden, wobei die Angabe des Datenbank-Dialekts zwingend nötig ist. Bislang hat nur die Datasource Informationen über die zu konnektierende Datenbank. Aber auch Hibernate muss wissen, um welche Datenbank es sich handelt, damit die entsprechende Kommunikation gewählt werden kann. Für eine HSQLDB wird der Dialekt org.hibernate.dialect.HSQLDialect verwendet. Die weiteren Einstellungen können die Entwicklung erheblich erleichtern. So sorgt die Property hibernate.hbm2ddl.auto mit dem Wert create dafür, dass Hibernate die Datenbank-Struktur mit Tabellen, Primär- und Fremdschlüsseln automatisch anlegt. Erhält hibernate.show_sql den Wert true, wird jedes von Hibernate ausgeführte SQL-Statement geloggt und kann so nachvollzogen werden. Dies ist zwar für eine Live-Umgebung tödlich, kann aber beim Aufsetzten eines neuen Projektes durchaus hilfreich sein.

Wie bereits erwähnt, wird ein Transaktion-Management benötigt, damit die SessionFactory den UserDAO mit einer gültigen Session versorgen kann. Die Transaktionen sollen dabei über Annotationen definierbar sein. Zunächst wird der HibernateTransactionManager konfiguriert, der Zugriff auf die SessionFactory benötigt:

<bean id=“transactionManager“ class=“org.springframework.orm.hibernate4.HibernateTransactionManager“>
<property name=“sessionFactory“ ref=“sessionFactory“ />
</bean>

Um die Steuerung von Transaktionen mit der Annotation Transactional zu ermöglichen, muss dieses Verhalten aktiviert werden:

<tx:annotation-driven/>

Sofern der Name des TransactionManagers nicht transactionManager lauten würde, müsste hier noch der entsprechende Name angegeben werden:

<tx:annotation-driven transaction-manager=“transactionManager“/>

Zu guter Letzt muss der UserDAO in der Spring-Konfiguration aufgenommen werden und Zugriff auf die SessionFactory erhalten:

<bean id=“userDAO“ class=“de.mz.dao.UserDAO“>
<property name=“sessionFactory“ ref=“sessionFactory“ />
</bean>

Nun wird der UserDAO um die erste CRUD-Operationen erweitert, wobei mit dem Speichern eines Benutzers begonnen wird:

@Transactional
public User save(User user) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(user);
return user;
}

Die save-Methode wird mit der Annotation Transactional versehen. Beim Aufruf der Methode wird nun eine Transaktion erzeugt, die erst beim Verlassen wieder geschlossen wird, womit dann die Datenbank-Änderungen wirksam werden. Träte zuvor eine Exception auf, so fände keine der Datenbank-Operationen der save-Methode statt. Weiterhin sorgt die Transaktion dafür, dass die SessionFactory eine gültige Session liefert, über die der Benutzer gespeichert werden kann.

Durch die Verwendung der saveOrUpdate-Methode kann die gleiche Methode zum Anlegen und Aktualisieren von Benutzerdaten verwendet werden. Besitzt der User keine Id, vergibt Hibernate den Primär-Schlüssel und führt das SQL-Statement Insert aus, andernfalls wird ein Update ausgeführt.

Anschließend wird zum Testen der Anwendung eine Klasse mit ausführbarer main-Methode erstellt, die den Spring-Context hochfährt und den UserDAO aus selbigem lädt:

ApplicationContext context = new ClassPathXmlApplicationContext(„applicationContext.xml“);
UserDAO userDAO = (UserDAO) context.getBean(„userDAO“);

Nun wird ein Benutzter mitsamt zugehöriger Adresse erstellt und über den UserDAO gespeichert:

List<Address> addresses = new ArrayList<Address>();
Address address = new Address();
address.setCity(„Cologne“);
addresses.add(address);

User user = new User();
user.setForname(„Max“);
user.setLastname(„Mustermann“);
user.setAddresses(addresses);

userDAO.save(user);

Dank der Hibernate-Einstellung show_sql lässt sich in den Log-Meldungen gut nachvollziehen, was für SQL-Statements Hibernate durchführt. Zuerst werden die Datenbank-Tabellen erstellt:

Hibernate: create table Address (id bigint generated by default as identity (start with 1), city varchar(255), street varchar(255), primary key (id))
Hibernate: create table User (id bigint generated by default as identity (start with 1), forname varchar(255), lastname varchar(255), primary key (id))
Hibernate: create table User_Address (User_id bigint not null, addresses_id bigint not null, unique (addresses_id))

Die Tabellen Address und User entsprechen unseren Java-Entitäten, während die Tabelle User_Address als Mapping-Tabelle zwischen Benutzer und Adressen fungiert. Anschließend werden der Mapping-Tabelle per constraints Fremdschlüssel auf die Haupt-Entitäten hinzugefügt:

Hibernate: alter table User_Address add constraint FK838B1F807A26EF9A foreign key (User_id) references User
Hibernate: alter table User_Address add constraint FK838B1F805ABA690C foreign key (addresses_id) references Address

Als nächstes werden nun die Benutzerdaten gespeichert. Es lässt sich gut nachvollziehen wie aufgrund des CascadeTypes SAVE_UPDATE der Benutzer und die dazugehörigen Adressen gespeichert werden. Natürlich darf auch der Eintrag in der Mapping-Tabelle nicht fehlen:

Hibernate: insert into User (id, forname, lastname) values (default, ?, ?)
Hibernate: insert into Address (id, city, street) values (default, ?, ?)
Hibernate: insert into User_Address (User_id, addresses_id) values (?, ?)

Die ersten zwei CRUD-Operationen wurden umgesetzt, als nächstes sollen Benutzer-Daten aus der Datenbank ausgelesen werden. Hierzu wird der UserDAO um die find-Methode erweitert:

@Transactional
public List<User> find() {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(User.class);
List<User> users = criteria.list();
return users;
}

Hibernate bietet verschiedene Möglichkeiten, um Datenbank-Abfragen umzusetzen, so kann auf einer Session ein HQL- (Hibernate Query Language) oder SQL-Statement ausgeführt oder wie in diesem Beispiel eine Criteria erstellt werden. Criterias bieten die Möglichkeit Objekt-Orientierte-Abfragen formulieren zu können. Wie auf einer Criteria Einschränkungen definiert werden können, wird im Folgenden noch erläutert. Zunächst soll nur eine Liste aller Benutzer zurückgegeben werden.

Bei der Verarbeitung der Ergebnisliste ist zu beachten, dass Hibernate verschiedene FetchMode zum Umgang mit verknüpften Entitäten bietet. Per Default ist dies Lazy-Loading, wobei verknüpfte Entitäten nur geladen werden, sofern auf sie zugegriffen wird. In diesem Beispiel werden die Adress-Datensätze nicht geladen, da innerhalb der Transaktion kein Zugriff stattfindet. Nach Abschluss der Transaktion – also beim Verlassen der find-Methode – kann nicht mehr auf sie zugegriffen werden, ohne dass eine LazyLoadingException geworfen wird:

org.hibernate.LazyInitializationException: could not initialize proxy – no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:430)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:121)
at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:266)

Wenn die Transaktion im verarbeitenden Service gestartet würde – nicht wie hier im UserDAO – wäre der Zugriff auf die entsprechenden Adress-Daten möglich. Hierbei wird der Vorteil des Lazy-Loadings ausgenutzt, es werden nur Daten geladen, die auch tatsächlich benötigt werden. Bei verteilten Systemen ist dies natürlich nicht unbedingt so möglich. Benötigt ein Client Adress-Informationen, die im Server geladen werden, so ist hier die Session bereits geschlossen. Daher kann für Entity-Verknüpfungen, die zwangsläufig mit der Haupt-Entität geladen werden sollen, der FetchMode Eager-Loading verwendet werden. Hier werden beim Laden des Benutzers direkt alle Adressen mitgeladen. Dies wird in der Annotation OneToMany definiert, die zusätzlich das Attribut fetch erhält. Die Property addresses der Klasse User besitzt nun folgende Annotationen:

@OneToMany(fetch=FetchType.EAGER)
@Cascade(value=CascadeType.ALL)
private List<Address> addresses;

Für große Daten-Mengen, die nicht immer benötigt werden, bietet sich dieses Verfahren natürlich nicht an. Hier kann das DTO – Data Transfair Object – Pattern zum Einsatz kommen. Hierbei wird nicht die User-Entity selber zum Client geschickt, sondern ein UserDTO, welches aber die gleichen Attribute besitzt. Bei der Anforderung der Daten werden die mitzuladenden Abhängigkeiten der Serverseite mitgeteilt, so dass die Antwort alle benötigten Daten in Form von DTOs beinhaltet. In unserem Beispiel würden beim Server die Benutzerdaten mitsamt der Adress-Datensätze angefragt werden. Sowohl die User– als auch die Adress-Daten werden in DTOs umgewandelt, wobei das UserDTO die Adressen in Form einer Liste von AddressDTOs besitzt. Da die Konvertierung innerhalb einer gültigen Session stattfindet, kommt es bei diesem Verfahren zu keiner LazyLoadingException und es werden nur die Daten geladen, die der Client auch tatsächlich benötigt.

Der Nachteil dieses Verfahrens liegt auf der Hand, bei jeder Client-Server-Kommunikation müssen DTOs in Entitäten und anschließend wieder zurück in DTOs konvertiert werden. Außerdem gibt es für jede Entität eine entsprechendes DTO, was zu dupliziertem Code führt. Der Vorteil der DTOs ist die Kapselung, da die Datenschicht vor dem Client-System versteckt wird.

Aber zurück zum Beispiel. Die find-Methode soll nicht mehr alle Benutzer liefern, sondern nur solche, deren Nachname Mustermann lautet. Hierfür wird auf der Criteria eine entsprechende Einschränkung definiert:

Criteria criteria = sessionFactory.getCurrentSession().createCriteria(User.class);
criteria.add(Restrictions.eq(„lastname“, „Mustermann“));
List<User> users = criteria.list();

Der Criteria wird hierzu eine Restriction hinzugefügt, deren erster Parameter den Namen der Property erhält, der zweite den zu prüfenden Wert. Die Restriction besitzt verschiedene Prüfungs-Methoden. Hier wurde auf Gleichheit getestet – equals – aber auch die anderen aus der SQL-Syntax bekannten Prüf-Operationen wie like, between usw. stehen zur Verfügung. Anschließend wird die Criteria wie gehabt ausgeführt und man erhält eine passende Liste von Benutzern.

Es sind aber nicht nur Suchkriterien auf dem Benutzer möglich, es können auch Einschränkungen auf der Adresse festgelegt werden. So interessieren im Folgenden nur Benutzer, die aus Köln kommen:

Criteria criteria = sessionFactory.getCurrentSession().createCriteria(User.class);
Criteria addressCiteria = criteria.createCriteria(„addresses“);
addressCiteria.add(Restrictions.eq(„city“, „Cologne“));
List<User> users = criteria.list();

Auf der bereits erstellen UserCriteria, wird eine weitere Criteria erzeugt. Der übergebene String enthält hierbei den Namen der Property aus der User-Entität, die auf die entsprechende Tabelle verweisst – in diesem Beispiel also addresses. Über die neue Criteria können nun Suchkritieren auf der Adressen definiert werden. Die list-Methode wird allerdings weiterhin auf der UserCriteria ausgeführt, da als Ergebnis-Menge eine Liste von Benutzern und keine Adressen erwartet werden.

Soweit ein kleiner Einblick in die Welt der Criterias. Bereits einen vorangegangenen Artikel – http://martinzimmermann1979.wordpress.com/2012/03/20/verwendung-einer-having-bedingung-bei-hibernate-criterias/ – widmete ich diesem Thema, wobei hier die Having-Bedingung im Vordergrund stand.

Aber zurück zum Thema, noch ist der UserDAO nicht vollständig, da die Lösch-Funktion fehlt:

@Transactional
public void delete(User user) {
sessionFactory.getCurrentSession().delete(user);
}

Diese ist denkbar einfach zu realisieren. Es muss lediglich auf der aktuellen Session die delete-Methode aufgerufen werden, die als Parameter die entsprechende Entität erwartet. Es mag merkwürdig erscheinen, dass zunächst die ganze Entität geladen werden muss, nur um sie anschließend zu löschen. Alternativ könnte eine HQL-Query zum Löschen des Benutzers verwendet werden, aber auch hier wird der Benutzer zuerst in den Hibernate internen Cache geladen, bevor er gelöscht wird. Die einzige Möglichkeit die obige Lösch-Funktion performanter zu gestalten, wäre das Laden und Löschen des Benutzers in derselben Session.

Der Artikel ist für den Einstieg in Hibernate und für das initiale Aufsetzten eines Hibernate Projektes mit Spring Integration gedacht. Es wurde der Einsatz der CRUD-Operationen und ein einfaches Mapping zwischen zwei Enitäten erläutert. Wirklich knifflig wird es erst anschließend, wenn es an das Mapping von Entitäten geht. Welche Entitäten kennen sich untereinander, wie werden sie miteinander verknüpft, welchen CascadeType und welchen FetchMode erhält welche Verbindung. Diese Fragen müssen für jedes Projekt und jede Anwendung einzeln Entschieden werden.

Spring AOP

Veröffentlicht in: spring | 0

Der folgende Artikel beschäftigt sich mit Spring 3.x AOP, wobei Spring lediglich den Support für das eigentliche AOP-Framework AspectJ zur Verfügung stellt. Die Aspekt orientierte Programmierung – kurz AOP – ermöglicht es, die eigentliche Business-Logik einer Anwendung von querschnittlichen Aspekten zu trennen. Als typische Aspekte seien hier Logging- oder Security-Layer genannt. Statt nun die Aspekte mit der Business-Logik zu verweben, findet eine klare Kapselung statt, die es ermöglicht, die Aspekte autonom umzusetzen und anschließend explizit über die Business-Logik zu legen. So können öffentliche Methoden, ganze Projekte, einzelne Packages oder nur bestimmte Methoden eines Services mit Aspekten versehen werden.

Der Artikel erläutert wann und wie ein Aspekt in den Programmfluss eingreifen kann und wie die Aspekte mit der Business-Logik verwoben werden. Ersteres wird über Advices, Zweiteres per Pointcuts realisiert.

Bevor es an die Implementierung geht, müssen zunächst die entsprechenden Libraries eingebunden werden. Für den Spring AOP Support werden die Libraries core und context benötigt, die aktuell in der Version 3.1.0 verfügbar sind:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

Wie bereits erwähnt handelt es sich bei Spring lediglich um den Wrapper um die AOP-Schicht. Für das eigentliche AOP Framework AspectJ werden folgende Libraries verwendet:

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.runtime</artifactId>
<version>1.7.0</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
<version>1.7.0</version>
</dependency>

Um den AspectJ-Support für Spring zu aktivieren, muss folgende Zeile in der Spring-Konfiguration aufgenommen werden:

<aop:aspectj-autoproxy/>

Anschließend wird die Klasse MyService erstellt, welche die eigentliche Business-Logik beinhalten soll.

public class MyService {

public void doSomeStuff() {
// do some business logic
}
}

Dieser Service soll nun mit einem Aspekt versehen werden, der z.B. das Logging von der Business-Logik kapselt.

@Aspect
public class MyAspect {

@Before(„execution (* de.mz.MyService.doSomeStuff(..))“)
public void aspect() {
// do some logging
}
}

Nun muss sowohl der Service als auch der Aspekt als Spring-Bean definiert werden:

<bean id=“myAspect“ class=“de.mz.MyAspect“ />
<bean id=“myService“ class=“de.mz.MyService“ />

Wird nun die Methode doSomeStuff des MyService aufgerufen, wird nicht nur die eigentliche Methode, sondern auch der Aspekt ausgeführt. Die Reihenfolge wird hierbei über den Advice definiert. In obigen Beispiel wird dieser über die Before-Annotation festgelegt, so dass zuerst der Aspekt und erst anschließend die Methode des MyService ausgeführt wird.

Die folgenden Advices bieten die Möglichkeit Eingriffe im Programmfluss vorzunehmen:

  • Before – Der Aspekt wird vor der eigentlichen Methode ausgeführt.
  • Around – Die Methodenausführung findet im Aspekt selber statt. Es kann also vor und nach der Methodenausführung eingegriffen werden.
  • After – Der Aspekt wird erst nach der Methodenausführung aktiv.
  • AfterReturning – Dieser Aspekt wird ebenfalls erst nach der Methodenausführung aktiv, es kann allerdings zusätzlich auf den Rückgabewert zugegriffen werden.
  • AfterThrowing – Der Aspekt wird nur aktiv, sofern in der eigentlichen Methode ein Fehler auftritt.

Um Methoden mit Aspekten zu versehen, werden die bereits erwähnten Pointcuts verwendet. In obigem Beispiel wurde der Pointcut einfach in der Before-Annotation mit angegeben. Wesentlich eleganter ist es, Pointcuts separat zu definieren und im Advice lediglich auf sie zu verweisen. So können die Pointcuts mehrfach verwendet werden und sind beliebig kombinierbar:

@Pointcut(„execution (* de.mz.MyService.doSomeStuff(..))“)
private void pointcutForDoSomeStuff() {}

@Before(„pointcutForDoSomeStuff()“)
public void aspect() {
// do what the aspect has to do
}

Nun wird in der Pointcut-Annotation auf die doSomeStuff-Methode vom MyService verwiesen, wobei der Pointcut selber lediglich aus einer leeren Methoden besteht, deren Rückgabetyp void seien muss. Auch wenn es nicht zwingen notwendig ist, wird als Sichtbarkeit private festgelegt, damit der Pointcut nicht aufgerufen werden kann. Auf die Pointcut-Methode wird nun in der Before-Annotation verwiesen.

Es gibt verschiedene Möglichkeiten mit denen Pointcut-Ausdrücke auf entsprechende Java-Resourcen gematched werden können. Um auf Methoden zu matchen, wird der bereits verwendete Pointcut-Designator (PCD) execution verwendet. Es ist nicht zwingend notwendig, dass hier nur explizit auf eine Methode verwiesen wird, so kann z.B. auch auf alle öffentlichen Methoden des MyService verwiesen werden:

@Before(„execution(public * de.mz.MyService.*(..)))“)

Alternativ können auch alle öffentlichen Methoden – bzw. alle Methoden mit einer fest definierten Sichtbarkeit – mit einem Aspekt versehen werden:

@Before(„execution(public * *(..))“)

Will man Aspekte nicht für Methoden sondern auf einer bestimmten Package-Ebene definieren, wird statt dem execution Designator within verwendet:

@Pointcut(„within(de.mz.*)“)

Sollen zusätzlich die Sub-Packages mit Aspekten versehen werden, muss das Statement minimal angepasst werden:

@Pointcut(„within(de..*)“)

Weiterhin kann man alle Klassen, die ein bestimmtes Interface implementieren mit einem Aspekten versehen, indem man den Designator this verwendet. Würde der MyService nun das Interface IMyService implementieren, würde auch folgender Pointcut matchen:

@Pointcut(„this(de.mz.IMyService)“)

Es gibt noch viele weitere Möglichkeiten, so dass sich hier ein Blick in die Dokumentation allemal lohnt:

http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html

Wie bereits erwähnt, können Pointcuts auch kombiniert werden. Hierbei sind AND &&, OR || und NOT ! Verknüpfungen möglich.

Sollen die Methode doSomeStuff und doSomeOtherStuff denselben Aspekt erhalten wird zunächst ein weiterer Pointcut für die zweite Methode definiert:

@Pointcut(„execution (* de.mz.MyService.doSomeOtherStuff())“)
private void pointcutForSomeOtherStuff() {}

Anschließend können beide Pointcuts mit ODER Verknüpft werden, so dass der Aspekt ausgeführt wird, sobald eine der Methoden aufgerufen wird:

@Pointcut(„pointcutForDoSomeStuff() || pointcutForSomeOtherStuff()“)
private void allPointcuts() {}

Weiterhin kann es nützlich sein im Aspekt auf die Übergabeparameter der aufgerufenen Methode zugreifen zu können. Hierzu wird die Methode doSomeStuff um zwei Übergabeparameter vom Typ String erweitert:

public void doSomeStuff(String para1, String para2) {
// do some business logic
}

Der bestehende Pointcut wird mittels UND mit dem Designator args verknüpft, welcher Zugriff auf die Parameter gewährt. Diese stehen dem Aspekt dann als eigene Übergabeparameter zur Verfügung:

@Before(„pointcutForDoSomeStuff() && args(para1,para2)“)
public void printParams(String para1, String para2) {
// nice aspect with access to parameters
}

Interessiert einen nur der erste Parameter, sieht das Statement so aus:

@Before(„pointcutForDoSomeStuff() && args(para1,..)“)

Für Zugriff auf den zweiten Parameter muss folgende Modifizierung vorgenommen werden:

@Before(„pointcutForDoSomeStuff() && args(..,para2)“)

Da aus Programmierer Sicht kein Unterschied zwischen Before- und After-Advice existiert, beschäftigt sich das folgende Beispiel mit dem Around-Advice. Hier ergeben sich interessante Möglichkeiten, da der Aspekt Zugriff auf den JoinPoint erhält und für dessen Ausführung zuständig ist. Bei dem JoinPoint handelt es sich um die mit einem Aspekt versehene Methode. Dadurch das die Ausführung dem Aspekt obliegt, kann im Fehlerfall eingegriffen und z.B. ein Retry-Mechanismus implementiert werden. Die Exception kann gecatched und der Methoden-Aufruf beliebig oft wiederholt werden:

@Around(„pointcutForDoSomeStuff“)
public Object retryexecution(ProceedingJoinPoint joinPoint) throws Throwable {
Exception exception = null;
int i = 0;
while (i < MAX_RETRY) {
try {
return joinPoint.proceed();
} catch (MyException e) {
exception = e;
}
i++;
}
throw exception;
}

Grade beim Aufruf von externen Services in verteilten Systemen kann so ein Retry-Mechanismus Sinn machen. Geht der erste Aufruf noch auf einen defekten Service, so kann der zweite erfolgreich die gewünschte Arbeit verrichten. Der Benutzer bekommt hierbei nicht einmal mit, dass ein Fehlverhalten aufgetreten ist.

Wer bereits mit Spring 2.x AOP gearbeitet hat, dem wird das Handling der obigen Beispiele vertraut vorkommen. Tatsächlich hat sich die Spring-API im AOP-Bereich seit der letzten Spring Version nicht geändert. Aufgrund der komfortablen API ist dies aber auch nicht Notwendig.

Datei Upload mit Spring 3

Veröffentlicht in: Java, spring | 0

Mein letzter Artikel beschäftigte sich mit der Erstellung eines Spring Web MVC Projektes ( http://martinzimmermann1979.wordpress.com/2012/05/23/in-10-minuten-ein-spring-web-mvc-projekt-erstellen/ ). Hier stand ein Controller zur Behandlung von GET- und POST-Requests zur Verfügung. Ersterer lieferte eine Benutzerliste, die in einer entsprechenden View angezeigt wurde. Zweiterer war für das Hinzufügen von weiteren Benutzern zuständig. Die bereits bestehenden Funktionalitäten sollen nun um einen Datei-Upload erweitert werden.

Zusätzlich zu den bereits verwendeten Spring-Libraries, wird folgende Maven dependency benötigt:

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>

In einem neu zu erstellenden FileController wird die Methode handleUpload benötigt. Da die Datei per POST-Request hochgeladen wird, erhält das Attibute method des RequestMappings den entsprechenden Wert. In der Methoden-Signatur wird diesmal kein
ModelAttribute verwendet, da kein Mapping zwischen Model und Formular-Daten stattfinden muss. Die Binär-Daten sind direkt im Request enthalten und per RequestParam zugreifbar.
Die Datei wird vom Client als Multipart-Request zum Server gesendet, welche in Spring von der Klasse MultipartFile repräsentiert wird. Über die getInputStream-Methode erhält man Zugriff auf den InputStream, der nun verarbeitet werden kann.

@RequestMapping(value = „/file“, method = RequestMethod.POST)
public String handleUpload(@RequestParam(„file“) MultipartFile file) {
if (file.isEmpty()) {
return „redirect:/uploadFailure“;
}
try {
InputStream inputStream = file.getInputStream();
// do with the InputStream, what you want to do
} catch (IOException e) {
return „redirect:/uploadFailure“;
}
return „redirect:/uploadSuccess“;
}

Die obigen Redirects auf die Ressourcen uploadSuccess und uploadFailure führen natürlich noch ins Leere. Wie diese Umzusetzen sind, kann meinem letzten Artikel entnommen werden.

Um per HTLM-Formular eine Datei hochzuladen muss das form-Attribut enctype auf multipart/form-data gesetzt werden. Außerdem wird über die action auf die handleUpload-Ressource verwiesen. Da der RequestParam MultipartFile im RequestParam mit file benannt wurde, muss auch das input-Tag diesen Namen erhalten. Das Attribut type wird ebenfalls auf file gesetzt, damit sich im Browser der Dateiauswahl-Dialog öffnet.

<form action=“file“ method=“post“ enctype=“multipart/form-data“>
<p>Datei:<br>
<input name=“file“ type=“file“>
<input type=“submit“ value=“Absenden“>
</p>
</form>

Die Spring Konfiguration, die für das Hochladen von Dateien notwendig ist, ist recht übersichtlich. Es muss lediglich ein CommonsMultipartResolver definiert werden, damit Spring mit dem MultipartFile umgehen kann:

<bean id=“multipartResolver“
class=“org.springframework.web.multipart.commons.CommonsMultipartResolver“ />

Anschließend können Dateien über das oben definierte Formular auf den Server hochgeladen und dort verarbeitet werden.

In 10 Minuten ein Spring Web MVC Projekt erstellen

Veröffentlicht in: Java, spring | 0

Bereits einen vorangegangenen Artikel widmete ich dem Thema REST Services mit Spring 3 (http://martinzimmermann1979.wordpress.com/2012/02/29/rest-services-mit-spring-3) und auch der folgende Beitrag beschäftigt sich mit Spring Web MVC. Diesmal wird allerdings eine Web-Seite realisiert und als Views JSPs (Java Server Pages) verwendet.

Innerhalb von maximal 10 Minuten soll ein vollständiges Spring Web MVC Projekt erstellt werden, mit dem sich eine Benutzerliste darstellen und über ein Formular weitere Benutzer hinzufügen lassen. Zum Einsatz kommt die aktuelle Spring Version 3.1.0. Da ich als Entwicklungsumgebung Eclipse verwende, werden zusätzlich ein paar Eclipse spezifische Details erläutert, grundsätzlich kann das Beispiel aber aus jeder IDE heraus nachvollzogen werden.

Zunächst wird in Eclipse ein Maven Projekt erstellt. Sofern das Eclipse WTP-Plugin (Web Tool Platform – http://www.eclipse.org/webtools/) installiert ist, kann man anschließend über die Projekt Einstellungen, die Project Facets konfigurieren. Hier wird nun das Häkchen bei Dynamic Web Module gesetzt.

Natürlich kann man auch andersherum vorgehen und zuerst ein Web Projekt erstellen und es anschließend zu einem Maven Projekt konvertieren.

Nun werden die drei Spring Libraries Core, Spring-Web und Spring-WebMVC als Maven Dependencys in die pom.xml eingetragen:

<properties>
<spring.version>3.1.0.RELEASE</spring.version>
</properties>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

Um mit Maven ein Web-Archive (WAR-Datei) erstellen zu können, muss das maven-war-plugin in die Maven-Konfiguration eingetragen und das Packaging von jar auf war umgestellt werden:

<packaging>war</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>

Sobald das Maven-Packaging geändert wird, erscheint in der Problem View von Eclipse folgender Fehler:

Project configuration is not up-to-date with pom.xml. Run Maven->Update Project Configuration or use Quick Fix.

Wie in der Meldung beschrieben, muss lediglich die Projekt-Konfiguration aktualisiert werden, um das Problem zu beheben.

Wer bereits mit WTP gearbeitet hat, ist gewohnt, dass das Web-Verzeichnis unter WebContent zu finden ist. Beim Aktivieren des Facets Dynamic Web Module wurde dieses Verzeichnis irreführender weise angelegt. Da unser Beispiel-Projekt allerdings mit Maven gebaut wird, ist dessen Standardverhalten entscheidend. Daher können die Verzeichnisse WebContent/WEB-INF getrost gelöscht und stattdessen src/main/webapp/WEB-INF angelegt werden. Hier wird vom maven-war-plugin die web.xml Datei erwartet – ist diese nicht vorhanden bricht der Build-Prozess mit einer Fehlermeldung ab.

In der web.xml Datei wird nun das DispatcherServlet definiert, welches alle eingehenden Requests annimmt und an die verantwortlichen Controller delegiert. Das DispatcherServlet ist Bestandteil von Springs IoC-Container und ermöglicht die Nutzung aller von Spring zur Verfügung gestellten Features:

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

<servlet-mapping>
<servlet-name>myWebApp</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

Der Spring-Dispatcher scannt die unter Spring-Context stehenden Klassen nach Controller Annotationen und erkennt für enthaltene Methoden, ob ein RequestMapping angegeben wurde. Unter dem hier angegebenen Pfad ist die Ressource dann später erreichbar, da das DispatcherServlet entsprechende Requests an die Controller-Methoden delegiert.

Wenden wir uns nun dem UserController zu, der im Package de.mz.server erstellt und mit @Controller annotiert wird. Für die Darstellung der Benutzerliste ist die Methode getUsers verantwortlich, die ein RequestMapping unter dem Pfad users erhält. Da in der web.xml als url-pattern der Root-Pfad angegeben wurde, ist die Ressource unter http://<ip-addresse>:<port>/de.mz.server/users erreichbar. Als Rückgabewert dient der Methode getUsers ein ModelAndView-Objekt. Wie der Name schon sagt, beinhaltet das Objekt die anzuzeigende View und als Model die Benutzerliste:

@Controller
public class UserController {

@RequestMapping(„/users“)
public ModelAndView getUsers() {
List<User> users = loadUsers();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName(„users“);
modelAndView.addObject(„users“, users);
return modelAndView;
}

// add loadUsers-method
}

Widmen wir uns nun der Spring-Konfiguration, die im WEB-INF-Verzeichnis platziert wird. Der Name der Datei beginnt mit dem in der web.xml definierten Servlet-Namen ergänzt um „-servlet.xml“ – also myWebApp-servlet.xml. Damit der UserController von Spring generiert wird, wird das Component-Scanning für das Package de.mz.server aktiviert:

<context:component-scan base-package=“de.mz.server“/>

Im Controller wurde dem ModelAndView-Objekt der View Name users gesetzt. Um den Namen auf eine View zu mappen, muss ein ViewResolver definiert werden. Abhängig von der View stellt Spring hier verschiedene Strategien zur Verfügung. In diesem Beispiel wird der UrlBasedViewResolver verwendet, der ohne explizites Mapping auskommt, da die Namen der Views automatisch auf die View-Ressourcen gemappt werden:

<bean id=“viewResolver“ class=“org.springframework.web.servlet.view.UrlBasedViewResolver“>
<property name=“viewClass“ value=“org.springframework.web.servlet.view.JstlView“/>
<property name=“prefix“ value=“/WEB-INF/jsp/“/>
<property name=“suffix“ value=“.jsp“/>
</bean>

Als viewClass wird die JstlView gewählt, es werden also JSPs unterstützt, welche die JSP Standard Tag Library verwenden können. Per Präfix wird definiert, dass sich die JSP-Seiten in dem Unterverzeichnis jsp befinden und das Suffix gibt an, dass Views die gleichnamige Datei-Endung besitzen. Die im UserController referenzierte View users befindet sich also unter WEB-INF/jsp/users.jsp und hat folgenden Inhalt:

<%@taglib uri=“http://java.sun.com/jsp/jstl/core“ prefix=“c“%>
<html>
<head><title>Users</title></head>
<body>

<h3>Users</h3>
<table>
<tr>
<th>Vorname</th>
<th>Nachname</th>
</tr>

<c:forEach var=“user“ items=“${users}“>
<tr>
<td>${user.forename}</td>
<td>${user.lastname}</td>
</c:forEach>
</table>

</body>
</html>

Ohne die explizite Angabe der Tag-Library in der ersten Zeile, können die c-Tags nicht verwendet werden. Mit Hilfe des forEach-Befehls wird über die Liste der Benutzer iterieren. Dem items-Attribut wird die Liste der Benutzer zugewiesen, indem auf das im ModelAndView-Objekt hinterlegte Model verwiesen wird. Im var-Feld wird eine temporäre Variable definiert, über die während der Schleifendurchläufe auf die Benutzer zugegriffen werden kann. Vor- und Nachnamen der Benutzer können so in separate Spalten der Tabelle geschrieben werden. Natürlich müssen hierfür die Eigenschaften forename und lastname im Model vorhanden und mittels getter-Methoden zugreifbar sein:

public class User {

private String forename;
private String lastname;

// getter and setter

}

Sofern die Web-Applikation in einem Tomcat-Server laufen soll, muss die JSTL-Library als Maven Dependency eingebunden werden, da diese nicht im Tomcat integriert ist:

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

Im Anschluss kann die Web-Applikation mit Maven gebaut werden. Dies geschieht über den Kommandozeilen-Aufruf mvn package, oder aus der Eclipse IDE heraus. Egal welchen Weg man wählt, nach erfolgreichem Build-Prozess befindet sich die frisch erzeugte WAR-Datei im Target-Verzeichnis und kann deployed werden.

Im zweiten Schritt sollen weitere Benutzer über ein Formular hinzufügbar sein. Hierzu wird der UserController um die Methode addUser erweitert. Über das RequestMapping wird der Pfad auf user und POST als Request-Art gesetzt. Als Übergabe-Parameter kann direkt das Model – also die User-Klasse – verwendet werden, da sich Request-Parameter bequem mit Objekt-Eigenschaften verknüpfen lassen. Hierzu reicht es aus, den Übergabe-Parameter mit der Annotation ModelAttribute zu versehen. Der hier vergebene Name userForm muss später auch im HTML-Formular verwendet werden. Nach erfolgreichem Speichern des Benutzers, soll anschließend ein redirect auf die users-Ressource stattfinden, damit die vervollständigte Benutzerliste betrachtet werden kann. Daher ist der Rückgabe-Wert diesmal ein String und kein ModelAndView-Objekt:

@RequestMapping(value=“/user“, method = RequestMethod.POST)
public String addUser(@ModelAttribute(„userForm“) User user) {
// save user
return „redirect:/users“;
}

Widmen wir uns dem HTML-Formular. Die Input-Tags im HTML-Formular müssen die gleichen Namen verwenden, wie die entsprechenden Eigenschaften in der User-Klasse und es müssen die dazugehörigen setter-Methoden existieren. Das Form-Attribut modelAttribute erhält denselben Namen, wie die gleichnamige Annotation im Controller, um das Mapping zwischen Input-Feldern und Eigenschaften der User-Klasse zu ermöglichen. Als action wird der Pfad zur addUser-Methode und als method POST angegeben, was den RequestMapping-Einstellungen des Controllers entspricht:

<form action=“user“ modelAttribute=“userForm“ method=“POST“>
<p>Vorname:<br><input name=“forename“ type=“text“></p>
<p>Nachname:<br><input name=“lastname“ type=“text“></p>
<p><input type=“submit“ value=“Absenden“></p>
</form>

Nach einem Redeployment können wie in der Zielsetzung beschrieben, nun weitere Benutzer hinzugefügt werden.

Innerhalb kürzester Zeit wurde mit Spring 3.x eine Web-Applikation erstellt, die aus Controller und View zur Administrierung von Benutzern besteht. Hierfür wurde das DispatcherServlet in die web.xml-Datei eingetragen und für die Auswahl der richtigen View ein ViewResolver verwendet, der den View-Namen auf eine JSP-Datei mappt. Alle weiteren Einstellungen ließen sich bequem per Annotation direkt im Controller vornehmen.

REST Services mit Spring 3

Veröffentlicht in: Java, spring | 0

Ging vor ein paar Jahren der Trend bei der Übertragung von Informationen zwischen Softwaresystemen noch in Richtung Web-Services, so geht mittlerweile die Tendenz klar in Richtung REST.

REST selber basiert auf dem HTTP-Protokoll und ist zustandslos, da alle benötigten Informationen Bestandteil des jeweiligen HTTP-Requests sind. Die einzelnen Ressourcen die ein REST-Service zur Verfügung stellt, sind über eindeutige URIs erreichbar. Auf den Ressourcen können unter anderem folgende Operationen ausgeführt werden:

  • GET – Abfrage einer Ressource
  • POST- Zur aufgerufenen Ressource wird eine Sub-Ressource erzeugt
  • PUT – Verändern einer bestehende Ressource
  • DELETE – Löschen einer Ressource

Aufgrund der Entwicklung der vergangenen Jahren und der immer weiteren Verbreitung von REST wurde JAX-RS mit der Version 1.1 in den Java EE 6 Standard aufgenommen. Aktuell wird an der Version 2.0 gearbeitet, welche in den Java EE 7 Standard einfließen soll. Die gängigsten JAX-RS Implementierungen sind:

  • Jersey – Referenzimplementierung von Oracle
  • RESTEasy von JBoss
  • Restlet

Alle diese JAX-RS Implementierungen unterstützen Spring, was auch der Grund dafür ist, dass Spring selber keine eigene JAX-RS Implementierung anbietet. Man ist daher also auf eines der oben genannten Frameworks angewiesen.

Da ich mich bereits mit Restlet beschäftigt habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/01/restlet-rest-im-osgi-kontext/), habe ich mich im folgenden Beispiel für dieses REST-Framework entschieden. Restlet kann unter http://www.restlet.org/ in der aktuellen Version 2.0.11 heruntergeladen werden.

Natürlich gibt es auch die Möglichkeit Restlet als Maven Dependency zu laden. Hier kann man zusätzlich die Restlet-Spring-Extension verwenden, die Spring in der Version 3.0.1 verwendet. Will man eine neuere Spring Version einbinden – aktuell ist Spring 3.1.0 verfügbar – so muss man dies explizit in der pom.xml definieren.

Hier die Maven Dependencys für Restlet:

<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet.ext.spring</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-oxm-tiger</artifactId>
<version>1.5.9</version>
</dependency>

Die Library spring-oxm-tiger beinhaltet den Jaxb2Marshaller, der zum marshalling bzw. unmarshalling von Java Objekten in XML und umgekehrt benötigt wird.

Für die Ergebnis-Darstellung wird Spring MVC verwendet. Unter Spring 3.x wurden weitere Views, wie die MarshallingView zur Darstellung von XML-Inhalten und die MappingJacksonJsonView für JSON-Inhalte, hinzugefügt.

Im folgenden Beispiel soll ein REST-Service umgesetzt werden, der die bekannten GET, POST, PUT und DELETE Operationen auf der Ressource MyBean zur Verfügung stellt. Im ersten Schritt findet der Datentransfer ausschließlich mittels XML statt, im zweiten Schritt soll zusätzlich JSON unterstützt werden. D.h. der Service kann XML und JSON verarbeiten und abhängig vom unterstützten Datenformat des Clients findet die Antwort des Servers in XML oder JSON statt.

Starten wir mit der Spring-Konfiguration. Zunächst muss die MVC Java Konfiguration aktiviert werden:

<mvc:annotation-driven />

Weiterhin wird das Component-Scanning auf Package-Ebene aktiviert. Hiermit erspart man sich, jede Bean einzeln in der Konfigurationsdatei anzugeben:

<context:component-scan base-package=“de.mz.rest“ />

Nun wird das RequestMapping auf Type- und Methoden-Ebene aktiviert:

<bean class=“org.spring…DefaultAnnotationHandlerMapping“/>
<bean class=“org.spring…AnnotationMethodHandlerAdapter“ />

Anschließend wird der Jaxb2Marshaller definiert, der fürs marshalling bzw. unmarshalling von Objekten in XML und umgekehrt zuständig ist. Unter classesToBeBound sind alle Objekte aufgelistet, die der Marshaller in irgendeiner Form umwandeln soll:

<bean id=“jaxbMarshaller“ class=“org.springframework.oxm.jaxb.Jaxb2Marshaller“>
<property name=“classesToBeBound“>
<list>
<value>de.mz.rest.data.MyBean</value>
</list>
</property>
</bean>

Für die Darstellung der vom Jaxb2Marshaller aufbereiteten XML-Inhalte ist die MarshallingView zuständig:

<bean id=“viewName“ class=“org.springframework.web.servlet.view.xml.MarshallingView“>
<constructor-arg ref=“jaxbMarshaller“ />
</bean>

Zu guter Letzt wird noch ein ViewResolver benötigt, der die Requests auf die entsprechenden Controller weiterleitet. Hier wird der BeanNameViewResolver verwendet. Über das RequestMapping im Controller selber, wird  das Mapping später aber noch mal explizit gesetzt:

<bean  class=“org.spring…BeanNameViewResolver“ id=“viewResolver“ />

Nun wird die Klasse de.mz.rest.data.MyBean erstellt, bei der es sich um die Bean handelt, die vom Jaxb2Marshaller in XML umgewandelt werden soll. Hierfür werden JAXB (Java Architecture for XML Binding) Annotationen verwendet:

@XmlRootElement
@XmlType
public class MyBean {
private String id;
private String message;
// getter and setter for id and message
}

Die Annotation @XmlRootElement definiert MyBean als ein Top Level Element. @XmlType hingegen kann dazu verwendet werden, um für die Attribute von MyBean eine bestimmte Reihenfolge zu definieren. Eigentlich ist diese Annotation laut JAXB-Spezifikation nicht zwingend nötig, allerdings benötigt der Jaxb2Marshaller diese Information. Andernfalls erachtet sich der Jaxb2Marshaller für die Klasse MyBean als nicht zuständig und beim Versuch diese Klasse in XML zu transformieren wird folgende, irreführende Fehlermeldung geworfen:

javax.servlet.ServletException: Unable to locate object to be marshalled in model

Wenden wir uns nun der REST-Ressource zu, die die gewohnte Spring MVC Annotation @Controller verwendet:

@Controller
@RequestMapping(„/myPath“)
public class MyResource extends ServerResource {

@RequestMapping(value=“/{id}“, method=RequestMethod.GET)
public ModelAndView get(@PathVariable String id)  {
return new ModelAndView(„viewName“, „object“, new MyBean());
}
}

Wie man in obigem Beispiel sieht, kommt die @RequestMapping Annotation zweimal zum Einsatz. Zum einen wird über die annotierte Klasse MyResource der Pfad definiert, unter dem die komplette REST-Ressource erreichbar ist, zum anderen werden die Methoden-Details definiert, wie Übergabe-Parameter und die jeweiligen Operationen (GET, POST, …) für die die Methode zuständig ist. Um innerhalb der Methode Zugriff auf die id aus dem Pfad zu erlangen, wird diese einfach als Übergabe-Parameter angegeben und mit der Annotation @PathVariable versehen. Nun muss dem ModelAndView-Element noch der ViewName der MarshallingView und die gewünschte MyBean übergeben werden. Die obige REST-Ressource ist über einen GET-Request auf die URI http://<server>:<port>/<webapp>/myPath/<id> aufrufbar.

Die letzten Einstellungen die noch fehlen, sind der ContextLoaderListener und das DispatcherServlet in der web.xml Datei:

<listener>
<listener-class>org.spring…ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>servletName</servlet-name>
<servlet-class>org.spring…DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

Bislang liefert die REST-Ressource nur ein einziges MyBean-Element zurück, welches über die id spezifiziert wird. Will man die vollständige Liste an MyBeans erhalten, wird ein Wrapper für eine Liste von MyBeans benötigt. Auch dieser Wrapper, die MyBeanList, ist wie gehabt mit dem JAXB @XmlRootElement zu versehen:

@XmlRootElement
@XmlType
public class MyBeanList {

private List<MyBean> myBeans;

public List<MyBean> getMyBeans() {
return myBeans;
}
public void setMyBeans(List<MyBean> myBean) {
this.myBean = myBean;
}
}

Anschließend kann die MyBean-Ressource um folgende Methode ergänzt werden:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView getMyBeans() {
List<MyBean> myBeans = new ArrayList<MyBean>();
// add MyBeans to list
MyBeanList beanList = new MyBeanList();
beanList.setMyBeans(myBeans);
return new ModelAndView(„viewName“, „object“, beanList);
}

Es ist zu beachten, dass auch die MyBeanList in der Spring Konfiguration des Jaxb2Marshaller unter classesToBeBound hinzugefügt werden muss.

Diese Ressource ist mittels eines GET-Requests unter der URI http://<server>:<port>/<webapp>/myPath erreichbar.

Die GET-Requests aus den vorangegangenen Beispielen sind recht simpel zu testen, da diese Ressourcen über einen Web-Browser aufrufbar sind. Die vom Jaxb2Marshaller serialisierten Daten werden im Browser als XML dargestellt. Zusätzlich sollen aber noch die Operationen POST, PUT und DELETE umgesetzt werden. Der POST und PUT Request beinhaltet Daten, die von der REST-Ressource deserialisiert werden müssen, um das Objekt MyBean zu erhalten. Hier nun die Umsetzung der POST-Schnittstelle:

@RequestMapping(method=RequestMethod.POST)
public ModelAndView addBean(@RequestBody MyBean bean) {
// add bean
return new ModelAndView(„viewName“, „object“, bean);
}

Die Methode des RequestMapping wird als POST-Operation definiert. Die Daten selber werden als Übergabeparameter in die Methoden-Signatur aufgenommen und müssen lediglich mit der Annotation @RequestBody versehen werden. Den Rest übernimmt der Jaxb2Marshaller.

Die Ressource ist mit einem POST-Request unter der URI http://<server>:<port>/webapp/myPath erreichbar.

Zum Testen der Ressource kann das Kommandozeilen-Tool curl http://curl.haxx.se/ verwendet werden. Hier der Befehl inklusive Parameter, mit denen sich die Ressource Testen lässt:

curl -v -H „Accept: application/xml“ -H „Content-type: application/xml“ -X POST -d ‚<myBean><id>1</id><message>some text</message></myBean>‘  http://<server>:<port>/<webapp>/myPath

Hier die Erläuterung zu den einzelnen Parametern:

  • H <header>: Hiermit wird dem Request eine Header-Information hinzugefügt.
    1. Die Antwort des Servers soll XML sein, da dies vom Client akzeptiert wird.
    2. Die vom Client gesendeten Daten bestehen aus XML.
  • X <command>: Der Request wird ausgewählt, z.B. GET, POST, PUT oder DELETE
  • d <data>: Hier wird der eigentliche Inhalt des Requests definiert.
  • v: Steht für verbose. Wird verwendet um mehr Informationen zu erhalten, kann ansonsten aber auch weggelassen werden.

Wenn man lieber mit einem Grafischen Tool arbeiten möchte, kann man statt curl das Firefox Plugin REST Client verwenden.

Erweitern wir nun die REST-Ressource um eine PUT-Methode, damit eine bestehende MyBean verändert werden kann. Hierbei wird zum einen die id benötigt, die im Pfad angegeben wird und als @PathVariable in der Methoden-Signatur annotiert ist. Zum anderen wird – wie beim POST-Request – die mit @RequestBody annotierte MyBean übergeben, die die aktualisierten Daten beinhaltet:

@RequestMapping(value = „/{id}“, method=RequestMethod.PUT)
public ModelAndView updateMyBean(@PathVariable String id, @RequestBody MyBean bean) {
// update bean
return new ModelAndView(„viewName“, „object“, bean);
}

Aufrufbar ist diese Ressource unter einem PUT-Request auf http://<server>:<port>/webapp/myPath/<id>.

Für die DELETE-Operation wird nur die id der zu löschenden MyBean im Pfad übergeben. Die REST-Ressource gibt dann eine Liste der verbleibenden MyBeans zurück, die wie gehabt in dem Wrapper MyBeanList enthalten sind.

@RequestMapping(value = „/{id}“, method=RequestMethod.DELETE)
public ModelAndView removeMyBean(@PathVariable String id) {
// delete mybean and return list of remaining beans
return new ModelAndView(„todos“, „object“, remainingBeans);
}

Die Ressource wird mittels eines DELETE-Requests auf http://<server>:<port>/webapp/myPath/<id> aufgerufen.

Im letzten Teil des Artikels soll der REST-Service erweitert werden, dass er nicht nur XML sondern auch JSON unterstützt. Hierfür bietet Spring den ContentNegotiatingViewResolver an, der für die korrekte Darstellung das View Konzept verwendet. Abhängig vom gewünschten Inhalt - diese gibt der Client im Accept-Header des Requests mit an (z.B.: Accept: application/json) - wird die entsprechende View angesprochen.

Für die JSON Unterstützung wird das Jackson-Framework http://xircles.codehaus.org/projects/jackson benötigt, welches zur Zeit in der Version 1.9.5 zu downloaden ist.

Das Jackson Framework kann natürlich auch als Maven Dependency verwendet werden:

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.5</version>
</dependency>

Nun muss der ContentNegotiatingViewResolver konfiguriert werden:

<bean class=“org.spring…ContentNegotiatingViewResolver“>
<property name=“mediaTypes“>
<map>
<entry key=“json“ value=“application/json“/>
<entry key=“xml“ value=“application/xml“/>
</map>
</property>
<property name=“viewResolvers“>
<list>
<ref bean=“viewResolver“ />
</list>
</property>
<property name=“defaultViews“>
<list>
<ref bean=“viewName“/>
<bean class=“org.spring…MappingJacksonJsonView“>
<property name=“prefixJson“ value=“true“/>
</bean>
</list>
</property>
</bean>

Zunächst werden die beiden MediaTypes definiert, die unterstützt werden sollen, also XML und JSON. Als ViewResolver kann der bereits definierte BeanNameViewResolver verwendet werden. Ansonsten müssen jetzt noch die Views definiert werden. Da weiterhin XML unterstützt werden soll, erhält die Property defaultViews die bereits definierte MarshallingView übergeben. Außerdem wird hier noch die MappingJacksonJsonView definiert.

Die JSON-Unterstützung der REST-Ressource kann ebenfalls mit curl überprüft werden, indem die Header-Informationen und die Daten angepasst werden:

curl -v -H „Accept: application/json“ -H „Content-type: application/json“ -X POST -d ‚{„id“:“1″,“message“:“Some Text“}‘  http://<server>:<port>/<webapp>/myPath

Insgesamt lässt sich sagen, dass man mit Spring 3.x recht komfortable REST-Services erstellen kann. Die dafür benötigte Konfigurations-Arbeit hält sich in Grenzen und mit der Kombination aus Spring MVC- und JAXB-Annotations kommt man schnell zum Ziel. Lediglich die Dokumentation ist mangelhaft und man muss sich die Informationen recht mühsam zusammen suchen. Auch das die vom Jaxb2Marshaller zu transformierenden Klassen zwangsläufig die @XmlType Annotation benötigen, ist nur schwer nachzuvollziehen.

Testgetriebene Entwicklung mit JUnit, EasyMock und Spring

Veröffentlicht in: Java, test-driven development | 0

Die testgetriebene Entwicklung – test-driven development (TDD) – wird häufig in der agilen Softwareentwicklung eingesetzt. Hierbei werden zunächst die Testfälle konzeptioniert und erst anschließend die gewünschte Funktionalität umgesetzt. Aufgrund fehlender Umsetzung werden daher die Testfälle zunächst zwangsläufig fehlschlagen. Erst mit sukzessiver Funktions-Implementierung werden die Testfälle nach und nach erfolgreich durchlaufen.

Am einfachsten zu Testen sind Methoden, die für unterschiedliche Übergabe-Parameter entsprechende Rückgabe-Werte liefern wie z.B. statische Utility-Methoden. Um diese Methoden zu testen wird JUnit verwendet, welches unter http://www.junit.org/ in der aktuellen Version 4.10 zum Download bereit liegt. Alternativ kann man sich JUnit auch als Maven Dependency laden:

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

Das Testen einer Utility-Methode mit JUnit sieht dann folgendermaßen aus:

assertTrue(MyUtility.returnTrue(„parameter“));

Sofern die zu testende Methode Aufgaben an weitere Klassen delegiert, ist es nicht mehr möglich ausschließlich JUnit Tests zu verwenden, ohne eine vollständige Anwendungs-Umgebung zu initialisieren. Stattdessen müssen zusätzlich alle verwendeten Klassen mit initialisiert und getestet werden.

Hierbei handelt es sich dann nicht mehr um Modultests, sondern um Integrationstests, die definitiv wichtig sind und ihre Berechtigung haben, bei der testgetriebenen Entwicklung aber nicht im Vordergrund stehen. Hier soll nur das zu entwickelnde Modul getestet werden.

Daher werden bei der testgetriebenen Entwicklung Mocking-Frameworks verwendet, damit alle Klassen die in der zu testenden Methode verwendet werden, durch sogenannte Mock-Objekte ersetzt werden können. Diese Mocks bieten die Möglichkeit zu testen, ob die erwarteten Methode-Aufrufe mit den definierten Übergabe-Parametern erfolgen. Findet der Aufruf nicht statt oder ein nicht erwarteter Aufruf, so schlägt der Testfall fehl. Die Funktionalität der gemockten Objekte wird also nicht mit getestet, sondern kann in separaten Testfällen geprüft werden.

Als Mocking-Framework wird im folgenden Beispiel EasyMock verwendet, welches unter http://easymock.org/ in der aktuellen Version 3.1 verfügbar ist. Auch EasyMock ist als Maven Dependency erhältlich:

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymockclassextension</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>

Die erste Library wird zum Mocken von Interfaces benötigt, was als best practice anzusehen ist. Ist es dennoch mal nötig eine Klasse zu mocken, weil man z.B. bestehenden Code nachträglich testen möchte, kann die EasyMock-Classextension verwendet werden.

Kommen wir nun zu einem kleinen Beispiel. Es wird ein UserService getestet der einen User erhält und diesen mittels eines UserDAOs speichern soll. Hierbei wird der UserDAO gemockt und es soll überprüft werden, ob die save-Methode des DAOs auch wirklich aufgerufen wird.

Auch wenn bei der testgetriebenen Entwicklung zuerst der Testfall erstellt werden sollte, folgt zum besseren Verständnis zunächst der UserService und erst anschließend die Testfälle:

public class UserService {

private UserDAO userDAO;

public User save(User user) {
return userDAO.save(user);
}

public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
}

Zuallererst wird ein Setup initialisiert, in welchem der DAO gemockt und die erwarteten Methoden-Aufrufe definiert werden. Die Annotation @Before besagt hierbei, dass das Setup vor der Ausführung jedes einzelnen Testfalls in dieser Test-Klasse ausgeführt werden soll.

@Before
public void setUp() {
name = „userName“;
user = new User(name);
userDAO = createMock(UserDAO.class);
expect(userDAO.save(user)).andReturn(user); // (1)
replay(userDAO);

userService = new UserService();
userService.setUserDAO(userDAO);
}

Wie bereits erwähnt wird der UserDAO gemockt und die Erwartung ausgesprochen, dass die save-Methode mit dem gegebenen User aufgerufen wird. Bei der expect-Methode handelt es sich um eine statische Methode von EasyMock, die verwendet wird, wenn der erwartete Methode-Aufruf einen Return-Value besitzt. Über die andReturn-Methode kann anschließend der Rückgabe-Wert definiert werden, den das Mock-Objekt zurückgeben soll.

Hätte die save-Methode keinen Rückgabe-Wert, sähe die Stelle (1) folgendermaßen aus:

userDAO.save(user);

Der expect-Aufruf würde also einfach weggelassen. Der replay-Aufruf in der darauf folgenden Zeile besagt, dass keine weiteren Aufrufe auf dem UserDAO-Mock erwartet werden und nun nur noch die Aufrufe folgen, die bei Test-Ausführung getätigt werden.

Nun muss der UserService noch den UserDAO-Mock gesetzt bekommen und wir können zum eigentlich Test kommen. Hierbei sollte man sich nicht wundern, das Setup ist meist deutlich länger als der eigentliche Test.

@Test
public void shouldSaveUser() {
User save = userService.save(user);
verify(userDAO);
assertNotNull(save);
assertEquals(„userName“, save.getName());
}

Hier wird die save-Methode des UserService aufgerufen und anschließend die verify-Methode von EasyMock aufgerufen. Damit wird überprüft ob die zuvor definierten Mock-Aufrufe auch wirklich getätigt werden oder nicht. Anschließend kann der Rückgabe-Wert wie gehabt mit JUnit überprüft werden.

Um EasyMock etwas genauer zu verstehen modifizieren wir nun die Zeile (1) der Setup-Methode:

expect(userDAO.save(new User(„userName“))).andReturn(user);

In der Setup-Methode wird dem UserDAO-Mock also ein anderer User übergeben, als dem UserService im Test, lediglich der Namen ist gleich. Da nur der Name auf Gleichheit überprüft wird, läuft der Testfall wie gehabt durch. Fügen wir nun dem Testfall noch folgende Zeile hinzu:

assertEquals(save, user);

Hierauf hin wird der Testfall fehlschlagen. Grund dafür ist, dass EasyMock die equals-Methode der User-Klasse überprüft, um zu Testen ob der Übergabe-Parameter gleich ist. Sofern die equals-Methode nicht überschrieben wurde, wird die equals-Methode von Object verwendet, die auf gleiche Referenz testet. Da hier nun zwei unterschiedliche Objekte mit lediglich den gleichen Property-Werten verwendet werden, schlägt der Testfall fehl. Sobald allerdings die equals-Methode von der User-Klasse überschrieben wird und hierbei auf Namens-Gleichheit getestet wird, ist der Test-Durchlauf wieder erfolgreich.

Dies Verhalten ist auch der Grund, warum ich auf einen weiteren wichtigen Bestandteil von EasyMock kommen möchte, das Capture.

Stellen wir uns vor, der UserService reicht das User-Objekt nicht direkt zum UserDAO durch, sondern erstellt aufgrund des Users ein UserEntity, welches vom UserDAO konsumiert wird. Die Properties des Users sind hierbei identisch mit denen des UserEntities. Die Implementierung sieht also so aus:

public User save(User user) {
UserEntity userEntity = new UserEntity(user.getName());
UserEntity entity = userDAO.save(userEntity);
User result = new User(entity.getName());
return result;
}

Sofern das UserEntity viele Properties beinhaltet, müßte für den Testfall also ein exakt gleiches UserEntity erstellt werden, damit das UserEntity im Testfall und das im UserService erzeugte von EasyMock als gleich anerkannt wird. Sofern die equals-Methode nicht überschrieben werden dürfte, würde der Testfall sogar immer fehlschlagen, da es sich zwangsläufig um zwei unterschiedliche Referenzen handeln würde. Aber auch so ein Fall lässt sich problemlos mit EasyMock abbilden.

Hierzu muss lediglich der übergebene User durch ein Capture ersetzt werden:

captureUser = new Capture<UserEntity>();
userDAO = createMock(UserDAO.class);
expect(userDAO.save(capture(captureUser))).andReturn(userEntity);
replay(userDAO);

Es wird ein Capture Objekt erzeugt, welches nicht direkt in die save-Methode des UserDAOs gegeben wird. Stattdessen wird hier die EasyMock-Methode capture aufgerufen. Nun kann im Test über das Capture auf das dem Mock übergebene Objekt zugegriffen und die Werte überprüft werden:

assertNotNull(captureUser.getValue());
UserEntity userEntity = captureUser.getValue();
assertEquals(„userName“, userEntity.getName());

Mit diesem Funktionsumfang von EasyMock lassen sich bereits die meisten Test-Szenarien abdecken. Ab und an wird man in spezial Fällen Lösungen in der EasyMock-Dokumentation suchen müssen, für die meisten Anwendungsfälle dürfte es aber genügen.

Nun noch ein kleiner Exkurs über die Modultest hinaus zu den Integrationstests. Sofern man Spring verwendet kann es sehr nützlich sein, für Testfälle einen Spring-Context hochzuziehen. Folgende Spring-Library ermöglicht dies:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.0.6.RELEASE</version>
<scope>test</scope>
</dependency>

Nun legt man den zu testenden Service an und definiert diesen in der Spring-XML Konfiguration, welche sich im Classpath befinden sollte:

<version=“1.0″ encoding=“UTF-8″?>
<beans>
<context:annotation-config/>
<bean id=“myService“ class=“MyService/>
</beans>

Nun kann man die XML-Konfiguration über die Annotation ContextConfiguration angeben und muss anschließend die Runner Klasse definieren. Da in der Spring-Konfiguration Annotations aktiviert wurden, kann nun der Service direkt in den Testfall per Autowire-Annotation injected werden:

@ContextConfiguration(locations = { „/test.xml“ })
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringContextTest {

@Autowired
private MyService myService;

@Test
public void shouldExecuteMyService() {
assertNotNull(myService);
assertTrue(myService.doWhatYouHaveTodo());
}
}

Über die testgetriebene Entwicklung wird immer wieder gesagt, dass sie zu langsam und damit zu teuer ist. Dies konnte ich bislang nicht feststellen. Sicherlich ist es zunächst mehr Aufwand testgetrieben zu Entwickeln, aber die Einarbeitung in jedes neue Framework kostet zunächst einmal Zeit und bringt es mit sich, sich bei neuen Gegebenheiten erneut mit der API und deren Dokumentation zu beschäftigen. Der Zeitaufwand der beim Erstellen der Testfälle selber benötigt wird, ist überschaubar und wird durch die direkt bemerkten Flüchtigkeitsfehler wieder wett gemacht. Es muss nicht umständlich ein Server gestartet werden, nur um dann doch wieder ein Fehlverhalten festzustellen. Es ist grundsätzlich schneller einen Testfall auszuführen, als bei einer Software manuell einen ganz bestimmten Zustand zu testen.

Migration eines Eclipse RCP Projektes auf 4.x

Veröffentlicht in: Eclipse, Java, RCP | 0

Zur Zeit liegt das Eclipse SDK in Version 4.1 zum Download unter http://www.eclipse.org/eclipse4/ bereit. Im Gegensatz zum Early Adopter Release (Version 4.0) verwendet die Version 4.1 die neuere Indigo Update Seite, die seit dem 22 Juni 2011 zur Verfügung steht. Der Vorgänger verwendete hier noch die Update Seite von Helios.

Für die Migration eines bestehenden RCP Projektes auf das Eclipse SDK 4.1 habe ich mich dazu entschieden, dass Produkt Plugin komplett zu ersetzten, um die Vorteile von Eclipse 4.x direkt verwenden zu können. Insgesamt besteht das Projekt aus 13 reinen Plugin Projekten und weiteren 3 Projekten, die sowohl dem Server als auch dem Client zur Verfügung stehen. Hierbei handelt es sich um Maven Projekte, das Manifest wird mit dem Maven Plugin Felix erstellt. Weiterhin wird in den Client Projekten Spring DM eingesetzt, die OSGI Services werden hierüber veröffentlicht.

Nachdem das Eclipse SDK installiert ist fällt einem neben dem veränderten Aussehen vor allem die Geschwindigkeit auf, mit der sich nun die Plugins installieren lassen. Selbst die Installation von größeren Plugins wie WTP gehen extrem schnell vonstatten. Nach der Installation der benötigten Plugins (Eclipse e4 Tools, Subclipse, WTP, Maven, Maven Extras) werden zunächst alle Projekte in den aktuellen Workspace ausgecheckt, mit Ausnahme von der Target Plattform und dem Produkt Plugin.

Schnell ist ein neues Projekt angelegt, welches als neues Target dienen soll. Hier werden nun die benötigten neuen Eclipse SDK 4.1 Plugins rein kopiert und natürlich alle anderen verwendeten OSGI Libraries. Nun fügt man dem Projekt noch die Target Definition hinzu und konfiguriert sie entsprechend. Nachdem nun die von Eclipse verwendete Target Plattform auf das neu angelegte Target gewechselt wurde, dürften die Compile-Fehler in den Projekten verschwunden sein.

Anschließend kann das neue Produkt Plugin erstellt werden, hierbei handelt es sich zunächst um ein ganz normales „Plug-in Project“, welches als OSGI Framework Equinox verwendet. Öffnet man nun das Manifest kann man mit einem Klick auf Extensions den entsprechenden Reiter sichtbar machen. Nun fügt man als Abhängigkeit org.eclipse.equinox.app hinzu und setzt das Häkchen bei „This plug-in is a singleton“. Anschließend fügt man die Extension org.eclipse.core.runtime.products
hinzu und trägt bei ID product ein. Dem Feld wird nun ein Product hinzugefügt mit der Application org.eclipse.e4.ui.workbench.swt.E4Application und einem beliebigen Namen. Dem Product wiederum wird die Property appName mit einem entsprechenden Namen hinzugefügt. Es wird noch eine zweite Property mit dem Namen applicationXMI benötigt, mit dem Wert <Projektname>/Application.e4xmi. Die entsprechende Datei existiert zum jetzigen Zeitpunkt noch nicht, wird aber im folgenden Schritt angelegt.

Dem Projekt wird nun ein neues Application Model hinzugefügt. Dies ist nur möglich, wenn vorher die Eclipse e4 Tools erfolgreich installiert worden sind. Als Container wird das Produkt Projekt angegeben, der Dateiname Application.e4xmi sollte beibehalten werden.

In dem sich nun öffnenden Editor fügt man unter Windows ein Trimmed Window hinzu, hier kann der Titel und die gewünschte Fenster-Größe definiert werden. Nun benötigt das Projekt noch eine Product-Configuration. Sobald sich der entsprechende Editor geöffnet hat, fügt man neben den eigenen Projekten noch folgende Abhängigkeiten hinzu:

  • org.eclipse.equinox.ds
  • org.eclipse.equinox.event
  • org.eclipse.e4.ui.workbench.renderers.swt

Nun kann das neu erstellte Produkt zum ersten Mal gestartet werden, es wird sich ein leeres Fenster in der gewünschten Größe mit dem angegebenen Titel öffnen.

Will man nun noch Spring DM verwenden, muss anschließend noch das Bundle org.springframework.osgi.extender gestartet werden. Werden jetzt die eigenen Bundle gestartet, erkennt Spring DM – sofern vorhanden – die XML-Konfigurationsdateien im META-INF Ordner und fährt für die jeweiligen Bundle den Spring Context hoch.

Jetzt erst wird die erste Java Klasse im Product Projekt erstellt, die bisher benötigten Standard Implementierungen von

  • ActionBarAdvisor
  • WorkbenchAdvisor
  • WorkbenchWindowAdvisor
  • IApplication
  • IPerspectiveFactory

sind endlich nicht mehr nötig.

Stattdessen kann direkt mit der Implementierung von Funktionalitäten begonnen werden, für ein simples Beispiel reicht zunächst einmal ein ExitHandler zum Beenden der Workbench aus.

public class ExitHandler {

@Execute
public void execute(IWorkbench workbench) {
workbench.close();
}

}

Die Workbench selber, wird dem ExitHandler hierbei von OSGI per Dependency Injection übergeben. Es können beliebige weitere Parameter verwendet werden, wie z.B. in Spring DM exportierte OSGI Services oder andere OSGI Komponenten. Kann OSGI die entsprechenden Klassen nicht injizieren kommt es zu einer RuntimeException.

Um den ExitHandler zu verwenden müssen nun noch ein paar Einstellungen in der Application.e4xmi vorgenommen werden.  Zunächst wird ein Command erstellt mit der Id app.exit und dem Namen Exit. Jetzt wird ein Handler erstellt, der das gerade erstellte Command verwendet und der auf den ExitHandler als Implementierung verweist.

In dem Trimmed Window kann anschließend ein Main Menu erzeugt werden, dieses wiederum erhält ein Menu und dieses schlussendlich ein HandledMenuItem, welches das Exit Command Aufruft.

Natürlich soll das Applikations-Fenster entsprechenden Inhalt bekommen. Hierzu wird, wie in der Vorgänger Version, eine View benötigt. Mittlerweile muss diese aber nicht von ViewPart Erben, ein ganz normales POJO reicht als View aus. Lediglich über dem Konstruktor wird eine @Inject Annotation benötigt, so dass das Parent Composite vom OSGI Framework übergeben werden kann.

  @Inject
public MyView(Composite parent) {
// do what you have to do – something with ui for example
}

Um die View zu verwenden muss auch diesmal eine Anpassung in der Application.e4xmi vorgenommen werden. Unterhalb des Trimmed Window befindet sich der Punkt Controls, hier kann nun ein Part Stack erstellt werden. Dieser bekommt wiederum einen Part der die soeben erstellte MyView Klasse zur Ausführung erhält.

Dieser Artikel soll einen kurzen Einblick in Eclipse SDK 4.x vermitteln und erläutern wie man einfach ein bestehendes RCP Projekt migrieren kann. Grade die endlich in OSGI integrierte Dependency Injection und das neue Application Model mit dem hilfreichen und einfach zu bedienenden e4xmi-Editor machen Freude.