Oct 14 2007
JAXB 2, Wprowadzenie
Filed under Eclipse,Java,JAXB,tlumaczenia,XML by Łukasz Dywicki
W ramach WarsJava, konferencji/warsztatów organizowanych przez Warszawski JUG, będę miał szansę przedstawić publiczności Java Architecture for XML Binding w wersji drugiej. Swoje boje z JAXB postanowiłem opisać na blogu, być może dla kogoś temat wyda się interesujący.. :)
Ogólniki
JAXB ma za zadanie ułatwić pracę z XML poprzez automatyczne dostarczanie obiektów zamiast żmudnego, ręcznego obrabiania plików SAX, DOM czy StAX. Jest to dodatkowa warstwa zbudowana na bazie Java Api for XML Processing
. Druga wersja JAXB jest dołączona do J2SE 1.6.
Spójrzmy teraz na schemat budowy JAXB:
- Kompilator
- Schema – dokument opisujący strukturę dokumentu – może to być XML Schema, DTD jak również Relax NG czy też WSDL.
- XML /Java Binding Customization – JAXB daje nam możliwość wpływania na wygenerowany kod poprzez ten właśnie mechanizm, dzięki temu można określić np. metody, które będą tworzyć elementy w przypadku odczytu z dokumentu bądź serializować w przypadku zapisu.
- Runtime
- Portable JAXB-annotated classes – kod, który wygenerowaliśmy przy użyciu kompilatora bądź klasy, do których dodaliśmy adnotacje.
- Object Factory – klasa, która tworzy obiektowe reprezentacje naszych elementów XML.
- Binding runtime framework implementation – obsługuje proces odczytu danych (unmarshalling) oraz ich zapisu (marshalling), obsługuje również walidację. Element ten do poprawnego działania wymaga elementów wymienionych powyżej – czyli klas z adnotacjami oraz ich “fabryki”.
Pierwszy projekt
Pamiętam, że kiedyś chciałem stworzyć prostą aplikację w której mógłbym notować jakie książki mam i komu je wypożyczyłem. Niestety w którymś momencie zabrakło chyba sił i poza definicją struktury dokumentu stworzyłem tyko kilka linii kodu. Nie mniej, po drobnych przeróbkach udało się doprowadzić schemat do porządku. W aplikacji będą występować następujące instancje klas:
- Books – repozytorium książek, zawiera listę książek
- Book – definicja książki – atrybuty to title, isbn oraz lista autorów
- Author – reprezentacja autora – atrybuty firstName, lastName. Dodatkowo opcjonalny jest type wskazujący czy mamy do czynienia z tłumaczem czy też z “normalnym” autorem.
- AuthorType – typ wyliczeniowy – dostępne wartości to Translator, Author
Aby korzystało się nam z kompilatora (xjc) wygodniej podepniemy go pod Eclipse jako narzędzie zewnętrzne. Tu przyda się kilka informacji na temat samego kompilatora:
xjc [-options …] file/URL/dir … [-b bindinfo] …
Gdzie najważniejsze opcje to:
- -d, katalog do którego trafi wygenerowany kod
- -b, dodatkowe mappingi umożliwiające zmianę zachowania kompilatora
- -p, paczka do której trafi wygenerowany kod
- -classpath, miejsca w których xjc ma szukać klas, które zostały użyte w mappingach
Przykładowo
xjc -d generated schema.xsd
Spowoduje, że klasy wylądują w katalogu generated. Nazwa paczki zostanie zaczerpnięta z atrybutu targetNamespace dokumentu zawartego w schemacie. Obok znajduje się screen z konfiguracją – z ważniejszych informacji – Working directory ustawione na ${project_loc} pozwala nam się nawigować po katalogach względem aktualnie zaznaczonego w nawigatorze projektu. wartość w polu Arguments -d generated ${resource_loc} -verbose -extension -npa oznacza, że wygenerowane klasy wylądują w katalogu “generated” (należy stworzyć taki source folder). Zmienna ${resource_loc} pozwala nam użyć aktualnie zaznaczonego pliku w nawigatorze. Dzięki takiej konfiguracji niezależnie od projektu i pliku jesteśmy w stanie wygenerować potrzebne nam klasy poprzez dwa kliknięcia – pierwsze w nawigatorze, drugie na ikonie external tools.
Schemat jest dostępny do ściągnięcia, podobnie jak przykładowy plik z danymi. Proszę zwrócić uwagę na to jak jest zdefiniowany główny element dokumentu – poprzez zawarcie typu anonimowego. Jeśli zdefiniujemy oddzielnie element i typ to wówczas konieczne będzie ręczne dodanie adnotacji @XmlRootElement przy klasie Books. O tym dlaczego JAXB się tak zachowuje można wyczytać na jednym z Sun-owskich blogów.
package pl.dywicki.books.app; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import pl.dywicki.books.Author; import pl.dywicki.books.AuthorType; import pl.dywicki.books.Book; import pl.dywicki.books.Books; import pl.dywicki.books.ObjectFactory; /** * Testowa klasa pokazująca użycie JAXB do zapisywania danych. * * @author Łukasz Dywicki */ public class App { public static void main(String[] args) throws Exception { // repozytorium książek Books books = new Books(); // przykładowa książka Book book = new Book(); book.setTitle("Test title"); book.setIsbn("111-111-111"); // testowy autor Author author = new Author(); author.setFirstName("Martin"); author.setLastName("Fowler"); book.getAuthor().add(author); // testowy tłumacz author = new Author(); author.setFirstName("Łukasz"); author.setLastName("Dywicki"); author.setType(AuthorType.TRANSLATOR); book.getAuthor().add(author); // dodanie książki do repozytorium books.getBook().add(book); JAXBContext context = JAXBContext.newInstance(ObjectFactory.class); Marshaller marshaller = context.createMarshaller(); // chcemy ładnych wcięć w wyniku! :) marshaller.setProperty("jaxb.formatted.output", true); marshaller.marshal(books, System.out); } }
Po uruchomieniu tego przykładu w konsoli powinien pokazać się taki oto wynik:
< ?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2_books xmlns:ns2="http://dywicki.pl/books"> <book> <title>Test title</title> <isbn>111-111-111</isbn> <author> <firstname>Martin</firstname> <lastname>Fowler</lastname> </author> <author> <firstname>Łukasz</firstname> <lastname>Dywicki</lastname> <type>Translator</type> </author> </book> </ns2_books>
(Zamiast ns2_books powinno być ns2:books, ale narzędzie kolorujące składnie sobie nie radzi).
Dla zainteresowanych załączam źródła projektu.
Fajne :) Choc mowiac prawde nawet PeHap sie czegos takiego juz dorobil :)
@Paweł czy mógłbyś podać jakiś link bądź nazwę? Z chęcią przyjrzę się implementacji. :)
Jedna rzecz nie daje mi spokoju: “ns2:” – Nie da się, by był bez tego wygenerowany XML?
Można pominąć przestrzeń nazw dostarczając rozszerzenie klasy NamespacePrefixMapper. Gdy Twoja implementacja zwróci pusty string JAXB powinno pominąć przestrzeń nazw i potraktować ją jako domyślną (xmlns=”mynamespace”).
Wydaje mi się to proste tylko w teorii. W praktyce nie chce mi zadziałać :(. Nie wiem jaką wersję mam JAXB, ale rzuca mi Exceptionem przy ustawianiu: com.sun.xml.bind.namespacePrefixMapper. W ogóle nie istnieje taka klasa. Już nawet “bind” nie istnieje.
Myślałem, że Java jest prostsza, a tutaj by cokolwiek zrobić to trzeba jakieś hacki.. :/… Co i tak nie udaje się (tak jak mi teraz).
Chyba najprostszym sposobem będzie zamiana wszystkich “ns2:” na “”, ale to nie jest zbyt eleganckie.
Oprócz tego przeczytałem, że jeśli klasa zwróci “” albo null, to ustawi namespace na standardowy (czyli “ns2”).
Fragment kodu, który działa:
Gdzie AgaviNamespacePrefixMapper to:
NamespacePrefixMapper domyślnie nie jest dostępny w paczce jaxb-impl, musisz dorzucić jeszcze jaxb-xjc.ar (korzystając z JDK 6 ominiesz ten problem).
No to mam problem, bo korzystam z JDK 6 (tak ustawiłem we właściwościach projektu) a paczki odpowiedniej nie znajduje. Co prawda nie używam eclipse’a tylko netbeans, ale wydaje mi się, że to nie powinien być w tym problem. Chyba, że źle zainstalowałem jdk1.6… Ale wątpię, bo w Windowsie po prostu się klika i wszystko powinno być gotowe :).
Swoją drogą nie mogę znaleźć nigdzie jaxb-xjc.jar.
Czy to, że korzystam z Apache/Tomcat robi dużą różnicę?
Komplet potrzebnych Ci paczek. Po ściągnięciu konieczne jest rozpakowanie przez java -jar JAXB2_20060426.jar.
To, w jakim środowisku korzystasz nie robi różnicy, jeśli masz dołączone biblioteki. Uprawnij się czy np tomcat nie chodzi na domyślnej, systemowej VM (sprawdź czy masz zainstalowane JDK 5.0, ew gdzie wskazuje JAVA_HOME).
Z tym coś nie chce działać nadal. Ale to już chyba nie na temat na komentarz do artykułu. Gdy dodałem paczkę do bibliotek i uruchomiłem test taki exception rzucił:
:(..
Zgaduję – upewnij się, że Twój mapper nie zwraca nulla tylko pustą nazwę. Ew zweryfikuj poprawnosć adnotacji.
Sam już nie wiem. Wydaje się, że nie miał szans by zwracać nulla (chyba, że suggestion takie było). Przeczytałem, że by takie zabiegi robić, w NetBeans trzeba w JAXB Wizard zaznaczyć “Extenssion”.. Ale niestety nie jestem w stanie tego zrobić, bo ten czarodziej działa tylko z Glassfish, a nie z Tomcatem (http://www.nabble.com/JAXB-in-Web-Application-tp15009566p15049223.html)… Ale dzięki za pomoc!
[…] Jedną z bolączek JAXB jest problematyczna obsługa dat i czasów. Przypomnijmy sobie schemat użyty w jednej z wcześniejszych not. […]
Teraz zamiast generowac na wyjsciu plik xml, chcilabym z niego moc czytac. Zrobilem rzecz nastepujaca:
JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
Unmarshaller um = context.createUnmarshaller();
Books books = (Books) um.unmarshal(new File( “src/books.xml”));
List booklist = books.getBook();
for (Iterator iter = (Iterator) booklist.iterator(); iter.hasNext();)
{
Book book = (Book) iter.next();
System.out.println(“Zobacz czy w ogole wchodzi do petli”);
}
Chcialbym moc po prostu wyisac ksiazki, ich auturow. Okazuje sie ze do petli for w ogole nie wchodzi gdyz jak pozniej sprawdzilem:
int lenght = booklist.size();
System.out.println(lenght);
Wartosc lenght daje w wyniku 0. Wie ktos moze jaka jest tego przyczyna?