Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Neues in Java 7
2 Threads und nebenläufige Programmierung
3 Datenstrukturen und Algorithmen
4 Raum und Zeit
5 Dateien, Verzeichnisse und Dateizugriffe
6 Datenströme
7 Die eXtensible Markup Language (XML)
8 Dateiformate
9 Grafische Oberflächen mit Swing
10 Grafikprogrammierung
11 Netzwerkprogrammierung
12 Verteilte Programmierung mit RMI
13 RESTful und SOAP Web-Services
14 JavaServer Pages und Servlets
15 Applets
16 Datenbankmanagement mit JDBC
17 Technologien für die Infrastruktur
18 Reflection und Annotationen
19 Dynamische Übersetzung und Skriptsprachen
20 Logging und Monitoring
21 Java Native Interface (JNI)
22 Sicherheitskonzepte
23 Dienstprogramme für die Java-Umgebung
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
Java 7 - Mehr als eine Insel von Christian Ullenboom
Das Handbuch zu den Java SE-Bibliotheken
Buch: Java 7 - Mehr als eine Insel

Java 7 - Mehr als eine Insel
Rheinwerk Computing
1433 S., 2012, geb.
49,90 Euro, ISBN 978-3-8362-1507-7
Pfeil 17 Technologien für die Infrastruktur
Pfeil 17.1 Property-Validierung durch Bean Validation
Pfeil 17.1.1 Technische Abhängigkeiten und POJOs
Pfeil 17.2 Wie eine Implementierung an die richtige Stelle kommt
Pfeil 17.2.1 Arbeiten mit dem ServiceLoader
Pfeil 17.2.2 Die Utility-Klasse Lookup als ServiceLoader-Fassade
Pfeil 17.2.3 Contexts and Dependency Injection (CDI) aus dem JSR-299
Pfeil 17.3 Zum Weiterlesen

17 Technologien für die InfrastrukturZur nächsten Überschrift

»Wenn einer keine Angst hat, hat er keine Phantasie.«
– Erich Kästner (1899–1974)


Rheinwerk Computing - Zum Seitenanfang

17.1 Property-Validierung durch Bean ValidationZur nächsten ÜberschriftZur vorigen Überschrift

In den Settern einer JavaBean konnten wir mithilfe der PropertyChangeEvents Änderungen melden. Gleichsam kann ein Listener gegen eine ungewünschte Belegung sein Veto einlegen. Wenn eine Property selbst einen bestimmten Wert nicht annehmen kann, kann sie gut eine IllegalArgumentException melden. Die Validierung innerhalb von Settern ist aber nur lokal und nicht besonders flexibel, wenn es zum Beispiel Abhängigkeiten zwischen den Properties gibt oder temporäre Falschbelegungen erlaubt sein sollten.

So lässt sich ein anderer Weg einschlagen, nämlich dass zunächst eine Property mit allen möglichen Werten initialisiert werden darf (also auch das Alter einer Person mit 1 000) und erst später das Objekt zu einem Validator gegeben wird, der schaut, ob Property-Belegungen erlaubt sind. Seit Java EE 6 gibt es hier einen Standard, der im JSR-303, »Bean Validation«, beschrieben ist. An die Properties werden Annotationen gesetzt, die zum Beispiel erlaubte Minimal-/Maximalwerte für Zahlen bestimmen oder reguläre Ausdrücke für gültige Stringbelegungen. Dieses Vorgehen ist deklarativ und kommt ohne Programmierung aus. Im zweiten Schritt wird ein Validierer auf die Bean angesetzt, der die Zustände selbstständig ausliest und auf die Einschränkungen testet.

Bezug der JSR-303-Referenz-Implementierung

