16Technologien für die Infrastruktur
»Wenn einer keine Angst hat, hat er keine Phantasie.«
– Erich Kästner (1899–1974)
16.1Property-Validierung durch Bean Validation
In den Settern einer JavaBean konnten wir mithilfe der PropertyChangeEvents Änderungen melden. Gleichsam kann ein Listener gegen eine unerwü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. Hierfür gibt es einen Standard, der im JSR-303, »Bean Validation«, beschrieben ist, aktuell ist Bean Validation 1.1 (http://beanvalidation.org/).
Bei der Bean-Validation werden an die Properties Annotationen gesetzt, die zum Beispiel erlaubte Minimal-/Maximalwerte für Zahlen bestimmen oder reguläre Ausdrücke für gültige String-Belegungen. 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
Ab Java EE 6 ist Bean-Validierung Teil eines jeden Applikationsservers. Da das JDK nicht mit einer Implementierung der JSR-303 daherkommt, muss sie extra installiert werden. Die Referenzimplementierung hat ihr Zuhause bei http://hibernate.org/validator/, und der Download befindet sich bei SourceForge unter http://sourceforge.net/projects/hibernate/files/hibernate-validator/. Das ZIP-Archiv, etwa hibernate-validator-5.0.3.Final-dist.zip (ca. 18 MiB groß), enthält zwei JAR-Dateien, die wir in den Klassenpfad aufnehmen müssen:
Im Verzeichnis dist/lib\required liegt validation-api-1.1.0.Final.jar, das die neuen Annotationen wie @NotNull, @Size, @Pattern, … deklariert.
Dann folgt im Verzeichnis dist mit hibernate-validator-5.0.3.Final.jar die eigentliche Implementierung des Frameworks.
Aus dist\lib\required sind drei zusätzliche JAR-Dateien nötig: jboss-logging-3.1.1.GA.jar, classmate-1.0.0.jar, javax.el-2.2.4.jar, auf die die Implementierung intern zurückgreift.
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 so genannten Constraints werden über Annotationen an die Objektvariablen oder Getter gesetzt:
Listing 16.1com/tutego/insel/bean/validation/Player.java, Player
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 16.2com/tutego/insel/bean/validation/PlayerValidatorDemo.java, main() Teil 1
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 javax.validation.Validator-Objekt, das über eine Fabrik erfragt wird. Der Validator besitzt eine validate(T object, Class<?>... groups)-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):
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 Standardmeldung, die wir aber auch überschreiben können, indem wir in der Annotation die Eigenschaft message setzen, etwa so:
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(T object, String propertyName, Class<?>... groups) eingesetzt, eine Methode, die erst das zu validierende Objekt erwartet und anschließend im String den Namen der Property.
Listing 16.3com/tutego/insel/bean/validation/PlayerValidatorDemo.java, main() Teil 2
validator.validateProperty( p, "age" );
if ( ! ageViolation.isEmpty() )
System.out.println( new ArrayList<>(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.
static ValidatorFactory buildDefaultValidatorFactory()
Gibt die Standard-ValidatorFactory zurück.
Validator getValidator()
Liefert den Validator der Fabrik.
<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.
String getMessage()
Liefert die Fehlernachricht.T getRootBean()
Liefert die Haupt-Bean, 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. Tabelle 16.1 gibt einen Überblick über alle vordefinierten Contraints:
Constraint-Annotation | Aufgabe | Gültig an den Typen |
---|---|---|
@Null | Die Referenz muss null bzw. nicht null sein. | Referenzvariablen |
@AssertTrue | Das Element muss true bzw. false sein. | boolean/Boolean |
@Min(value=) | Muss eine Zahl und größer/kleiner oder gleich dem Wert sein. | byte/Byte, short/Short, |
@DecimalMin(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 | Das Element ist ein Datum in der Vergangenheit/Zukunft bezogen auf jetzt. | Date, Calendar |
@Pattern(regex=[,flags=]) | Der String muss einem Pattern gehorchen. | String |
Tabelle 16.1Die wichtigsten Annotationen von Bean-Validation
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 Constraint-Typ erlaubt. Die Verkettung erfolgt nach dem Oder-Prinzip. List ist sinnvoll, denn mehrfach kann die gleiche Annotation nicht an einem Element stehen.
[zB]Beispiel
Die Zeichenkette ist entweder eine E-Mail oder eine Webadresse:
@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 Lebenszyklus 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
eine leere Java-Schnittstelle aufgebaut,
einer Annotation wie @NotNull ein Class-Objekt für die Validierungsschnittstelle mitgegeben und
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 16.4com/tutego/insel/bean/validation/DialogPlayer.java, main()
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 sich nicht nur allein das Alter 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 16.5com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main() Teil 1
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 16.6com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main() Teil 2
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 16.7com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main() Teil 3
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 des Namens gibt:
Listing 16.8com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main() Teil 4
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 sich das Alter im korrekten Bereich befindet. Mit einem gesetzten Namen verschwinden alle Fehler:
Listing 16.9com/tutego/insel/bean/validation/DialogPlayerValidatorDemo.java, main() Teil 5
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:
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 16.10com/tutego/insel/bean/validation/PersonValidator.java, PersonValidator
public static class Person {
@NotNull
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 kann nicht null sein
Die Meldung »ist keine gültige E-Mail-Adresse« kommt von unserem eigenen Annotationstyp. Der ist wie folgt deklariert:
Listing 16.11com/tutego/insel/bean/validation/Email.java, EMail
@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.[ 125 ](Die Meta-Annotationen sind Bestandteil von Kapitel 17, »Typen, Reflection und Annotationen«.) Wichtig sind:
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 16.12com/tutego/insel/bean/validation/EmailValidator.java, EMailValidator
@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
public String email = "a@b.com";
so gibt es keinen Validierungsfehler mehr.
void initialize(A constraintAnnotation)
Initialisiert den Validator.boolean isValid(T value, ConstraintValidatorContext context)
Validiert den Wert value.
16.1.1Technische Abhängigkeiten und POJOs
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:
das Implementieren einer Schnittstelle
das Erweitern einer Oberklasse
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[ 126 ](IoC-Container sind Umgebungen, bei denen die Objekte vom Container die Verweise auf andere Objekte gesetzt bekommen, anstatt sich die Verweise auf die Beteiligten selbst zu 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. So genannte 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 sind.
Als in Java 5 Metadaten über Annotationen eingeführt wurden, verschwanden im Laufe der Zeit viele XML-Dokumente zur Beschreibung der Metadaten bzw. 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[ 127 ](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[ 128 ](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 so genannten Domain Driven Designs (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.