https://visualhunt.com/photo/206745/

XML w Javie (JAXB) cz. 2

Minęło kilka dni od poprzedniego wpisu, pora na kontynuację tematu związanego z operacjami na dokumentach XML w Javie, z wykorzystaniem „frameworku” JAXB. W poprzedniej części zarysowałem o co właściwie w tym chodzi, przedstawiłem na prostym przykładzie działanie narzędzia xjc do generowania klasy opisującej dane do wiązania ze strukturą XML oraz pokazałem jak przekształcić informacje w formacie XML na obiekty. W części drugiej przedstawię operacje odwrotne, tzn. utworzę ręcznie model (przy okazji opisując kilka podstawowych adnotacji), który następnie zostanie uzupełniony przykładowymi danymi i zapisany do pliku wynikowego.

Generalnie specjalnie nie natrudziłem się z wymyśleniem przykładu i przyjąłem najbardziej typowy. Struktura XML jaką opisałem, reprezentuje bibliotekę. Jak wiadomo w bibliotece są książki, które posiadają m. in. swój tytuł oraz autorów. Tyle wstępu, wszelkie szczegóły wyjaśnią się w dalszej części wpisu. Przykład zrealizowany jest w trzech etapach: utworzenie modelu danych, jego uzupełnienie przykładowymi wartościami oraz wygenerowanie dokumentu XML.

Model danych to zwykłe klasy JAVA (POJO), które zostały dodatkowo skonfigurowane za pomocą adnotacji pochodzących z pakietu javax.xml.bind.annotation. Opisują one jak ze struktury obiektowej ma być utworzone drzewo XML. Różnice w zastosowanych adnotacjach będą widoczne w dokumencie zawierającym definicje XML – XML Schema, jednak nie czuję się jeszcze na siłach, aby podjąć się analizy tego tematu. Poniżej zamieściłem listing klas Author, Book oraz Library oraz krótki opis użytych adnotacji.

@XmlType(name = "author")
@XmlAccessorType(XmlAccessType.NONE)
public class Author {
 
    @XmlTransient
    private int id;
 
    @XmlElement(name = "author_name", required = true)
    private String name;
 
    @XmlElement(name = "author_surname", required = true)
    private String surname;
 
    // konstruktory
    // settery i gettery
}

Pierwszą napotkaną adnotacją jest @XmlType, która mapuje klasę na typ XML Schema. Nie wnikając w szczegóły, adnotacją tą w moim przykładzie oznaczyłem elementy podrzędne drzewa XML. Element główny (korzeń) został oznaczony przy pomocy @XmlRootElement. Różnice pomiędzy nimi zostały wyczerpująco opisane na stackoverflow.com.

Adnotacja @XmlAccessorType pozwala na kontrolę procesu mapowania i przyjmuje jedną z czterech dostępnych opcji.

  1. XmlAccessType.FIELD – Wiązane są wszystkie niestatyczne oraz nieoznaczone adnotacją @XmlTransient pola klasy.
  2. XmlAccessType.NONE – Domyślnie nie są wiązane żadne pola, jednak jest to możliwe, jeśli zostaną specjalnie oznaczone, np. adnotacją @XmlElement.
  3. XmlAccessType.PROPERTY – Jeśli pole nie jest oznaczone adnotacją @XmlTransient, wiązane są wszystkie pola do których istnieją pary metod dostępowych (set i get).
  4. XmlAccessType.PUBLIC_MEMBER – Wartość domyślna w przypadku braku adnotacji @XmlAccessorType w klasie lub pakiecie, do którego klasa należy, wiążąca wszystkie publiczne pary metod dostępowych i każde publiczne pola, jeśli nie są oznaczone adnotacją @XmlTransient.