Jeder Java EE 6-Container bringt bereits eine Implementierung mit. Da Java 7 nicht mit einer Implementierung der JSR-303 daherkommt, muss sie extra installiert werden. Die Referenzimplementierung hat ihr Zuhause bei http://www.hibernate.org/subprojects/validator.html, und der Download befindet sich unter http://sourceforge.net/projects/hibernate/files/hibernate-validator. Im Zip-Archiv hibernate-validator-4.2.0.Final-dist.zip (etwa 16 MiB groß) stehen mehrere Jar-Dateien, die wir in den Klassenpfad aufnehmen müssen:

  • Zunächst ist es validation-api-1.0.0.GA.jar im Verzeichnis lib\required, das die neuen Annotationen wie @NotNull, @Size, @Pattern, ... deklariert.
  • Dann folgt mit hibernate-validator-4.0.2.GA.jar die eigentliche Implementierung des Frameworks.

Eine Person, die nie null heißen durfte

Hat ein Spieler einen Namen und ein Alter, so lassen sich leicht erste Gültigkeitsregeln aufstellen. Der Name soll gesetzt sein, also nicht null sein, und das Alter soll zwischen 10 und 110 liegen. Genau diese sogenannten Constraints werden über Annotationen an die Objektvariablen oder Getter gesetzt.

Listing 17.1: com/tutego/insel/bean/validation/Player.java, Player

import javax.validation.constraints.*;

public class Player
{
private String name;

@Min(10)
@Max(110)
private int age;

public void setName( String name )
{
this.name = name;
}

@NotNull
public String getName()
{
return name;
}

public void setAge( int age )
{
this.age = age;
}

public int getAge()
{
return age;
}
}

Die Annotation @NotNull sagt aus, dass getName() nie null liefern darf, und @Min(10)/@Max(110) besagt, dass sich age zwischen 10 und 110 (jeweils inklusiv) bewegen muss. Dass @Min(10) vor @Max(110) geschrieben wird, ist unwichtig, denn die Reihenfolge beim Prüfen ist unbestimmt. Ob die Annotation an den Attributen oder Gettern der Property sitzt, ist egal; im ersten Fall greift das Validierungsframework direkt auf das Attribut zu, und sind die Getter annotiert, wird die getXXX()-Methode aufgerufen. Die Sichtbarkeit der Objektvariablen spielt keine Rolle. Statische Variablen können nicht annotiert und geprüft werden.

Durchführen der Validierung

Das Beispiel zeigt die erste Hälfte der JavaBean Validation: die deklarative Angabe von gültigen Belegungen. Die zweite Hälfte von Validiation besteht aus einer API, damit die tatsächliche Überprüfung auch stattfinden kann und Fehler der einzelnen Properties erkannt werden.

Listing 17.2: com/tutego/insel/bean/validation/PlayerValidatorDemo.java, main() Teil 1

Player p = new Player();

Validator validator
= Validation.buildDefaultValidatorFactory().getValidator();

Set<ConstraintViolation<Player>> constraintViolations = validator.validate( p );

for ( ConstraintViolation<Player> violation : constraintViolations )
System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

Der Player wird nicht korrekt mit einem Namen und einem Alter initialisiert, sodass zwei Fehler zu erwarten sind. Die tatsächliche Überprüfung übernimmt ein Validator-Objekt, das über eine Fabrik erfragt wird. Der Validator besitzt eine validate()-Methode, die als Argument das zu validierende Objekt bekommt. Die Rückgabe ist eine Menge von Fehler-Objekten; gibt es keinen Fehler, ist die Menge leer. Da wir in unserem Beispiel zwei Fehler haben, iteriert die Schleife durch die Menge mit den ConstraintViolation-Objekten und gibt den Namen der Property und eine vordefinierte Fehlermeldung aus. Nach dem Start ergibt sich folgende Ausgabe (wobei die Ausgaben vom Logger ignoriert werden):

age muss grössergleich 10 sein
name kann nicht null sein

Dass die Ausgaben auf Deutsch sind, sollte uns nicht verwundern, denn die Implementierung ist internationalisiert.

