StAX
Frissítve: 2017. november 19.
Technológiák: JAXP 1.4, StAX 1.0
Ha már a webszolgáltatásokról volt szó, mely szorosan kapcsolódik az XML-hez, nézzük, hogy mi újítást hozott a JDK 6-ban bevezetett JAXP 1.4. A legnagyobb újdonság a StAX bevezetése, mely egy nagy teljesítményű stream alapú XML feldolgozó szűréssel (filter), mely támogatja a módosítást is.
A JAXP-ben már a W3C által definiált SAX és DOM feldolgozási modell régóta létezett. A SAX esetében esemény alapú feldolgozást lehet megvalósítani, ahol a feldolgozó (parser) hívja az osztályainkat (parser client). Ezt hívják ún. push parsernek, mikor a parser megy végig az XML-elen, és tolja nekünk az adatokat. Ezt viszonylag nehéz programozni, mert tárolni kell az állapotot, manuálisan kell pl. vermet kezelni, hogy tároljuk, hol tartunk a feldolgozásban. A DOM a teljes XML-t betölti egy faként a memóriába, melyen lehet fabejárást is végezni, sőt tetszőlegesen módosítható is. Ez könnyen használható, a fa adatszerkezetet is a legtöbben ismerik, viszont rengeteg memóriára van szüksége. Ennek feloldására a BEA kezdett el kidolgozni egy API-t, mely a JSR 173 specifikációban lett megfogalmazva, Streaming API for XML címmel.
Célja, hogy egyszerűbben programozható legyen, mint a SAX, de kevesebb memóriát fogyasszon, mint a DOM. Streaming API-nak nevezik, mert a parser XML dokumentumnak csak egy részét látja egyszerre. Itt jön képbe a consume (felemészt) fogalom is, ami azt jelenti, hogy a streamen nem haladhatunk visszafele, így ha egyszer átment rajta a parser, akkor a stream beolvasásra került, tehát ha újra fel akarjuk dolgozni, valamilyen módon biztosítani kell a stream újra rendelkezésre állását (pl. klónozás). Ezen kívül a parsert pull parsernek nevezik, mert a kliens irányítja, ő kéri el az adatokat. Ennek több előnye is van, pl. egyszerre több dokumentumot is lehet párhuzamosan feldolgozni, akár összefésülni az eredményeket, valamint a szálat teljes kontroll alatt lehet tartani, ugyanis akkor szüneteltetjük a végrehajtást, mikor akarjuk. A library is kisebb lesz, és a kliens kód is kisebb lesz.
A StAX további előnye, hogy nem csak olvasni (pl. SAX) képes a
dokumentumokat, hanem írni is képes azokat. A StAX alapvetően két
API-val is rendelkezik, az egyik a cursor API, a másik az iterator API.
E mögötti meggondolás az, hogy inkább két egyszerű API-t készítenek,
mint egy bonyolultat. Az előbbihez az XMLStreamReader
, és
XMLStreamWriter
osztályok tartoznak, míg a másodikhoz az XMLEventReader
és XMLEventWriter
osztályok.
A példa projekt letölthető a GitHubról. Nézzük is a cursor API használatával hogyan dolgoznánk fel a következő XML fájlt:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book isbn10="059610149X">
<title>Java and XML</title>
<author>Brett McLaughlin, Justin Edelson</author>
<publisher>O'Reilly Media</publisher>
<year>2006</year>
</book>
<book isbn10="1590597060">
<title>Pro XML Development with Java Technology</title>
<author>Ajay Vohra</author>
<publisher>Apress</publisher>
<year>2005</year>
</book>
<book isbn10="1449365116">
<title>Java Web Services: Up and Running</title>
<author>Martin Kalin</author>
<publisher>O'Reilly Media</publisher>
<year>2013</year>
</book>
</catalog>
A kód, mely ezt feldolgozza cursor API használatával:
List<Book> catalog = new ArrayList<>();
XMLInputFactory f = XMLInputFactory.newInstance();
XMLStreamReader r = f.createXMLStreamReader(source);
Book book = null;
while (r.hasNext()) {
if (r.getEventType() == XMLStreamConstants.START_ELEMENT) {
if ("book".equals(r.getName().getLocalPart())) {
book = new Book();
catalog.add(book);
book.setIsbn10(r.getAttributeValue(null, "isbn10"));
}
else if ("title".equals(r.getName().getLocalPart())) {
book.setTitle(r.getElementText());
}
}
r.next();
}
return catalog;
Látható, hogy először gyártatunk egy XMLInputFactory
példányt, majd
egy XMLStreamReader
példányt. Amíg tart a feldolgozás, addig
feldolgozzuk az adatokat, de mindig mi lépünk a következő adatra a
next()
metódus segítségével (pull API). Az adat típusa, melyen a
reader éppen áll, az XML-nél megszokottak lehetnek: XML deklaráció,
nyitó tag, záró tag, karakterek, megjegyzések, stb. Attól függően, hogy
mi, hívhatjuk meg a reader többi metódusát. Pl. nyitó tag esetén
lekérdezhető a getName
metódussal a neve, vagy getText
metódussal a
tartalma, sőt a getAttribute*
kezdetű metódusokkal az attribútumai is.
A XMLStreamException
kivételt kezelni kell.
Nézzük, hogyan írjunk ki egy hasonló XML dokumentumot:
StringWriter sw = new StringWriter();
XMLOutputFactory output = XMLOutputFactory.newInstance();
XMLStreamWriter writer = output.createXMLStreamWriter(sw);
writer.writeStartDocument();
writer.writeStartElement("catalog");
for (Book book: catalog) {
writer.writeStartElement("book");
writer.writeAttribute("isbn10", book.getIsbn10());
writer.writeStartElement("title");
writer.writeCharacters(book.getTitle());
writer.writeEndElement();
writer.writeEndElement();
}
writer.writeEndElement();
writer.flush();
return sw.toString();
Nézzük az XML feldolgozást most az iterator API segítségével:
List<Book> catalog = new ArrayList<>();
XMLInputFactory f = XMLInputFactory.newInstance();
XMLEventReader r = f.createXMLEventReader(source);
Book book = null;
while (r.hasNext()) {
XMLEvent event = r.nextEvent();
if (event.getEventType() == XMLStreamConstants.START_ELEMENT) {
if (event instanceof StartElement) {
StartElement element = (StartElement) event;
if ("book".equals(element.getName().getLocalPart())) {
book = new Book();
catalog.add(book);
book.setIsbn10(element.getAttributeByName(new QName("isbn10")).getValue());
}
else if ("title".equals(element.getName().getLocalPart())) {
book.setTitle(r.getElementText());
}
}
}
}
return catalog;
Itt látható, hogy nem közvetlenül a reader
példányt szólítjuk meg,
hanem az event
példányt. Ezt aztán a megfelelő típusra kényszeríteni
kell, és már el is érhetjük a megfelelő metódusait.
Ahhoz, hogy eldöntsük, hogy a kettő közül melyiket érdemes választani, érdemes a következőket a fejben tartani:
- Az iterator API
XMLEvent
osztályai nem módosíthatóak, a parse-olás után is megőrzik az értéküket - Emiatt többlépéses (bővíthető, plug-inelhető) feldolgozásokat is könnyebben ki lehet dolgozni ezen példányok továbbadásával
- A fentiekből következik, hogy a cursor API kevesebb memóriát használ, és a példányosítások hiánya miatt gyorsabb is
- Az
XMLEvent
interfészt akár magunk is implementálhatjuk, akár teljesen új eseményt hozhatunk létre, akár egy meglévőt egészíthetünk ki utility metódusokkal - Az iterator API esetén módosítani is lehet a streamet
Összességében, ha nem a teljesítmény az elsődleges szempont, érdemes a magasabb szintű iterator API-t használni.