@XmlElement ma za zadanie wskazać, że pole ma być mapowane do znacznika XML. Adnotacja jest konieczna, w przypadku gdy klasę oznaczymy za pomocą @XmlAccessorType z parametrem XmlAccessType.NONE. Natomiast przykładowo przy wyborze opcji XmlAccessType.FIELD, nie jest obowiązkowe stosowanie @XmlElement, można jednak jej użyć z parametrem name, jeśli nazwa elementu XML ma być inna niż nazwa pola klasy. Analogicznie do nazwy znacznika istnieje możliwość konfiguracji np. wartości domyślnej (defaultValue) czy oznaczenia pola jako wymagane (required), co przekłada się na szablon XML Schema.

Adnotacja @XmlTransient wyłącza pole id z mapowania, staje się ono niewidoczne podczas konwertowania obiektów na strukturę XML. W moim przykładzie w ten sposób zostało oznaczone pole id, którego akurat w pliku XML nie chcę, a może być przydatne w innej części logiki aplikacji.

@XmlType(name = "book")
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
@XmlAccessorType(XmlAccessType.NONE)
public class Book {
 
    @XmlAttribute(name = "ISBN", required = false)
    String isbn;
 
    @XmlElement(name = "publisher")
    String publisher;
 
    @XmlElement(name = "title", required = true)
    String title;
 
    @XmlElementWrapper(name = "authors")
    List<Author> authors;
 
    @XmlElement(name = "pages", defaultValue = "0")
    int pagesAmount;
 
    // konstruktory
    // settery i gettery
}

W klasie Book pojawiła się nowa adnotacja – @XmlAccessorOrder, której zadaniem jest konfiguracja kolejności znaczników. Dostępne są dwie opcje:

  1. XmlAccessOrder.ALPHABETICAL – Sortowanie właściwości w klasie w kolejności alfabetycznej.
  2. XmlAccessOrder.UNDEFINED – Sortowanie nie jest określone.

Następnym nowym elementem jest @XmlAttribute, którego działanie podobne jest do adnotacji @XmlElement, jednak w tym wypadku pole klasy mapowane jest na atrybut znacznika, a nie na sam znacznik.

Adnotacja @XmlElementWrapper tworzy element nadrzędny wobec listy autorów książki. Dzięki jej zastosowaniu możliwe jest utworzenie struktury XML w poniższej postaci:

<authors>
     <author></author>
     <author></author>
</authors>

Na zakończenie pozostało przygotować klasę agregującą wszystkie książki, czyli bibliotekę. Biblioteka ma dwie cechy – swoją nazwę oraz listę dostępnych książek.

@XmlRootElement(name = "library")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"libraryName", "books"})
public class Library {
 
    @XmlElement(name = "libraryName")
    String libraryName;
 
    @XmlElements(value = {
        @XmlElement(name = "book", type = Book.class)})
    @XmlElementWrapper(name = "books")
    List<Book> books;
 
    // konstruktory
    // settery i gettery
}

@XmlRootElement jest znacznikiem określającym korzeń drzewa XML i umożliwia mapowanie obiektu na strukturę XML.

Do ustawienia kolejności znaczników XML wykorzystałem adnotację @XmlType oraz jej parametr propOrder.

Kolejny krok to przygotowanie danych. Nie ma tu żadnej magii, więc myślę, że spokojnie mogę pominąć ten etap. Po prostu dodajemy książki do biblioteki.

Library library = new Library();
// (...)
library.addBook(book3);

Ostatecznie została wykonana serializacja, która wygląda analogicznie do operacji unmarshallingu z poprzedniej części wpisu o JAXB, tylko działa w drugą stronę. 🙂 Tu również potrzebny jest kontekst JAXBContext, z którego można utworzyć obiekt typu Marshaller. Oczywiście można wykonać na nim dodatkową konfigurację (setProperty()), np. ustawiając kodowanie (jaxb.encoding) czy formatowanie (jaxb.formatted.output). W przykładzie struktura XML generowana jest na standardowe wyjście (System.out) oraz do pliku output.xml.