Der Validator hat also die beiden Fehler korrekt erkannt. In der Ausgabe sehen wir vom ConstraintViolation-Objekt die Rückgaben von getPropertyPath() und getMessage(). Die erste Methode liefert den Namen der Property, die zweite eine Standard-Meldung, die wir aber auch überschreiben können, indem wir in der Annotation die Eigenschaft message setzen, etwa so: @NotNull(message="muss ungleich null sein"). Zusätzliche Methoden von ConstraintViolation sind unter anderem getRootBean(), getRootBeanClass(), getLeafBean() und getInvalidValue().

Mit validate(p) setzen wir den Validator auf das ganze Player-Objekt an. Es lassen sich aber auch einzelne Properties validieren. Dazu wird validateProperty() eingesetzt, eine Methode, die erst das zu validierende Objekt erwartet und anschließend im String den Namen der Property.

Listing 17.3: com/tutego/insel/bean/validation/PlayerValidatorDemo.java, main() Teil 2

Set<ConstraintViolation<Player>> ageViolation =
validator.validateProperty( p, "age" );
if ( ! ageViolation.isEmpty() )
System.out.println( new ArrayList<ConstraintViolation<Player>>(ageViolation)
.get( 0 ).getMessage() );

Die von validateProperty() zurückgegebene Menge ist entweder leer oder enthält Fehler. In der letzten Zeile kopieren wir zum Test die Fehlermenge in eine Liste und greifen auf den ersten Fehler zu.

class javax.validation.Validation
  • static ValidatorFactory buildDefaultValidatorFactory()
    Gibt die Standard-ValidatorFactory zurück.
interface javax.validation.ValidatorFactory
  • Validator getValidator()
    Liefert den Validator der Fabrik.
interface javax.validation.Validator
  • <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName,
    Class<?>... groups)

    Validiert vom Objekt object die Eigenschaft propertyName. Optionale Gruppen (sie werden später vorgestellt) sind möglich.
  • <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups)
    Validiert das gesamte Objekt.
interface javax.validation.ConstraintViolation<T>
  • String getMessage()
    Liefert die Fehlernachricht.
  • T getRootBean()
    Liefert die Hauptbean, die validiert wird.
  • T getLeafBean()
    Liefert die tatsächliche Bean, bei der die Eigenschaft validiert wird.
  • Object getInvalidValue()
    Liefert den fehlerhaften Wert.
Hinweis

Das Validation-Framework kann ganze Objektgraphen beachten und so eine tiefe Validierung durchführen. Wenn etwa ein Game-Objekt einen Player referenziert und die Validierung auf dem Game ausgeführt wird, kann der Player mit überprüft werden. Automatisch wird diese tiefe Prüfung aber nicht durchgeführt. Die Game-Klasse muss an der Player-Referenz die Annotation @Valid tragen. Selbst wenn das Game in einer Datenstruktur (Feld, Map oder alles Iterable wie Collection) viele Player referenziert, werden alle Spieler in der Sammlung überprüft, wenn die Sammlung die @Valid-Annotation trägt.

Die Constraints im Überblick

Unser Player nutzt drei Constraints, aber standardmäßig gibt es noch einige mehr. Zudem lassen sich eigene Constraints leicht programmieren. Die folgende Tabelle gibt einen Überblick über alle vordefinierten Contraints:

Tabelle 17.1: Die wichtigsten Annotationen von Bean-Validation

Constraint-Annotation Aufgabe Gültig an den Typen

@Null

@NotNull

Die Referenz muss null beziehungsweise nicht null sein.

Referenzvariablen

@AssertTrue

@AssertFalse

Das Element muss true beziehungsweise false sein.

boolean/Boolean

@Min(value=)

@Max(value=)

Muss eine Zahl und größer/kleiner oder gleich dem Wert sein.

byte/Byte, short/Short,
int
/Integer, long/Long, BigInteger, BigDecimal

@DecimalMin(value=)

@DecimalMax(value=)

Muss eine Zahl und größer/kleiner oder gleich dem Wert sein.

Double/Double und float/Float sowie String, byte/Byte, short/Short, int/Integer, long/Long, BigInteger, BigDecimal

@Size([min=],[max=])

Die Größe muss sich in einem Intervall bewegen.

String, Collection, Map, Feld

@Digits(integer=,fraction=)

Das Element muss eine gegebene Anzahl an Stellen besitzen.

String, byte/Byte, short/Short, int/Integer, long/Long, BigInteger, BigDecimal

@Past

@Future

Das Element ist ein Datum in der Vergangenheit/Zukunft bezogen auf jetzt.

Date, Calendar

@Pattern(regex=[,flags=])

Der String muss einem Pattern gehorchen.

String

Der Unterschied zwischen @Min/@Max und @DecimalMin/@DecimalMax ist der, dass im zweiten Fall ein String angegeben wird. So sind @Min(10) und @DecimalMax("10") gleichwertig. Bei @Digits ist die Angabe der Nachkommastellen nicht optional. Bei @Size können min/max alleine oder zusammen angegeben werden.

Beim @Pattern ist flags optional. Ohne Flag wird nur der reguläre Ausdruck als String angegeben, etwa so: @Pattern(regexp="\\d*"). Falls Flags angegeben werden, entsprechen sie dem Flag der Pattern-Klasse, etwa @Pattern(regexp="\\d*", flags=Pattern.Flag.CASE_INSENSITIVE).

Hinweis

Der Standard sieht double und float aufgrund von Rundungsproblemen nicht bei @Min/@Max/@DecimalMin/@DecimalMax vor, wobei die tatsächliche Implementierung das durchaus berücksichtigen kann. Wer Einschränkungen auf Fließkommazahlen benötigt, muss also im Moment den Variablentyp auf java.math.BigDecimal setzen.

Alle Annotationstypen deklarieren einen inneren Annotationstyp List, der eine Auflistung vom gleichen Contraint-Typ erlaubt. Die Verkettung erfolgt nach dem Oder-Prinzip. List ist sinnvoll, denn mehrfach kann die gleiche Annotation nicht an einem Element stehen.

Beispiel

Die Zeichenkette ist entweder eine E-Mail oder eine Webadresse:

@Pattern.List({ @Pattern(regexp="[\\w|-]+@\\w[\\w|-]*\\.[a-z]{2,3}"),
@Pattern(regexp="https?://[-\\w]+(\\.\\w[-\\w]*)") })
String emailOrWeb;

Es ist naheliegend, diese beiden komplexen Ausdrücke nicht zu einem großen unleserlichen regulären Ausdruck zu vermengen. Die Liste ist auch sinnvoll, wenn jeder Teilausdruck zu unterschiedlichen Gruppen gehört – zu den Gruppen folgt jetzt mehr.

Validierung von Gruppen

Ist ein Objekt korrekt, erfüllt es alle Validierungs-Constraints. Dieses Alles-oder-nichts-Prinzip ist jedoch etwas hart. Betrachten wir zwei Szenarien:

  • In der grafischen Oberfläche wird ein Spieler erfasst und später in der Datenbank gespeichert. Sein Name kann aber in der Oberfläche leer bleiben, da der Platz, falls nichts angegeben ist, mit einem Zufallsnamen gefüllt wird. Geht der Spieler jedoch in die Datenbank, so muss er auf jeden Fall einen Namen tragen. Je nach Lebenszkylus des Spielers sind also unterschiedliche Belegungen erlaubt.
  • Eine grafische Oberfläche zeigt einen Wizard über mehrere Seiten an. Auf der ersten Seite muss zum Beispiel der Spieler seinen Namen angeben, auf der zweiten Seite sein Alter. Würde auf der ersten Seite der Spieler komplett validiert werden, wäre das Alter ja noch gar nicht gesetzt, und die Validierung würde einen Fehler melden.

Da ein Objekt in den unterschiedlichsten Phasen korrekt sein kann, lassen sich Gruppen bilden. Um eine eigene Validierungsgruppe zu nutzen, wird

  1. eine leere Java-Schnittstelle aufgebaut,
  2. einer Annotation wie @NotNull ein Class-Objekt für die Validierungsschnittstelle mitgegeben und
  3. der validate()-Methode die Validierungsschnittstelle als letzter Parameter bekannt gegeben.

Führen wir das exemplarisch mit einem Spieler durch, der im ersten Schritt einen gültigen Namen haben muss und im zweiten Schritt ebenfalls ein gültiges Alter. Deklarieren wir zwei Schnittstellen in einer Klasse DialogPlayer. Um es kurz zu halten, verzichtet der DialogPlayer auf Setter/Getter.

Listing 17.4: com/tutego/insel/bean/validation/DialogPlayer.java, main()

public class DialogPlayer
{
public interface NameValidation { }
public interface AgeValidation extends NameValidation { }

@NotNull( groups = NameValidation.class )
public String name;

@Min( value = 10, groups = AgeValidation.class )
@Max( value = 110, groups = AgeValidation.class )
public int age;
}

Die ersten beiden Schritte sind hier in dem Spieler schon sichtbar. Die zwei Schnittstellen sind deklariert und als groups-Element bei den Validierungsannotationen angegeben. Das Validierungs-Framework erlaubt auch Vererbung, was das Beispiel bei interface AgeValidation extends NameValidation zeigt; AgeValidation basiert auf NameValidation, sodass der Name auf jeden Fall auch korrekt ist – also nicht null sein darf – und nicht nur allein das Alter sich zwischen 10 und 110 bewegt. Jeder Constraint kann zu mehreren Gruppen gehörten, aber das brauchen wir hier nicht. Standardmäßig gibt es auch eine Default-Gruppe, wenn keine eigene Gruppe definiert wird. Versteckt steht dann standardmäßig groups = Default.class.

Im dritten Schritt gilt es, der validate()-Methode das Class-Objekt für die Gruppe mitzugeben.

Listing 17.5: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 1

Validator v = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<DialogPlayer>> constraintViolations;

DialogPlayer p = new DialogPlayer();

constraintViolations = v.validate( p, DialogPlayer.NameValidation.class );
for ( ConstraintViolation<DialogPlayer> violation : constraintViolations )
System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

Der DialogPlayer hat keinen Namen, daher ist die Ausgabe »name kann nicht null sein«. Weitere Fehler kommen nicht vor, denn DialogPlayer.NameValidation.class validiert nur alle Eigenschaften, die ein groups = NameValidation.class tragen. Und das trägt nur name.

Setzen wir den Namen, und validieren wir neu:

Listing 17.6: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 2

p.name = "chris";
System.out.println( v.validate( p, DialogPlayer.NameValidation.class ).size() ); // 0

Es gibt keine Fehlermeldungen mehr, auch wenn das Alter nicht gesetzt ist.

Validieren wir mit dem DialogPlayer.AgeValidation, gibt es wieder einen Fehler, da age noch auf 0 war:

Listing 17.7: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 3

constraintViolations = v.validate( p, DialogPlayer.AgeValidation.class );
for ( ConstraintViolation<DialogPlayer> violation : constraintViolations )
System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

Die Ausgabe ist »age muss grössergleich 10 sein«. Dass AgeValidation einen Namen ungleich null und das Alter prüft, zeigt sich, wenn der name auf null gesetzt wird. Initialisieren wir das Alter, damit es nicht zwei Fehler, sondern nur einen Fehler wegen dem Namen gibt:

Listing 17.8: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 4

p.name = null;
p.age = 60;
constraintViolations = v.validate( p, DialogPlayer.AgeValidation.class );
for ( ConstraintViolation<DialogPlayer> violation : constraintViolations )
System.out.println( violation.getPropertyPath() + " " + violation.getMessage() );

Jetzt ist die Ausgabe nur »name kann nicht null sein«, da das Alter sich im korrekten Bereich befindet. Mit einem gesetzten Namen verschwinden alle Fehler:

Listing 17.9: com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main(), Teil 5

p.name = "chris";
System.out.println( v.validate( p, DialogPlayer.NameValidation.class ).size() ); // 0
Hinweis

Den validateXXX()-Methoden kann nicht nur eine Validierungsgruppe mitgegeben werden, sondern über ein Varargs auch mehrere. Doch wenn es etwa validate(p, V1.class, V2.class) heißt, ist die Reihenfolge unbestimmt, auch wenn der Aufruf suggeriert, es würden erst die Eigenschaften aus V1 validiert und dann die aus V2. Um eine feste Reihenfolge vorzugeben, muss eine neue Schnittstelle deklariert werden, die eine Annotation GroupSequence trägt, in der die Reihenfolge bestimmt wird.

@GroupSequence( {V1.class, V2.class} )
public interface OrderedValidation {}

Reihenfolgen sind auch nützlich, um schnelle Validierungen zuerst durchzuführen und anschließend Validierungen anzuwenden, die zeitaufwändiger sind.

Eigene Validatoren

Die Anzahl der Standard-Validatoren ist beschränkt, doch Anforderungen nach etwa einem E-Mail-Validator ergeben sich schnell. Wir wollen – ohne auf die Details von eigenen Annotationstypen genau einzugehen – einen neuen Validierungsannotationstyp für E-Mail-Adressen schreiben.

Beginnen wir mit einer Person, die ein Attribut email besitzt und deren E-Mail-Adresse geprüft werden soll. Die Variable email bekommt unsere eigene Annotation EMail.

Listing 17.10: com/tutego/insel/bean/validation/PersonValidator.java, PersonValidator

public class PersonValidator
{
public static class Person
{
@NotNull
@EMail
public String email; // = "a@b.com";
}

public static void main( String[] args )
{
Validator v = Validation.buildDefaultValidatorFactory().getValidator();

Person p = new Person();

Set<ConstraintViolation<Person>> constraintViolations = v.validate( p );
for ( ConstraintViolation<Person> violation : constraintViolations )
System.out.println( violation.getPropertyPath() + " " +
violation.getMessage() );
}
}

Läuft das Programm, gibt es zwei Fehlermeldungen aus, da email gleich null ist und nicht dem entsprechenden Pattern gehorcht:

email ist keine gültige E-Mail-Adresse
email kann nicht null sein

Die Meldung »ist keine gültige E-Mail-Adresse« kommt von unserem eigenen Annotationstyp. Der ist wie folgt deklariert:

Listing 17.11: com/tutego/insel/bean/validation/Email.java, EMail

@Constraint( validatedBy = EMailValidator.class )
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention( RUNTIME )
@Documented
public @interface EMail
{
String message() default "ist keine gültige E-Mail-Adresse";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

Auf die einzelnen Bestandteile, wie @Target, @Retention und die merkwürdige Schreibweise zur Deklaration neuer Annotationstypen, wollen wir an dieser Stelle nicht eingehen.[102](Die Meta-Annotationen sind Bestandteil von Kapitel 18, »Reflection und Annotationen«.) Wichtig ist:

  • message(), die unsere Ausgabe bestimmt. Die Meldung lässt sich über ein Resource-Bundle auch internationalisieren, doch auch das überspringen wir.
  • der Verweis auf die Implementierung des eigentlichen Validators. Der Klassenname steht in der Annotation @Constraint(validatedBy = EMailValidator.class).

Der EMailValidator implementiert ConstraintValidator und die zentrale isValid()-Methode:

Listing 17.12: com/tutego/insel/bean/validation/EmailValidator.java, EMailValidator

public class EMailValidator implements ConstraintValidator<EMail, String>
{
@Override
public void initialize( EMail constraintAnnotation ) { }

@Override
public boolean isValid( String value, ConstraintValidatorContext context )
{
return value != null && value.matches( "[\\w|-]+@\\w[\\w|-]*\\.[a-z]{2,3}" );
}
}

Ändern wir aus dem Beispielprogramm die Deklaration des Attributs email in

@NotNull
@EMail
public String email = "a@b.com";

so gibt es keinen Validierungsfehler mehr.

interface javax.validation.ConstraintValidator<A extends Annotation, T>
  • void initialize(A constraintAnnotation)
    Intitialisiert den Validator.
  • boolean isValid(T value, ConstraintValidatorContext context)
    Validiert den Wert value.

Rheinwerk Computing - Zum Seitenanfang

17.1.1 Technische Abhängigkeiten und POJOsZur nächsten ÜberschriftZur vorigen Überschrift

In objektorientierten Programmen stehen Klassen ganz im Mittelpunkt. Sie realisieren die komplette Geschäftslogik, aber auch Hilfsdienste wie String-Konvertierungen. In einer guten objektorientierten Modellierung existiert eine wohldurchdachte Objekthierarchie und ein bescheidendes Objektgeflecht, in dem jedes Objekt eine ganz spezielle Aufgabe erfüllt. Das krasse Gegenteil wäre eine Riesenklasse, die alles macht. Neben dem Wunsch, dass ein Objekt nur eine klar umrissene Aufgabe erfüllt, ist es optimal, wenn ein Objekt wenig Abhängigkeiten von anderen Objekten besitzt (das wird niedrige Kopplung genannt) sowie technische und fachliche Aspekte sauber trennt. Doch leider ist genau dies schwierig. Oftmals haben Klassen technische Abhängigkeiten und damit eine höhere Kopplung an genau diese technischen Realisierungen. Drei Zwänge erhöhen diese Kopplung:

  1. das Implementieren einer Schnittstelle,
  2. das Erweitern einer Oberklasse oder
  3. das Setzen einer Annotation.

Ein Beispiel: Wenn ein Objekt in Java serialisiert werden soll – das heißt, die Objektzustände können automatisch ausgelesen und in einen Datenstrom geschrieben werden –, dann muss die Klasse die Schnittstelle java.io.Serializable implementieren. Nehmen wir an, ein Konto-Objekt soll serialisiert werden, so hat das Objekt die fachliche Aufgabe, den Kontostand zu vermerken, hat aber gleichzeitig über die Implementierung der Schnittstelle einen technischen Bezug zur Serialisierung, die überhaupt nichts mit dem Konto an sich zu tun hat. Die Implementierung dieser Schnittstelle ist aber zwingend, denn andernfalls kann das Konto-Objekt nicht an der Standard-Serialisierung teilhaben.

Serialisierung ist nur ein Beispiel einer technischen Realisierung für Objektpersistenz. Es gibt andere Abhängigkeiten für Persistenz, die heutzutage durch Annotationen ausgedrückt werden. Dann ist es etwa die Annotation @Entity zur Beschreibung einer auf eine Datenbank abbildbaren Klasse oder @XmlElement für eine Abbildung einer Eigenschaft auf ein XML-Element. Ober wenn ein Dienst als Web-Service angeboten werden kann, kommt zur fachlichen Realisierung noch @WebMethod an die Methode.

In den letzten Jahren hat sich die Kopplung, also haben sich die Abhängigkeiten zur technischen Realisierung verschoben, wurden aber nicht wirklich aufgehoben. Dabei gab es eine interessante Entwicklung. In den Anfängen gab es oftmals Oberklassen, die zu erweitern waren, oder Schnittstellen, die zu implementieren waren. Es folgte dann eine Abkehr von diesen technischen Realisierungen, angestoßen durch IoC-Container[103](IoC-Container sind Umgebungen, bei denen die Objekte vom Container die Verweise auf andere Objekte gesetzt bekommen, anstatt das sich Objekte die Verweise auf die Beteiligten selbst besorgen.) wie Spring. Der Container verwaltet ein Objekt ohne technische Abhängigkeiten und setze zur Laufzeit etwa Persistenzeigenschaften dazu. Nun muss allerdings die Information, dass ein Objekt in einen Hintergrundspeicher persistiert werden kann, irgendwo vermerkt werden, sodass der Container den Wunsch auf Persistierung erkennt. Sogenannte Metadaten sind nötig. Hierzu wurden oftmals XML-Dokumente verwendet. Nun hat XML keinen guten Ruf, und es stimmt auch, dass megabyte-große XML-Dokumente keine gute Lösung.

Als in Java 5 Metadaten über Annotationen eingeführt wurden, verschwanden im Laufe der Zeit viele XML-Dokumente zur Beschreibung der Metadaten beziehungsweise wurden nur noch als optionaler Zusatz geführt. Annotationen lösten zwar die lästigen XML-Dokumente ab, bedeuten aber wiederum einen Schritt zurück, da sie die technischen Belange wieder in die Klasse hineinnehmen, die die XML-Dokumente gerade erst entfernt hatten.

Heutzutage besitzen alle bedeutenden Java-Frameworks eine große Anzahl von Annotationen zur Beschreibung einer technischen Realisierung, insbesondere für die Objektpersistenz, die bei Geschäftsanwendungen eine gewichtige Rolle einnimmt. Eine Modellierung aufzubauen, die keine technischen Abhängigkeiten hat, ist daher schwierig und wird von den meisten Entwicklern auch nicht verfolgt, da es oft zu aufwändig ist und zu mehr Klassen führt.

Plain Old Java Object (POJO)

Das Gegenteil dieser mit Abhängigkeiten und Annotationen vollgepumpten Klassen sind POJOs. Ein POJO[104](http://www.martinfowler.com/bliki/POJO.html) ist ein Plain Old Java Object, also ein ganz einfaches, nettes Java-Objekt ohne Infrastruktur-Abhängigkeiten. Ist ein POJO[105](Auch in der .NET-Welt gibt es Vergleichbares. Dort heißt es POCO (Plain Old CLR Object). Kurz war auch PONO für Plain Old .NET Object im Gespräch, aber das klang den Entwicklern wohl zu sehr nach POrNO. Aber wer schon WIX (Windows Installer XML) hat, dem kann PONO nicht zu peinlich sein ...) ein Objekt der Geschäftslogik, dann sollte es

  • keine technische Schnittstelle implementieren,
  • keine spezielle technische Oberklasse erweitern und
  • keine Annotationen tragen.

Die POJOs spielen bei einem Entwurfsmodell des sogenannten Domain Driven Design (DDD) eine zentrale Rolle, in dem es um saubere Objektorientierung geht und das fachliche Problem im Mittelpunkt steht.

Ernüchternd lässt sich jedoch feststellen, dass heutzutage auch Klassen, die Annotationen tragen, POJOs genannt werden. Entwickler argumentieren, es seien »nur« Metadaten, die in einem anderen Kontext auch gar nicht ausgewertet und benutzt werden müssten. Das ignoriert jedoch die Tatsache, dass die Klasse mit den Annotationen schlichtweg gar nicht erst compiliert werden kann, wenn der Annotationstyp nicht bekannt ist. Hier gibt es folglich eine ganz klare Abhängigkeit, auch wenn diese geringer als bei zu implementierenden Schnittstellen oder Oberklassen ist.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
  Zum Katalog
Neuauflage: Java SE 8 Standard-Bibliothek
Neuauflage: Java SE 8 Standard-Bibliothek
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Einstieg in Eclipse






 Einstieg in Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de