JAVA exPress > Archiwum > Numer 4 (2009-06-01) > Obsługa XML w Javie. Biblioteka XStream

Obsługa XML w Javie. Biblioteka XStream

Format XML

XML (Extensible Markup Language, w wolnym tłumaczeniu Rozszerzalny Język Znaczników) to uniwersalny język formalny przeznaczony do reprezentowania różnych danych w ustrukturalizowany sposób. XML jest niezależny od platformy, co umożliwia łatwą wymianę dokumentów pomiędzy różnymi systemami i znacząco przyczyniło się do popularności tego języka. XML wyrósł na niekwestionowany standard wymiany danych w sieci WWW. Język i dokumenty XHTML pozwalają skutecznie opisywać zawartość stron WWW; tymczasem XML pozwala na opisywanie dowolnych danych, byle miały jakąś zdefiniowaną strukturę. Do tego zamiast udostępniać ograniczony zestaw znaczników do oznaczenia elementów dokumentu, XML pozwala na definiowanie własnych słowników, dla każdej dziedziny z osobna. Dokładny opis standardu XML nie jest celem tego artykułu. Chętnych do zapoznania się z specyfikacją standardu odsyłam do literatury i sieci.

Przykłady zastosowania

W praktyce dokumenty XML są bardzo często stosowane i mają szeroki zakres zastosowań. Jako programiście często zdarza mi się otrzymać plik XML z danymi, które trzeba zapisać w bazie. Mogą to być wszelkiego rodzaju informacje o ludziach, książkach, instytucjach itp.. Dokument trzeba umieć sparsować i otrzymane obiekty zapisać w bazie. Dane zawarte w dokumencie XML mogą być przekształcone na dokument HTML, PDF , czy dowolny inny format tekstowy przy użyciu mechanizmu XSLT. Warto więc umieć tworzyć dokumenty XML z posiadanych danych, chociażby do wygenerowania pliku PDF. XML jest wykorzystywany do definiowania plików konfiguracyjnych – z zastosowaniem tym spotkało się na pewno wielu programistów JAVA.

Obsługa XML w Javie

Popularne mechanizmy

Myślę, że dwa najpopularniejsze API do obsługi XML z poziomu Javy to SAX i DOM. Służą one do analizy składniowej dokumentów. Pierwszy przetwarza dokument i za każdym razem, gdy napotka jakiś znacznik, komentarz, fragment tekstu, lub jakiś inny element XML odwołuje się do kodu programu w celu zasygnalizowania zdarzenia. Wtedy nasz kod może wykonać odpowiednie zadanie. Używając tego API mamy dostęp tylko do aktualnie przetwarzanego elementu. Interface SAX jest szczególnie przydatny przy analizowaniu dużych plików. DOM po sparsowaniu pliku XML udostępnia w pamięci kompletną reprezentację dokumentu w postaci drzewa. Mimo że opisywane API nie są tematem artykułu, to zachęcam do zapoznania się z nimi.

Alternatywne podejścia

Odmiennym podejściem jest mapowanie (wiązanie danych). Zamiast pracować z elementami i atrybutami, posługujemy się obiektami Javy co znacznie ułatwia pracę z XML. Podejście powinno wydać się znajome czytelnikom, którzy spotkali się z Hibernatem (wiążącym tabele z bazy danych z obiektami Javy).

Częściowo, aby zaznaczyć różnicę w wiązaniu danych zastosowano nieco odmienną terminologię – zamiast terminów "analiza składniowa" (parsing) czy "serializacja" (serializing) stosuje się termin szeregowanie (marshaling) - konwersja obiektów XML na obiekty Javy. Proces odwrotny to rozszeregowywanie (unmarshaling) tj. zamiana klas Javy na obiekty XML. Narzędziem opartym na opisanym podejściu jest biblioteka XStream, którą postaram się przybliżyć w niniejszym artykule. Biblioteka wykorzystuje mechanizm refleksji do w celu zidentyfikowania pól, które mają być zapisane. Jest bardzo prosta (nie wymaga określania XML Schema) i znacznie ułatwia obsługę XML z poziomu Javy. Więcej informacji o bibliotece można znaleźć na stronie http://xstream.codehaus.org/.

Użycie biblioteki XStream

Dodanie biblioteki do projektu

Jeśli chcemy korzystać z XStream, musimy dodać ją do swojego projektu. W tym celu należy pobrać ze strony projektu najnowszego jara zawierającego bibliotekę – w czasie pisania artykułu była to wersja 1.3.1. Jeśli nie będziemy rozwijać XStreama, a tylko z niego korzystać radzę pobrać wersję binarną. Pobrany plik należy rozpakować - interesujące nas archiwum znajduje się w podkatalogu lib (plik xstream-1.3.1.jar). Następnie należy dodać ścieżkę do pobranego jara do Java Build Path naszego projektu. Jeśli ktoś, podobnie jak ja używa Mavena do tworzenia aplikacji, powinien dodać do pliku pom.xml następującą zależność :

 
            <dependency>
                <groupId>com.thoughtworks.xstream</groupId>
                <artifactId>xstream</artifactId>
                <version>1.3.1</version>
            </dependency>
 

