Effiziente Navigation auf großen XML-Strukturen
XML eignet sich hervorragend für den plattformunabhängigen Austausch strukturierter Daten. JAXB ist ein unglaublich praktisches Stück Java-Technologie, das den Entwickler von der mühsamen Aufgabe befreit, manuell (De-)Serialisierungscode zu schreiben, und ihm reguläre Java-POJOs als Programmierschnittstelle bietet. Wenn es jedoch um die Navigation in großen, stark vernetzten Objektgraphen geht, die aus reiner JAXB gewonnen werden, kann es zu ernsthaften Leistungsproblemen kommen.
Dieser Artikel gibt einige Einblicke, wie man diese Herausforderungen auf elegante und dennoch effiziente Weise lösen kann. Das Open-Source-Projekt ist auf GitHub zu finden.
Die Herausforderung
Das Modell, das ich in diesem Artikel als Referenzbeispiel verwende, ist der Vehicle Electric Container, das zur Beschreibung des gesamten elektrischen Netzes eines modernen Autos verwendet werden kann. Um Ihnen ein Gefühl dafür zu geben, wovon ich spreche, wenn ich von “großen, stark vernetzten Objektgraphen” spreche, hier einige Schlüsselzahlen:
- Struktur:
- 300 verschiedene Klassen
- 200 einzelne Querverweise (Einschlüsse nicht mitgerechnet)
- Instanzen:
- > 300Mb XML
- > 1,5 Millionen Objekte
- > 2,3 Millionen Querverweise
Wenn Sie frei in der Struktur navigieren wollen, dann ist die Verwendung von JAXB von Haus aus nicht ausreichend. Ich werde erklären, wie man dies erreichen kann, ohne die Vorteile von JAXB zu verlieren, ohne umständlichen Code zu schreiben und ohne die SOLID-Prinzipien zu verletzen. Aber das Wichtigste zuerst.
Das Modell
Das obige Diagramm zeigt einen kleinen Teil des Modells, das ich verwenden werde, um die Herausforderungen und später die Lösung zu erklären. Es stellt den Schaltplan eines elektrischen Systems - die linke Seite (blau hervorgehoben) stellt die elektrischen Komponenten dar, die rechte Seite (grün hervorgehoben) die Verbindungen zwischen ihnen.
Eine erforderliche Funktion für dieses Modell könnte sein: Gib mir alle ComponentNodes, die mit ComponentPort X verbunden sind.
Es gibt viele Möglichkeiten, das Ergebnis zu berechnen, aber der einfachste und intuitivste Weg ist:
- Navigieren Sie zu den ConnectionEnds, die auf den ComponentPort X verweisen.
- Navigieren Sie zu der Verbindung der gefundenen Verbindungsenden und finden Sie die anderen Seiten (Verbindungsenden).
- Navigieren Sie von den anderen ConnectionEnds zu den referenzierten ComponentPorts und von dort in der Hierarchie nach oben, über den ComponentConnector zu dem ComponentNode.
- Sie haben Ihr Ergebnis erreicht!
So weit, so gut. Das Problem dabei ist, dass in reinem XML / JAXB die meisten der oben genannten Navigationen einfach nicht verfügbar sind. Warum ist das so? Werfen wir einen Blick auf die XML- und Java JAXB-Darstellung.
Das Schema
XML ist nicht speziell für die objektorientierte Serialisierung / Deserialisierung konzipiert, es ist ein viel allgemeineres Konzept. Um es für die OO-Serialisierung zu verwenden, müssen die OO-Konzepte auf definierte XML-Konzepte abgebildet werden. In unserem Fall ist das Transformationsmuster vom UML-Modell in XML-Schema wie folgt:
- Eine Klasse wird als
xs:complexType
übersetzt. - Eine Vererbung wird als
xs:extension
übersetzt. - Alle
xs:complexType
haben einxs:Attribut
mit dem Namenid
und dem Typxs:ID
als technischen Primärschlüssel innerhalb der XML-Datei. Dieses ist in der Auflistung nicht enthalten, da dieses Attribut bereits in derxs:extension base
definiert ist. - Ein Attribut wird als
xs:element
des entsprechendenxs:complexType
übersetzt. - Eine Komposition wird als
xs:element
übersetzt (verschachtelte Klassen/Elemente) mit der aggregierten Klasse alstype
. - Eine Assoziation wird als
xs:element
mitxs:IDREF(S)
alstype
übersetzt.
Die sich daraus ergebende XML-Schema-Definition für einige Klassen im obigen Diagramm finden Sie in der nachstehenden Auflistung (ich habe alle Details weggelassen, die für das Beispiel nicht relevant sind):
Teilweise Auflistung des XML-Schemas
<xs:complexType name="ComponentPort">
<xs:complexContent>
<xs:extension base="vec:ConfigurableElement">
<xs:sequence>
<xs:element name="Identification" type="xs:string"/>
...
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ComponentConnector">
<xs:complexContent>
<xs:extension base="vec:ConfigurableElement">
<xs:sequence>
<xs:element name="Identification" type="xs:string"/>
...
<xs:element name="ComponentPort"
type="vec:ComponentPort"
minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ConnectionEnd">
<xs:complexContent>
<xs:extension base="vec:ConfigurableElement">
<xs:sequence>
<xs:element name="Identification" type="xs:string"/>
...
<xs:element name="ConnectedComponentPort" type="xs:IDREF"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
Die Java-Klassen
Wenn Sie dieses Schema in den “JAXB XML Schema to Java Compiler” (XJC) eingeben, erhalten Sie Java-Klassen, die denen in der folgenden Liste ähneln.
Es gibt einige bemerkenswerte Details in den generierten Klassen, die Sie beachten sollten:
- Alle Klassen (z.B.
VecComponentPort
) wissen nichts über eingehende Referenzen. Es gibt auch keinen Verweis auf die enthaltende Klasse (z.B.VecComponentConnector
) und es gibt auch keine Möglichkeit der Rückwärtsnavigation zu referenzierenden Klassen (z.B.VecConnectionEnd
). Daher wird die oben beschriebene gewünschte Navigation blockiert. Hierfür gibt es zwei Gründe:- JAXB-Klassen können auch zur Serialisierung verwendet werden. Bidirektionale Assoziationen würden Informationen redundant speichern, inkonsistente Datenstrukturen zulassen und den Serialisierer noch Komplexität des Serialisierers.
- Aufgrund des allgemeinen Zwecks von XML ist es für den XJC unmöglich, eingehende Referenzen für alle XML-Anwendungsfälle zu bestimmen.
- Die Java-Darstellung von
IDREF
-Assoziationen (z.B.VecConnectionEnd.connectedComponentPort
) sind mit dem BasistypObject
definiert, obwohl das UML-Modell sie alsVecComponentPort
definiert. Der Grund dafür ist, dass die vom XML-Schema definierte Syntax weniger streng ist als die Definition des UML-Modells. In XML Schema sindIDREF(S)
Referenzen nicht typisiert und können auf jedes Element mit einer gültigenxs:ID
zeigen. Daher ist es für XJC unmöglich, den richtigen Typ zu bestimmen.
Auflistung der von JAXB generierten Klassen
Erste Java-Klasse: VecComponentPort
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ComponentPort", propOrder = {
"identification"
})
public class VecComponentPort
extends VecConfigurableElement
implements Serializable
{
@XmlElement(name = "Identification", required = true)
protected String identification;
public String getIdentification() {
return identification;
}
public void setIdentification(String value) {
this.identification = value;
}
}
Zweite Java-Klasse: VecComponentConnector
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ComponentConnector", propOrder = {
"identification",
"componentPorts"
})
public class VecComponentConnector
extends VecConfigurableElement
implements Serializable
{
@XmlElement(name = "Identification", required = true)
protected String identification;
@XmlElement(name = "ComponentPort")
protected List<VecComponentPort> componentPorts;
public String getIdentification() {
return identification;
}
public void setIdentification(String value) {
this.identification = value;
}
public List<VecComponentPort> getComponentPorts() {
if (componentPorts == null) {
componentPorts = new ArrayList<VecComponentPort>();
}
return this.componentPorts;
}
}
Dritte Java-Klasse: VecConnectionEnd
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ConnectionEnd", propOrder = {
"identification",
"connectedComponentPort"
})
public class VecConnectionEnd
extends VecConfigurableElement
implements Serializable
{
@XmlElement(name = "Identification", required = true)
protected String identification;
@XmlElement(name = "ConnectedComponentPort", required = true, type = Object.class)
@XmlIDREF
@XmlSchemaType(name = "IDREF")
protected Object connectedComponentPort;
public String getIdentification() {
return identification;
}
public void setIdentification(String value) {
this.identification = value;
}
public Object getConnectedComponentPort() {
return connectedComponentPort;
}
public void setConnectedComponentPort(Object value) {
this.connectedComponentPort = value;
}
}
Sackgassen-Ansätze
Für eine Implementierung mit reinen JAXB-Klassen habe ich zwei Ansätze gesehen, die beide ihre eigenen Nachteile haben. Um ein Bewusstsein für die daraus entstehenden Probleme zu schaffen, werde ich die beiden Ansätze kurz besprechen.
Der pragmatische Ansatz
Der pragmatische Ansatz lautet: “Wenn der direkte Weg versperrt ist, gehe ich außen herum!”. Wie in der realen Welt sind die Umwege jedoch normalerweise länger.
Bei diesem Ansatz würde allein die Navigation von ComponentPort
zu den angeschlossenen ComponentPorts
erfordern:
- Beginnen Sie bei der Wurzel (
ConnectionSpecification
), gehen Sie durch die Hierarchie und iterieren Sie über alleConnectionEnds
. - Prüfen, ob der referenzierte
ComponentPort
derselbe ist wie der, mit dem wir begonnen haben. - Wenn dies der Fall ist, wählen Sie die anderen
ConnectionEnds
. Für diesen Vorgang müssen Sie sich an Sie die aktuelleConnection
als Kontext, da eine Rückwärtsnavigation in der Hierarchie nicht möglich ist. - Navigieren Sie zu den verbundenen
ComponentPorts
. - Wenn Sie nun die entsprechenden
ComponentNodes
(die transitiven Eltern der ComponentPorts) wissen wollen, müssen Sie erneut eine vollständige Baumsuche starten, dieses Mal auf der linken Seite.
Falls dieses Beispiel nicht für sich selbst spricht, hier sind einige Gründe, warum Sie es niemals so machen sollten, wie gerade beschrieben:
- Leistung: Der skizzierte Algorithmus hat eine Laufzeitkomplexität von O(n + m) für eine Operation, die eine inhärente Komplexität von O(1) oder nahe daran hat (mit n=#all
ComponentPort
, m=#allConnectionEnd
).
Wenn Sie diese Strategie mehr als einmal anwenden und Ihre Funktionen in größeren Anwendungsfällen verwenden (z. B. verschachtelte Navigationen), kommen Sie leicht zu polynominaler oder sogar exponentielle Komplexität. Das Ergebnis bei großen Datenstrukturen ist dann, dass man buchstäblich ewig auf Ergebnisse warten muss, die man in einem Wimpernschlag berechnet werden könnten. Stabilität: Im Gegensatz zu einer direkten Navigation benötigen Sie immer Wissen über den Kontext der Navigationsziele. Um korrekt zu navigieren, müssen alle Ihre Tiefensuchen / Modellüberquerungen erfordern, dass Sie alle möglichen Positionen Ihrer Navigationsziele kennen. Angenommen, das zugrunde liegende Modell ändert sich so, dass z.B.Verbindungen
an einem anderen Ort als derConnectionSpecification
enthalten sein können. Ihr Navigationsalgorithmus wird zusammenbrechen, und Sie werden es nicht einmal merken. Wartbarkeit: Ihre implementierte Funktionalität ist viel komplizierter, als es das eigentliche Problem erfordern würde (einfache Navigation von A nach B vs. verschachtelte Schleifen mit gespeicherten Zuständen usw.). Infolgedessen ist Ihr Code schwieriger zu verstehen, fehleranfälliger, schwieriger zu ändern und zu testen, und das alles ohne guten Grund.
Der Caching-Ansatz
Bei diesem Ansatz berechnen Sie alle Rückwärtsnavigationen im Voraus und speichern die Ergebnisse irgendwo zur späteren Verwendung - nennen wir diesen “irgendwo” Assoziations-Cache. Nach der Deserialisierung der XML-Datei führen Sie im Grunde ein komplettes Modell-Traversal durch und legen alle relevanten Assoziationen in einem Cache für die Rückwärtssuche.
Dadurch wird das Leistungsproblem gelöst, indem der kontinuierliche Overhead für jede Operation durch einen einmaligen Overhead zur Startzeit ersetzt wird. Allerdings werden die Probleme der Stabilität und Wartbarkeit bleiben jedoch bestehen. Es ist immer noch ein komplexes Modell-Traversal erforderlich, Ihr Code benötigt immer noch Wissen über mögliche Navigationen und Sie verletzen das Single Responsibility Principle.
Das Prinzip der einzigen Verantwortung besagt, dass eine Codeeinheit die Verantwortung für eine einzige Funktionalität haben sollte, und diese Verantwortung exklusiv sein sollte. Im Gegensatz dazu wird die “Navigationsverantwortung” bei diesem Ansatz zwischen der von JAXB generierten Klasse (für Vorwärtsrichtungen) und dem Assoziations-Cache für Rückwärtsrichtungen.
Ein weiterer Nachteil dieses Ansatzes ist, dass der Cache eine Art Singleton-Objekt mit einem auf das aktuelle Modell beschränkten Geltungsbereich sein muss. Dieses Objekt muss für alle Stellen zugänglich gemacht werden, an denen eine Navigation in Rückwärtsrichtung erforderlich ist. Dies kann zu sehr unhandlichem Code führen.
Lösungsskizze
Es würde den Rahmen dieses Artikels sprengen, die Lösung in allen Einzelheiten mit all den kleinen Maschen und Knoten zu beschreiben. Daher werde ich nur die wichtigsten Konzepte erläutern und Sie Sie müssen die Lücken dazwischen selbst ausfüllen oder mich direkt für eine Diskussion kontaktieren.
Eine saubere Lösung der Aufgabe kann durch einige grundlegende Anforderungen definiert werden:
- Navigationen im Modell sollten mit einer Laufzeitkomplexität ausgeführt werden, die der inhärenten Komplexität der Navigation entspricht. Einfache Navigationen zum Kontext Element oder Rückwärtsnavigationen in einer Assoziation sollten O(1) sein.
- Entwickler, die Funktionen auf dem Modell implementieren, sollten sich nicht um die Unterschiede zwischen Vorwärts- und Rückwärtsnavigationen kümmern müssen (gleiches Zugriffsmuster).
- Die Implementierung sollte einfach zu warten und robust gegenüber Modelländerungen sein.
Der Weg, all dies zu erreichen, ist die offensichtlichste und einfachste Lösung, die man sich vorstellen kann: Wir machen einfach alle Assoziationen bidirektional navigierbar, mit fast keinen Auswirkungen auf die Laufzeit!.
Sie kennen vielleicht bidirektionale Assoziationen in Hibernate. Der Ansatz für diese Lösung ist fast die gleiche. Um sie zum Laufen zu bringen, haben wir zwei Bausteine:
- Wir müssen die JAXB-Klassen so modifizieren, dass sie bidirektionale Navigation unterstützen.
- Wir müssen die Assoziationen korrekt initialisieren.
JAXB-Klassenanpassung
Um bidirektionale Navigationen zu unterstützen, benötigen wir JAXB-Klassen, die wie das modifizierte VecComponentPort
in der folgenden Liste aussehen. Wie Sie sehen können, definiert sie eine “ParentComponentConnector”-Eigenschaft für die Navigation zum enthaltenden Kontextelement, sowie eine “RefConnectionEnd”-Eigenschaft für alle “ConnectionEnds”, die Referenzen zu diesem ComponentPort
definieren. Beide sind als @XmlTransient
markiert, da bidirektionale Navigationen von JAXB selbst nicht unterstützt werden und sie von dem JAXB (Un)Marshaller ignoriert werden.
Geänderte VecComponentPort.java
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ComponentPort", propOrder = {
"identification",
})
public class VecComponentPort
extends VecConfigurableElement
implements Serializable
{
@XmlElement(name = "Identification", required = true)
protected String identification;
@XmlTransient
private VecComponentConnector parentComponentConnector;
@XmlTransient
private Set<VecConnectionEnd> refConnectionEnd = new HashSet<VecConnectionEnd>();
public String getIdentification() {
return identification;
}
public void setIdentification(String value) {
this.identification = value;
}
public VecComponentConnector getParentComponentConnector() {
return parentComponentConnector;
}
public Set<VecConnectionEnd> getRefConnectionEnd() {
return refConnectionEnd;
}
}
Aber wie erreichen wir diese Änderungen? Wie immer gibt es viele Möglichkeiten. Sie könnten alle Klassen manuell ändern, aber das ist eine mühsame Aufgabe und die Änderung von generierten Klassen ist ein absolutes NO GO!
Glücklicherweise bietet XJC einen Plugin-Mechanismus, der die Ausführung von benutzerdefinierten Plugins während des Generierungsprozesses ermöglicht. Ein XJC-Plugin hat Zugriff auf das Code-Modell der generierten Klassen und kann diese modifizieren. Ein Beispiel für ein einfaches Plugin finden Sie hier.
Der folgende Codeschnipsel kann ein privates Feld mit einem optionalen Initialisierungswert und einer entsprechenden öffentlichen Getter-Methode in einer bestehenden Klasse erzeugen:
JCodeModel Beispiel
public JFieldVar build() {
final JFieldVar field = targetClass.field(JMod.PRIVATE, baseType, name);
if (init != null) {
field.init(init);
}
field.annotate(codeModel.ref(XmlTransient.class));
final JMethod getter = targetClass.method(JMod.PUBLIC, baseType, getterName);
getter.body()
._return(JExpr.ref(field.name()));
if (getterJavadoc != null) {
getter.javadoc()
.addAll(getterJavadoc);
}
return field;
}
Zusätzlich können XJC-Plugins durch JAXB-Bindings parametrisiert werden. Mit einer JAXB-Bindungsanpassung, können Sie spezifische Parameter für bestimmte Schemaelemente bereitstellen. Dies funktioniert sowohl für JAXB-Standardfunktionen als auch für XJC-Plugins. Wir haben also ein JAXB-Plugin implementiert, das die Informationen über Referenzen aus der Bindung übernimmt und den generierten Code entsprechend modifiziert.
Ein Teil der resultierenden Bindung ist unten aufgeführt. Sie enthält zwei Anpassungen:
<nav:parent/>
definiert, dass eine übergeordnete Assoziation für diese Klasse erstellt werden soll, mit dem angegebenen Typ und Namen.<nav:backref/>
definiert, dass eine Rückwärtsnavigation in der Zielklasse mit dem angegebenen Namen erstellt werden soll.
Wie Sie vielleicht bemerken, behebt diese Bindung auch das Problem der Typsicherheit von IDREF
Assoziationen.
Benutzerdefinierte JAXB-Bindung
<jxb:bindings node="//xs:complexType[@name='ComponentPort']">
<nav:parent name="parentComponentConnector" type="VecComponentConnector"/>
</jxb:bindings>
<jxb:bindings node="//xs:complexType[@name='ConnectionEnd']">
<nav:parent name="parentConnection" type="VecConnection"/>
<jxb:bindings node=".//xs:element[@name='ConnectedComponentPort']">
<nav:backref name="refConnectionEnd"/>
<jxb:property>
<jxb:baseType name="de.foursoft.harness.somepackage.VecComponentPort"/>
</jxb:property>
</jxb:bindings>
</jxb:bindings>
Um die Wartbarkeit dieses Ansatzes zu verbessern, wird die benutzerdefinierte JAXB-Bindung nicht manuell erstellt. Wie eingangs erwähnt, ist die XML-Schemadarstellung nicht so prägnant wie das UML-Modell. In unserem Fall ist das zum Glück anders, wird das XML-Schema glücklicherweise aus einem UML-Modell generiert, das alle von uns benötigten Informationen enthält. Daher verwenden wir das UML-Modell auch zur Generierung der JAXB-Bindung über einige leistungsstarke XSLT-Skripte. Durch diesen Ansatz ist gewährleistet, dass die JAXB-Bindung immer konsistent mit dem XML-Schema ist und keine Elemente fehlen. Als Nebeneffekt sparen wir die Zeit, die für die manuelle Erstellung und Anpassung erforderlich ist.
Modellinitialisierung
Mit dem ersten Schritt haben wir nun JAXB-Klassen, die unseren Anforderungen entsprechen. Allerdings müssen wir sie noch korrekt initialisieren, da unsere Erweiterungen benutzerdefiniert sind und JAXB sie ignorieren wird. Für die Initialisierung unserer Erweiterungen, verwenden wir die JAXB-Funktionalität der Schnittstelle “Unmarshaller.Listener”. Eine Implementierung dieser Schnittstelle wird über jedes unmarshallte Objekt und seinen entsprechenden Elternteil im XML-Baum benachrichtigt.
Der Vorteil dieses Mechanismus ist, dass wir kein eigenes Model Traversal implementieren müssen und es ist sichergestellt, dass wir über jede einzelne Instanz benachrichtigt werden, unabhängig vom umgebenden Kontext. Dennoch gibt es zwei Themen mit denen wir uns beschäftigen müssen:
- Der JAXB
Unmarshaller
ist nur in der Lage, einen einzigenListener
zu behandeln. Aus Design-Gründen haben wir uns entschieden, eine spezifische Listener-Instanz proKlasse
zu haben. Daher wird dieListener
-Schnittstelle von einemModelPostProcessorManager
implementiert, der die Benachrichtigungen an die verschiedenenModelPostProcessor
-Instanzen delegiert. - Die Initialisierung erfordert zwei Phasen:
- Die erste Phase wird von JAXB gesteuert und wir verwenden die Listener.afterUnmarshall()-Ereignisse, um alle relevanten Objekte für die spätere Initialisierung zu sammeln.
Die eigentliche Initialisierung kann zu diesem Zeitpunkt nicht erfolgen, da JAXB die IDREF-Assoziationen in diesem Stadium noch nicht initialisiert hat. - Sobald der Unmarshalling-Prozess von JAXB abgeschlossen ist, beginnen wir die zweite Phase der eigentlichen Initialisierung. Dies wird erreicht, indem der
Unmarshaller
mit demExtendedUnmarshaller
umhüllt wird. DerExtendedUnmarshaller
ist für die korrekte Konfiguration desUnmarshallers
verantwortlich und löst die zweite Phase durch den Aufruf desModelPostProcessorManager.doPostProcessing()
aus.
- Die erste Phase wird von JAXB gesteuert und wir verwenden die Listener.afterUnmarshall()-Ereignisse, um alle relevanten Objekte für die spätere Initialisierung zu sammeln.
Die Initialisierung der Assoziationen erfolgt durch einen ReflectiveAssociationPostProcessor
. Diese Implementierung verwendet Reflection, um die notwendigen Informationen zu sammeln und die Assoziationen entsprechend zu initialisieren. Um sie mit allen
mit allen erforderlichen Informationen zu versorgen, wurden zwei Annotationen eingeführt, die vom benutzerdefinierten XJC-Plugin hinzugefügt werden, um diese Informationen zu speichern: @XmlParent
und @XmlBackreference
.
@XmlParent" wird verwendet, um ein Feld in einer Klasse für die Initialisierung mit dem entsprechenden Elternobjekt zu markieren. @XmlBackreference
markiert eine IDREF
-Assoziation als bidirektional und definiert den Namen der umgekehrten Navigationseigenschaft in der Zielklasse.
Kommentierte VecConnectionEnd.java
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ConnectionEnd", propOrder = {
"identification",
"connectedComponentPort"
})
public class VecConnectionEnd
extends VecConfigurableElement
implements Serializable
{
@XmlElement(name = "Identification", required = true)
protected String identification;
@XmlElement(name = "ConnectedComponentPort", required = true, type = Object.class)
@XmlIDREF
@XmlSchemaType(name = "IDREF")
@XmlBackReference(destinationField = "refConnectionEnd")
protected VecComponentPort connectedComponentPort;
@XmlTransient
@XmlParent
private VecConnection parentConnection;
// Rest of the class is omitted.
....
}
Weitere Gedanken zu diesem Thema
Ich denke, die skizzierte Lösung spricht für sich selbst, aber bedenken Sie, dass dies nur eine kurze Zusammenfassung ist. Insbesondere die konkrete Implementierung der Modellinitialisierung sollte mit besonderer Vorsicht durchgeführt werden, da sonst sonst gibt es zahlreiche Möglichkeiten, neue Leistungslöcher zu schaffen.
Bei korrekter Implementierung (ohne übermäßige Leistungsoptimierungen) fügt diese Lösung dem reinen JAXB-Unmarshalling etwa 10 % zusätzlichen Overhead Zeit hinzu. Dafür erhalten Sie eine komfortable, intuitive Navigation zwischen JAXB-Objekten bei gleichbleibender Laufzeitkomplexität.
Das ist aber noch nicht das Ende der Fahnenstange! Was passiert, wenn Sie im Modell navigieren wollen, z.B. um Objekten nach ID
oder nach bestimmten Kriterien wie “alle Objekte einer bestimmten Klasse finden” suchen? Im Moment überlasse ich dies Ihrer eigenen Kreativität.