Zuordnung von XML zu Java, Teil 1

XML ist heiß. Da XML eine Form selbstbeschreibender Daten ist, können damit umfangreiche Datenmodelle codiert werden. Es ist leicht zu erkennen, dass XML als Datenaustauschmedium zwischen sehr unterschiedlichen Systemen dient. Daten können auf allen Arten von Systemen einfach als XML verfügbar gemacht oder veröffentlicht werden: ältere COBOL-Programme, Datenbanken, C ++ - Programme usw.

TEXTFELD:

TEXTBOX_HEAD: XML auf Java abbilden: Lesen Sie die ganze Serie!

  • Teil 1 - Verwenden Sie die SAX-API, um XML-Dokumente Java-Objekten zuzuordnen
  • Teil 2 - Erstellen Sie eine Klassenbibliothek, die die SAX-API verwendet, um XML-Dokumente Java-Objekten zuzuordnen

: END_TEXTBOX

Die Verwendung von XML zum Erstellen von Systemen ist jedoch mit zwei Herausforderungen verbunden. Erstens ist das Generieren von XML zwar eine unkomplizierte Prozedur, die inverse Operation unter Verwendung von XML-Daten aus einem Programm jedoch nicht. Zweitens können aktuelle XML-Technologien leicht falsch angewendet werden, was dazu führen kann, dass ein Programmierer ein langsames, speicherhungriges System hat. In der Tat können sich hohe Speicheranforderungen und langsame Geschwindigkeiten für Systeme als problematisch erweisen, die XML als primäres Datenaustauschformat verwenden.

Einige derzeit verfügbare Standardtools für die Arbeit mit XML sind besser als andere. Insbesondere die SAX-API verfügt über einige wichtige Laufzeitfunktionen für leistungsabhängigen Code. In diesem Artikel werden einige Muster für die Anwendung der SAX-API entwickelt. Sie können schnellen XML-zu-Java-Zuordnungscode mit minimalem Speicherbedarf erstellen, selbst für relativ komplexe XML-Strukturen (mit Ausnahme von rekursiven Strukturen).

In Teil 2 dieser Reihe werden wir die Anwendung der SAX-API auf rekursive XML-Strukturen behandeln, in denen einige der XML-Elemente Listen von Listen darstellen. Wir werden auch eine Klassenbibliothek entwickeln, die die Navigationsaspekte der SAX-API verwaltet. Diese Bibliothek vereinfacht das Schreiben von XML-Mapping-Code basierend auf SAX.

Der Zuordnungscode ähnelt dem Kompilieren von Code

Das Schreiben von Programmen, die XML-Daten verwenden, ist wie das Schreiben eines Compilers. Das heißt, die meisten Compiler konvertieren den Quellcode in drei Schritten in ein ausführbares Programm. Zunächst gruppiert ein Lexer- Modul Zeichen in Wörter oder Token, die der Compiler erkennt - ein Prozess, der als Tokenisierung bezeichnet wird. Ein zweites Modul namens Parser analysiert Gruppen von Token, um Konstrukte der Rechtssprache zu erkennen. Zuletzt nimmt ein drittes Modul, der Codegenerator, eine Reihe von Konstrukten der Rechtssprache und generiert ausführbaren Code. Manchmal vermischen sich Parsing und Codegenerierung.

Um XML-Daten in einem Java-Programm zu verwenden, müssen wir einen ähnlichen Prozess durchlaufen. Zunächst analysieren wir jedes Zeichen im XML-Text, um zulässige XML-Token wie Start-Tags, Attribute, End-Tags und CDATA-Abschnitte zu erkennen.

Zweitens überprüfen wir, ob die Token legale XML-Konstrukte bilden. Wenn ein XML-Dokument vollständig aus rechtlichen Konstrukten gemäß der XML 1.0-Spezifikation besteht, ist es wohlgeformt. Auf der einfachsten Ebene müssen wir sicherstellen, dass beispielsweise alle Tags übereinstimmende öffnende und schließende Tags haben und die Attribute im öffnenden Tag richtig strukturiert sind.

Wenn eine DTD verfügbar ist, können wir auch sicherstellen, dass die beim Parsen gefundenen XML-Konstrukte in Bezug auf die DTD legal sind und gut geformtes XML darstellen.

Schließlich verwenden wir die im XML-Dokument enthaltenen Daten, um etwas Nützliches zu erreichen - ich nenne dieses Mapping-XML Java.

XML-Parser

Glücklicherweise gibt es Standardkomponenten - XML-Parser -, die einige dieser compilerbezogenen Aufgaben für uns ausführen. XML-Parser übernehmen für uns alle lexikalischen Analyse- und Parsing-Aufgaben. Viele derzeit verfügbare Java-basierte XML-Parser unterstützen zwei beliebte Parsing-Standards: die SAX- und die DOM-API.

Die Verfügbarkeit eines handelsüblichen XML-Parsers lässt den Eindruck entstehen, dass der schwierige Teil der Verwendung von XML in Java für Sie erledigt wurde. In der Realität ist das Anwenden eines handelsüblichen XML-Parsers eine komplizierte Aufgabe.

SAX- und DOM-APIs

Die SAX-API ist ereignisbasiert. XML-Parser, die die SAX-API implementieren, generieren Ereignisse, die verschiedenen Funktionen im analysierten XML-Dokument entsprechen. Indem Sie auf diesen Strom von SAX-Ereignissen in Java-Code reagieren, können Sie Programme schreiben, die auf XML-basierten Daten basieren.

Die DOM-API ist eine objektmodellbasierte API. XML-Parser, die DOM implementieren, erstellen ein generisches Objektmodell im Speicher, das den Inhalt des XML-Dokuments darstellt. Sobald der XML-Parser die Analyse abgeschlossen hat, enthält der Speicher einen Baum von DOM-Objekten, der Informationen sowohl zur Struktur als auch zum Inhalt des XML-Dokuments enthält.

Das DOM-Konzept ist aus der HTML-Browserwelt hervorgegangen, in der ein gemeinsames Dokumentobjektmodell das in den Browser geladene HTML-Dokument darstellt. Dieses HTML-DOM wird dann für Skriptsprachen wie JavaScript verfügbar. HTML DOM war in dieser Anwendung sehr erfolgreich.

Gefahren von DOM

Auf den ersten Blick scheint die DOM-API funktionsreicher und daher besser zu sein als die SAX-API. DOM weist jedoch schwerwiegende Effizienzprobleme auf, die leistungsempfindliche Anwendungen beeinträchtigen können.

Die aktuelle Gruppe von XML-Parsern, die DOM unterstützen, implementiert das speicherinterne Objektmodell, indem viele kleine Objekte erstellt werden, die DOM-Knoten darstellen, die entweder Text oder andere DOM-Knoten enthalten. Dies klingt natürlich genug, hat jedoch negative Auswirkungen auf die Leistung. Eine der teuersten Operationen in Java ist der newOperator. Entsprechend newmuss der JVM-Garbage Collector für jeden in Java ausgeführten Operator das Objekt eventuell aus dem Speicher entfernen, wenn keine Verweise auf das Objekt mehr vorhanden sind. Die DOM-API neigt dazu, das JVM-Speichersystem mit seinen vielen kleinen Objekten, die normalerweise kurz nach dem Parsen beiseite geworfen werden, wirklich zu verprügeln.

Ein weiteres DOM-Problem ist die Tatsache, dass das gesamte XML-Dokument in den Speicher geladen wird. Bei großen Dokumenten wird dies zu einem Problem. Da das DOM so viele winzige Objekte implementiert, ist der Speicherbedarf sogar noch größer als das XML-Dokument selbst, da die JVM einige zusätzliche Informationsbytes zu all diesen Objekten sowie zum Inhalt des XML-Dokuments speichert.

Es ist auch beunruhigend, dass viele Java-Programme die generische Objektstruktur von DOM nicht verwenden. Sobald die DOM-Struktur in den Speicher geladen wird, kopieren sie die Daten in ein Objektmodell, das für eine bestimmte Problemdomäne spezifisch ist - ein subtiler, aber verschwenderischer Prozess.

Ein weiteres subtiles Problem für die DOM-API besteht darin, dass der dafür geschriebene Code das XML-Dokument zweimal scannen muss. Der erste Durchgang erstellt die DOM-Struktur im Speicher, der zweite lokalisiert alle XML-Daten, an denen das Programm interessiert ist. Bestimmte Codierungsstile können die DOM-Struktur mehrere Male durchlaufen, während verschiedene XML-Daten lokalisiert werden. Im Gegensatz dazu unterstützt der Codierungsstil von SAX das Auffinden und Sammeln von XML-Daten in einem einzigen Durchgang.

Einige dieser Probleme könnten mit einem besseren zugrunde liegenden Datenstrukturdesign behoben werden, um das DOM-Objektmodell intern darzustellen. Probleme wie das Ermutigen mehrerer Verarbeitungsdurchläufe und das Übersetzen zwischen generischen und spezifischen Objektmodellen können in den XML-Parsern nicht behandelt werden.

SAX fürs Überleben

Compared to the DOM API, the SAX API is an attractive approach. SAX doesn't have a generic object model, so it doesn't have the memory or performance problems associated with abusing the new operator. And with SAX, there is no generic object model to ignore if you plan to use a specific problem-domain object model instead. Moreover, since SAX processes the XML document in a single pass, it requires much less processing time.

SAX does have a few drawbacks, but they are mostly related to the programmer, not the runtime performance of the API. Let's look at a few.

The first drawback is conceptual. Programmers are accustomed to navigating to get data; to find a file on a file server, you navigate by changing directories. Similarly, to get data from a database, you write an SQL query for the data you need. With SAX, this model is inverted. That is, you set up code that listens to the list of every available piece of XML data available. That code activates only when interesting XML data are being listed. At first, the SAX API seems odd, but after a while, thinking in this inverted way becomes second nature.

The second drawback is more dangerous. With SAX code, the naive "let's take a hack at it" approach will backfire fairly quickly, because the SAX parser exhaustively navigates the XML structure while simultaneously supplying the data stored in the XML document. Most people focus on the data-mapping aspect and neglect the navigational aspect. If you don't directly address the navigational aspect of SAX parsing, the code that keeps track of the location within the XML structure during SAX parsing will become spread out and have many subtle interactions. This problem is similar to those associated with overdependence on global variables. But if you learn to properly structure SAX code to keep it from becoming unwieldy, it is more straightforward than using the DOM API.

Basic SAX

There are currently two published versions of the SAX API. We'll use version 2 (see Resources) for our examples. Version 2 uses different class and method names than version 1, but the structure of the code is the same.

SAX is an API, not a parser, so this code is generic across XML parsers. To get the examples to run, you will need to access an XML parser that supports SAX v2. I use Apache's Xerces parser. (See Resources.) Review your parser's getting-started guide for specifics on invoking a SAX parser.

The SAX API specification is pretty straightforward. In includes many details, but its primary task is to create a class that implements the ContentHandler interface, a callback interface used by XML parsers to notify your program of SAX events as they are found in the XML document.

The SAX API also conveniently supplies a DefaultHandler implementation class for the ContentHandler interface.

Once you've implemented the ContentHandler or extended the DefaultHandler, you need only direct the XML parser to parse a particular document.

Our first example extends the DefaultHandler to print each SAX event to the console. This will give you a feel for what SAX events will be generated and in what order.

To get started, here's the sample XML document we will use in our first example:

   Bob   New York   

Next, we see the source code for XML mapping code of the first example:

import org.xml.sax.*; import org.xml.sax.helpers.*; import java.io.*; public class Example1 extends DefaultHandler { // Override methods of the DefaultHandler class // to gain notification of SAX Events. // // See org.xml.sax.ContentHandler for all available events. // public void startDocument( ) throws SAXException { System.out.println( "SAX Event: START DOCUMENT" ); } public void endDocument( ) throws SAXException { System.out.println( "SAX Event: END DOCUMENT" ); } public void startElement( String namespaceURI, String localName, String qName, Attributes attr ) throws SAXException { System.out.println( "SAX Event: START ELEMENT[ " + localName + " ]" ); // Also, let's print the attributes if // there are any... for ( int i = 0; i < attr.getLength(); i++ ){ System.out.println( " ATTRIBUTE: " + attr.getLocalName(i) + " VALUE: " + attr.getValue(i) ); } } public void endElement( String namespaceURI, String localName, String qName ) throws SAXException { System.out.println( "SAX Event: END ELEMENT[ " + localName + " ]" ); } public void characters( char[] ch, int start, int length ) throws SAXException { System.out.print( "SAX Event: CHARACTERS[ " ); try { OutputStreamWriter outw = new OutputStreamWriter(System.out); outw.write( ch, start,length ); outw.flush(); } catch (Exception e) { e.printStackTrace(); } System.out.println( " ]" ); } public static void main( String[] argv ){ System.out.println( "Example1 SAX Events:" ); try { // Create SAX 2 parser... XMLReader xr = XMLReaderFactory.createXMLReader(); // Set the ContentHandler... xr.setContentHandler( new Example1() ); // Parse the file... xr.parse( new InputSource( new FileReader( "Example1.xml" )) ); }catch ( Exception e ) { e.printStackTrace(); } } } 

Finally, here is the output generated by running the first example with our sample XML document:

Example1 SAX Events: SAX Event: START DOCUMENT SAX Event: START ELEMENT[ simple ] ATTRIBUTE: date VALUE: 7/7/2000 SAX Event: CHARACTERS[ ] SAX Event: START ELEMENT[ name ] SAX Event: CHARACTERS[ Bob ] SAX Event: END ELEMENT[ name ] SAX Event: CHARACTERS[ ] SAX Event: START ELEMENT[ location ] SAX Event: CHARACTERS[ New York ] SAX Event: END ELEMENT[ location ] SAX Event: CHARACTERS[ ] SAX Event: END ELEMENT[ simple ] SAX Event: END DOCUMENT 

As you can see, the SAX parser will call the appropriate ContentHandler method for every SAX event it discovers in the XML document.

Hello world

Nachdem wir das Grundmuster von SAX verstanden haben, können wir etwas Nützliches tun: Werte aus unserem einfachen XML-Dokument extrahieren und das klassische Hallo-Welt-Programm demonstrieren.