Biblioteka XStream obsługuje adnotacje i w związku z tym do korzystania z niej potrzebna jest Java 1.5 lub nowsza.

Pierwsze użycie

W celu wykonania pierwszej prostej konwersji stwórzmy w Javie Beana:

 
            public class Person  {
                private String name;
                private String surname;
                private Date birthday;
                //gettery i settery
            }
 

Teraz użyjemy opisywanej biblioteki do stworzenia XML.

 
            public String person2Xml(Person person) {
                XStream mapping=new XStream(new DomDriver());
                String xml=mapping.toXML(person);
                return xml;
            }
 

Stworzymy i wypełnijmy obiekt Person (wypełniłem swoimi danymi) i uruchomiamy napisaną metodę. W jej wyniku ujrzymy na ekranie:

 
            <pl.marek.Person>
                <name>Marek</name>
                <surname>Kapowicki</surname>
                <birthday>1983-09-28 00:00:00.0 CET</birthday>
            </pl.marek.Person>
 

Jak widać odwzorowanie odbywa się po nazwach. XStream mapuje całą nazwę klasy (wraz z pakietem, w którym się znajduje) czego nie zawsze chcemy. Również stworzona data jest w nietypowym formacie. Na szczęście możemy wpływać na odwzorowanie np. poprzez definiowanie aliasów (najlepiej przy użyciu adnotacji). Do zamiany otrzymanego XML z powrotem na obiekt Javy używamy:

 
            public Person xml2Person(String xml){
                XStream mapping=new XStream(new DomDriver());
                return (Person) mapping.fromXML(xml);
            }
 

Zaawansowane wykorzystanie biblioteki

W celu pokazania możliwości biblioteki stworzyłem program służący do przechowywania informacji o filmach. Przechowywane dane to tytuł, opis, gatunek filmu, lista występujących aktorów, rok powstania, informację o reżyserze, okładka oraz link do strony WWW opisującej wybrany film. Aplikacja umożliwia tworzenie pliku XML, na podstawie danych wprowadzonych w Javie, oraz wykonanie operacji przeciwnej – skonstruowanie obiektów z wybranego pliku.

Aplikacja stworzona jest przy użyciu Mavena. Uruchamiamy ją wykonując z linii poleceń mvn install. Składa się z kilku pakietów, które po krótce opisze (więcej informacji znajduje się w javadocach)

- pl.marek.beans – pakiet zawierający beany, które będą mapowane na dokumenty XML tj.

- Film – do reprezentacji pojedynczego filmu

- Person – do przechowywania informacji o osobach: reżyserach, aktorach

- Films – klasa zawierająca pełną informację o filmach, właścicielu itp. Objekty tej klasy są przekształcane w pliki xml – które są właściwym wynikiem działania programu

Wszystkie beany dziedziczą po klasie abstrakcyjnej BaseXML, dzięki czemu możliwe jest mapowanie wszystkich plików przy pomocy tego samego mechanizmu (jeśli do sterowania mapowaniem używamy tylko adnotacji, a nie udostępnianych przez bibliotekę metod).

- pl.marek.enums – pakiet zawierający enuma z możliwymi kategoriami filmów

- pl.marek.xstream – zawiera właściwą część aplikacji odpowiedzialną za mapowanie danych

- XMLMappingInterface – interface służacy do mapowania Beanów dziedziczących po klasie BaseXML. Udostępnia cztery metody:

- public String java2xml(BaseXML base) – zamienia objekt na napis będący zawartością pliku XML

- public void java2xmlFile(BaseXML base, String fileName) – zamienia objekt na plik XML.

Metody tworzące XML są uniwersalne i mogą być używane do wszystkich klas dziedziczących po BeanXML. Podczas wykonywania konwersji wykrywany jest typ przetwarzanego obiektu i wczytywane są odpowiednie adnotacje.

- BaseXML xml2java(String xml,Class<? extends BaseXML> className) - zamienia napis (zawartość pliku XML) na objekt

public BaseXML xmlFile2Java(String fileName,Class<? extends BaseXML> className) - zamienia plik XML na objekt

Wywołując metody konwerujące plik xml na objekty Javy należy podać typ tego objektu.

- XMLMapping - klasa implementująca powyższy interface.

- PersonXMLMapping – służy do mapowania objektów klasy Person. Stworzona w celu pokazania, że używając udostępnianych przez XStream metod zamiast adnotacji narażamy się na problemy i nie możemy korzystać z jednego uniwersalnego mechanizmu mapowania.

- PersonConventer – konwerter służący do ręcznego odwzorowania klas na pliki XML.

Wywołania stworzonych metod znajdują się w katalogu z testami JUnit. Po uruchomieniu testów powinniśmy zobaczyć na dysku dwa nowe pliki author.xml i films_list.xml.

Patrząc w kod beanów zauważymy, że mapować możemy proste typy Javy, obiekty własnych klas np. składowa director w klasie Film oraz kolekcje – lista filmów w klasie Films

Zarządzanie mapowaniem

Korzystając z opisywanej biblioteki możemy wpływać na mapowanie. Najlepiej korzystać z adnotacji, umieszczając je przy dowolnych polach w beanach. Należy poinformować konwerter (obiekt klasy XStream) o tym, że powinien przetwarzać adnotacje. W tym celu wywołujemy metodę

xStream.processAnnotations(Class className) – adnotacje z klasy className będą wczytane przez konwerter. Jeśli używamy adnotacji przy tworzeniu pliku XML to przy jego ponownej zamianie na obiekty musimy również poinformować konwerter o adnotacjach. W przeciwnym razie XStream będzie używał odwzorowania po nazwie i nie będzie w stanie wykonać zamiany.

Przydatne adnotacje:

@XStreamAlias("nazwa") może być stosowana zarówno przy nazwach klasy, jak i nazwach pól składowych. Określa na jaką nazwę ma być mapowane wskazane pole. Przykłady użycia znajdują się w klasie Film

@XStreamImplicit(itemFieldName="nazwa") używane przy mapowaniu kolekcji. Określa na jaką nazwę mają być mapowane elementy kolekcji. Przykład użycia w klasie Films

@XStreamOmitField adnotacja umieszczane przy polach, które nie będą ulegały mapowaniu

Jeśli nie chcemy używać adnotacji możemy korzystać z metod udostępnianych przez konwerter. Przy tworzeniu pliku author.xml użyłem np. xStream.alias("director", Person.class) w celu określenia aliasu (zastępowania nazwy klasy Person napisem direktor). Uważam, że nie jest to podejście ułatwiające używanie biblioteki i "zaciemnia" kod. Dlatego zachęcam do używania adnotacji, dzięki którym możemy w prosty sposób zarządzać mapowaniem.

Konwertery

Jeśli przy mapowaniu nie chcemy używać standardowych mechanizmów wbudowanych w bibliotekę XStream, możemy własnoręcznie napisać konwerter. W tym celu musimy stworzyć klasę rozszerzającą com.thoughtworks.xstream.converters.Converter, która implementuje dwie metody:

- public boolean canConvert(Class clazz) – sprawdza jakie klasy mogą być konwertowane

- public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) – metoda, która jest wywoływana przy zamianie obiektu Java na xml:

- value – konwerowany obiekt

- writer – obiekt używany do zapisywania danych

- context – aktualny kontekst

Metoda użyta jest w napisanym przeze mnie konwerterze PersonConverter – służącym do ręcznej zamiany obiektów klasy Person

 
            DateFormat df=DateFormat.getDateInstance(DateFormat.MEDIUM);
            if(author.getBirthday()!=null) {
                writer.startNode("birthday");
                writer.setValue(df.format(author.getBirthday()));
                writer.endNode();
            }
 

W metodzie sprawdzamy, jakie pole jest aktualnie przetwarzane i określamy co należy zrobić przy przetwarzaniu tego pola. W przytoczonym przykładzie zamieniamy datę na format rrrr-mm-dd.

- public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) metoda wykonywana przy zamianie pliku xml na objekt Javy.

Podsumowując

Mam nadzieję, że udało mi się zachęcić do zapoznania z biblioteką XStream. Jest to proste narzędzie ułatwiające obsługę plików xml z poziomu Javy. Biblioteka jest łatwa w użyciu i nie powinna przysporzyć nikomu problemów. Dzięki obsłudze adnotacji i możliwości definiowania konwerterów, mamy możliwość zarządzania mapowaniem.

Komentarze

  1. mam taki fragment XML:

    ala ma kota


    Wszystko idzie pięknie, tylko gdzie wsadzić "ala ma kota" ?

    ps. Dzięki za fajny przewodnik :)

  2. ups, zapomniałem, że Wordpress spróbuje zinterpretować XML. Teraz zamieniam nawiasy ostre na kwadratowe. Mam nadzieję, że tym razem będzie widać o co chodzi :)
    [answer-data]
    [startnode from="0" to="12"]ala ma kota[/startnode]
    [/answer-data]

Tylko zalogowani użytkowincy mogą pisać komentarze

Developers World