2 czerwca 2007

Serializacja do XML i deserializacja przy użyciu XStream

Po długiej i wyczerpującej, ale bardzo pouczającej fazie czytania, eksperymentowania i rozmyślania przystąpiłem wreszcie do pisania… pisania swojej pracy magisterskiej. Częścią tej że jest implementacja aplikacji WWW. Po sporządzeniu ogólnego projektu przystąpiłem do realizacji metodą z góry na duł (ang. top-down) tj. począwszy od interfejsu użytkownika stopniowo przesuwając się w stronę serwisów warstwy logiki i w końcu serwisów DAO (akr. Data Access Object). I tu pojawił się problem, którego rozwiązanie leży u podstaw tego artykułu, tj. pisanie tymczasowych klas zaślepek (ang. stub) serwisów pobierających dane. Zacząłem od wymarzenia sobie biblioteki, która wczytywałaby drzewo obiektów z pliku XML. Uznałem, że tak będzie najłatwiej. Chwile potem miałem już namierzone rozwiązanie, bibliotekę XStream, http://xstream.codehaus.org/. Załóżmy, że chcemy zbudować, docelowo poprzez pobranie z bazy danych, ale póki co najłatwiej jak się da, strukturę obiektów modelujących katalog produktów podzielonych na kategorie. W tym celu użyjemy klas:

public class ProductCategory {
private String categoryName;
private ProductCategory parentCategory;
private List<ProductCategory> childCategories;
private List<Product> products;

// metody pomijam
}

public class Product {
private String productName;
private String productDescr;

// metody pomijam
}

Programowe tworzenie wystarczających rozmiarów struktury tego typu byłoby nudne i męczące, łatwiej będzie napisać odpowiedni plik XML. W naszym przykładzie może on wyglądać tak:

<linked-list>
<pl.mariuszlipinski.ProductCategory id="1">
<categoryName>Komputery</categoryName>
<childCategories>
<pl.mariuszlipinski.ProductCategory>
<categoryName>Laptopy</categoryName>
<parentCategory reference="1" />
<products>
<pl.mariuszlipinski.Product>
<productName>ThinkPad T41</productName>
<productDescr>Fajna rzecz</productDescr>
</pl.mariuszlipinski.Product>
<pl.mariuszlipinski.Product>
<productName>Compaq nc6400</productName>
<productDescr>Dobra rzecz</productDescr>
</pl.mariuszlipinski.Product>
</products>
</pl.mariuszlipinski.ProductCategory>
</childCategories>
</pl.mariuszlipinski.ProductCategory>
</linked-list>

W jaki sposób XML ten odpowiada klasom widać gołym okiem, dopasowanie odbywa się po nazwach. Możliwe jest również definiowanie aliasów i to nawet za pomocą adnotacji, ale nie jest to szczególnie istotne. No więc jak ten plik XML wczytać? Jak zwykle trzeba zacząć od pobrania biblioteki ze strony projektu, ze strony http://xstream.codehaus.org/download.html. Najłatwiej będzie pobrać dystrybucję binarną, tj. jeden plik .zip zawierający także zależności, czyli parser XML. Sam XStream to plik xstream-(…).jar, rekomendowany parser Xpp3 to plik xpp3-(…).jar. To co teraz należy wykonać to deserializacja XML. Oto kod który realizuje to zadanie:

XStream xStream = new XStream();
xStream.setMode(XStream.ID_REFERENCES);

List<ProductCategory> catalog = xStream.fromXML(new FileReader("catalog.xml"));

To, co zasługuje w powyższym przykładzie to szczególną uwagę to referencje. Zwróćmy uwagę na fragment naszego XML’a:

<pl.mariuszlipinski.ProductCategory id="1">
<childCategories>
<pl.mariuszlipinski.ProductCategory>
<parentCategory reference="1" />

Wczytanie elementu <pl.mariuszlipinski.ProductCategory id="1"> spowoduje utworzenie obiektu klasy ProductCategory. Element childCategories spowoduje utworzenie i przypisanie do zmiennej tego obiektu o tej że nazwie obiektu implementującego interfejs List. Kolejny element <pl.mariuszlipinski.ProductCategory> spowoduje utworzenie nowego obiektu klasy ProductCategory i dodanie go do wspomnianej listy. I teraz uwaga. Element <parentCategory reference="1" /> spowoduje przypisanie do zmiennej parentCategory obiektu z atrybutem @id=”1”, a więc tego utworzonego w wyniku parsowania elementu <pl.mariuszlipinski.ProductCategory id="1">. XStream wspiera kilka różnych sposobów wyrażania referencji, i właśnie po to by użył tego opisanego powyżej wywołujemy metodę:

xStream.setMode(XStream.ID_REFERENCES);

Kolejnym ciekawym zastosowaniem dla deserializacji XML jest wczytywanie zaawansowanej konfiguracji. Po co ograniczać się do prostej mapy wartości możliwej do uzyskania przy użyciu plików .properties.

Jakiś czas temu, w artykule Implementacja metody toString() z użyciem Reflection API zaproponowałem uniwersalny kod implementujący metodę toString(). Używałem tej że do testowania serwisów DAO poprzez wypisanie odczytanych obiektów. W komentarzu do tamtego artykułu Michał zaproponował lepsze rozwiązanie, z użyciem klasy ReflectionToStringBuilder. Teraz znowu moja propozycja, serializacja do XML. Aby zserializować kompletne drzewo obiektów wystarczy poniższy kod:

XStream xStream = new XStream();

String xml = xStream.toXML(products);

6 komentarzy:

mikeos pisze...

XStream jest fajnym narzędziem, też pierwszy raz użyłem go w pracy mgr. Ale oprócz tego co piszesz ma jeszcze kilka dość przydatnych funkcji o których możnaby wspomnieć, takich jak np pisanie własnych konwerterów/serializatorów w oparciu o całkiem sensowne interfejsy, tworzenie aliasów tagów itp.

Pzdr
mikeos

Mariusz Lipiński pisze...

Dzięki za uzupełnienie. Naturalnie XStream ma sporo różnych możliwości i zastosowań o których nie wspomniałem, aczkolwiek nie było moją intencją kompleksowe omówienie narzędzia. Tak czy inaczej, ktoś kto zainteresuje się nim poważnie i tak zajrzy na stronę projektu, a tam wszystko jest opisane.

syllepsa pisze...

Jesli moge dorzucic swoje trzy grosze, to do tworzenia atrap uzywam Springa. Nastepnie wstrzykuje (w faces-config.xml) pobrane dane do managed beanow. Patrzac na kod xml XStreama wydaje mi sie, ze definicje beanow w Springu sa bardziej intuicyjne. Oczywiscie jest to tylko moje subiektywne odczucie.

Pozdrawiam

Mariusz

Mariusz Lipiński pisze...

Ciekawa propozycja, musisz to opisać na swoim blogu! Ostatnio jakoś mało się tam działo :)

syllepsa pisze...

Ostatnio mam na glowie pisanie pracy magisterskiej. Jak juz bede miec to za soba, to opisze integracje JSF ze Springiem.

Pozdrawiam
Mariusz

Anonimowy pisze...

Chłopie dół, a nie duł!