private void generateXML(Library library) throws PropertyException, JAXBException {
        File file = new File("output.xml");
        JAXBContext jaxbContext = JAXBContext.newInstance(Library.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        jaxbMarshaller.marshal(library, file);
        jaxbMarshaller.marshal(library, System.out);
    }

Dla zobrazowania działania powyższego kodu, zamieszczam poniżej wynikowy plik output.xml. Struktura XML zawiera dane w formie zgodnej z oczekiwaniami. Każdy znacznik book zawiera atrybut z numerem ISBN oraz znaczniki potomne z jednym lub wieloma autorami publikacji, tytułem i wydawnictwem. Widać również poprawność działania adnotacji @XmlTransient, dzięki której atrybut id klasy Author nie został uwzględniony w wynikowym pliku.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<library>
    <books>
        <book ISBN="978-83-283-1333-0, 9788328313330">
            <authors>
                <author>
                    <author_name>Cay S.</author_name>
                    <author_surname>Horstmann</author_surname>
                </author>
            </authors>
            <pages>248</pages>
            <publisher>Helion</publisher>
            <title>Java 8. Przewodnik doświadczonego programisty</title>
        </book>
        <book ISBN="978-83-283-3006-1, 9788328330061">
            <authors>
                <author>
                    <author_name>Allen B.</author_name>
                    <author_surname>Downey</author_surname>
                </author>
                <author>
                    <author_name>Chris</author_name>
                    <author_surname>Mayfield</author_surname>
                </author>
            </authors>
            <pages>0</pages>
            <publisher>Helion</publisher>
            <title>Myśl w języku Java! Nauka programowania</title>
        </book>
        <book>
            <authors>
                <author>
                    <author_name>Rogers</author_name>
                    <author_surname>Cadenhead</author_surname>
                </author>
            </authors>
            <pages>0</pages>
            <publisher>Helion</publisher>
            <title>Java w 21 dni. Wydanie VII</title>
        </book>
    </books>
</library>

Moje wnioski są jednoznaczne – JAXB umożliwia w bardzo prosty, a przede wszystkim szybki sposób, wykonywanie operacji na strukturach XML. Jak pokazał pierwszy przykład (utworzenie modelu za pomocą narzędzia xjc), wystarczy się jedynie orientować, co w pliku się znajduje, aby być w stanie przetwarzać dane w nim zawarte. Nie jest konieczna dokładna analiza struktury dokumentu, aby dobrać się do danych.

Jeśli wrócimy na chwilę do tematyki pierwszej części i zajrzymy do pliku z wygenerowanym modelem, przekonamy się, że są tam te same adnotacje, które dziś opisywałem. Na pewno jest to mniej czytelne niż klasy napisane odręcznie. Poza tym w jednym pliku znajduje się ich kilka. Jest to jednak szybkie rozwiązanie umożliwiające wygenerowanie modelu ze schematu, bez większej rozkminy.

Te dwie części na temat JAXB z pewnością nie wyczerpały całego tematu – zdaję sobie z tego sprawę. Z pewnością zabrakło wątku związanego z generowaniem schematu na podstawie modelu, z wykorzystaniem narzędzia schemagen oraz wiele wiele innych. Nie wspominałem również o wbudowanym w JAXB mechanizmie walidacji, który weryfikuje poprawność przetwarzanej struktury. Jest to jednak wystarczający wstęp, aby pracować z tym narzędziem oraz abym mógł wrócić za jakiś czas do tych moich notatek i w razie potrzeby odświeżyć wiedzę. Będę się cieszył, jak przyda się to jeszcze komuś innemu. 🙂

Na zakończenie zostawiam linka do mega ogromnej bazy wiedzy na temat JSR-222 (JAXB) oraz tematy pokrewne: Blaise Doughan Blog.

Literatura obowiązkowa:

Package javax.xml.bind.annotation

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *