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.

Eclipse Extension Points und Extensions

Veröffentlicht in: Eclipse, RCP | 1

Eclipse beinhaltet ein hoch modulares Konzept um Erweiterungen Plugin-übergreifend realisieren zu können. Das zu erweiternde Plugin bietet einen Extension-Point an, an dem andere Plugins mit ihren Extensions andocken können. Beide Arten an Plugins müssen hierbei zwangsläufig im Manifest als singleton definiert sein.

Um einen Extension-Point zu definieren, wird die plugin.xml Datei des zu erweiternden Bundles geöffnet und zum Reiter Extension Points gewechselt. Sofern keine plugin.xml Datei existiert, kann alternativ im Manifest (MANIFEST.MF) der genannte Reiter aktiviert werden, Eclipse erstellt nun automatisch die plugin.xml Datei. Nun kann ein Extension-Point erzeugt werden, als ID und Namen erhält er im folgenden Beispiel den Wert de.mz.myExtensionPoint und myExtensionPoint.


Nach erfolgreicher Erstellung des Extension-Points wird automatisch das dazugehörige Schema geöffnet. Auch wenn es nicht zwingend notwendig ist, sollten alle Attribute im Schema – Reiter Definition – definiert werden. Nur so können die Felder beim Anlegen einer Erweiterung bequem im Plug-In Manifest Editor über die UI angegeben werden können. Will man später Veränderungen am Schema vornehmen, so ist dieses in der Dateistruktur des Projektes unter schema/myExtensionPoint.exsd zu finden.

Hier wird nun ein neues Element mit dem Namen client erzeugt, dem zwei Attribute zugewiesen werden. Das Erste Attribut hat den Namen class und ist vom Typ Java. Hier hat man nun die Möglichkeit ein Interface zu hinterlegen, welches von der im Attribut class verwiesenen Klasse zu implementieren ist. Im folgenden Beispiel ist das zu implementierende Interface IMyExtension.

Das Interface selber enthält für Beispielzwecke die Methode doSomething:

public interface IMyExtension {
public void doSomething(String text);
}

Das zweite Attribut erhält den Namen text und ist vom Typ String. Hier sind keine weiteren Einstellungen nötig.

Per Context-Menü wird der extension zunächst ein Choice-Element zugewiesen, welches wiederum den Eintrag client erhält. Damit die Extension beliebig viele Client-Einträge erhalten kann, wird die Max Occurrences des Client-Elements auf Unbounded gesetzt. Eine Extension kann also mehrere Erweiterungen enthalten.

Die Plugin.xml hat nun folgenden Eintrag erhalten:

<extension-point id=“de.mz.myExtensionPoint“ name=“myExtensionPoint“ schema=“schema/myExtensionPoint.exsd“/>

Damit andere Bundles nun tatsächlich den Extension-Point erweitern können, müssen sie das Interface IMyExtension implementieren können. Daher muss im Manifest das Package des Interfaces exportiert werden:

Export-Package: de.mz

Damit ist die Definition des Extension-Points abgeschlossen und wir können uns der Erstellung einer Extensions zuwenden. Öffnen wir hierzu die plugin.xml Datei des Bundles, welches den Extension-Point erweitern soll. Auf dem Reiter Extensions kann zwischen den möglichen Erweiterungen gewählt werden, auch der zuvor erstelle Extension-Point de.mz.myExtensionPoint wird hier aufgeführt. Per Context-Menü können hier die Client-Erweiterungen hinzugefügt werden. Für die im Schema deklarierten Attribute stehen im Plug-In Manifest Editor Eingabehilfen zur Verfügung, so kann bequem für das class Attribut eine Implementierung und für das text Attribut ein String angegeben werden.

Diese Einstellung führt zum folgenden Eintrag in der plugin.xml Datei:

<extension point=“de.mz.myExtensionPoint“>
<client
class=“de.mz.MyExtensionImpl“
text=“Hallo Welt“>
</client>
</extension>

Extension-Point und eine dazu passende Extension wurden bereits erstellt, nur Verwendung findet die Erweiterungen bislang noch nicht. Doch hierzu bietet Eclipse die ExtensionRegistry an, auf die man über die Runtime-Platform Zugriff erhält:

IConfigurationElement[] extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(„de.mz.extension.example.myExtensionPoint“);
for (IConfigurationElement extension : extensions) {
try {
String text = extension.getAttribute(„text“);
IMyExtension myExtension = (IMyExtension) extension.createExecutableExtension(„class“);
myExtension.doSomething(text);
// do with myExtension what you have to do
} catch (CoreException e) {
// do some logging
}
}

In obigem Beispiel erhält man alle Erweiterungen die zum Extension-Point de.mz.extension.example.myExtensionPoint gehören. Nun können die Attribute der einzelnen Erweiterungen erfragt werden – hierbei ist es nicht relevant, ob diese im Schema definiert sind – und für das Klassen-Attribut ein entsprechendes Objekt erstellen werden (createExecutableExtension). Bei einem optionalen Klassen-Feld macht es Sinn, sich zunächst zu vergewissern, dass das Attribut einen Wert zugewiesen bekommen hat. Andernfalls wird nämlich eine CoreException geworfen, die über diese Abfrage verhindert werden kann.

Auch wenn das Beispiel auf die Eclipse 3.x Plattform ausgelegt ist, funktioniert der Mechanismus grundsätzlich auch bei Eclipse 4. Hier spart man sich den Umweg über die Runtime-Platform, da sich die ExtensionRegistry bequem per Dependency Injection injizieren lässt.

Will man das Beispiel testen, kann das Laden der Extensions in einem Activator vorgenommen werden, der hierfür lediglich im Manifest eingetragen werden muss:

Bundle-Activator: de.mz.Activator

Nun kann das Extension-Point Bundle als OSGI Framework ausgeführt werden. Treten hierbei Fehler auf, sollten in der Run-Configuration zunächst alle Bundles deselektiert und anschließend nur die Plugins ausgewählt werden, die den Extension-Point und die Extensions beinhalten. Natürlich werden auch Eclipse spezifische Bundles benötigt, die sich über einen Klick auf Add Required Bundles hinzufügen lassen.