2010.06_Biblioteka Dozer Proste mapowanie obiektów_[Biblioteka Miesiaca].pdf

(1065 KB) Pobierz
441090793 UNPDF
BIBLIOTEKA MIESIĄCA
Biblioteka Dozer
Proste mapowanie obiektów
Ręcznie pisanym translatorom można zarzucić wiele –
są czasochłonne, błędogenne, a ich przejrzystość zależy
w dużej mierze od umiejętności programistycznych.
Może czas odrzucić dotychczasowe przyzwyczajenia?
Interesującym rozwiązaniem wydaje się być biblioteka Dozer.
Dowiesz się:
• W jaki sposób zainicjalizować Dozer'a;
• W jaki sposób tworzyć proste i zaawansowane pliki konigura-
cyjne wykorzystywane przez Dozer'a;
• W jaki sposób mapować obiekty typu JAXB przy pomocy Dozer'a.
Powinieneś wiedzieć:
• Jak programować w języku Java;
• Podstawowa znajomość XML'a.
pomiędzy obiektami z różnych modeli danych.
W przypadku systemów rozproszonych,
obiekty przekazywane są pomiędzy różnymi systemami.
Sytuacje, w których zewnętrzny model danych wykorzy-
stywany jest wewnątrz naszej aplikacji, są niepożądane.
Zdarza się, że dostawca usług nagle zmienia w zna-
czący sposób model danych. Następstwem tego są
zmiany w całej naszej aplikacji. Dlatego lepszym roz-
wiązaniem byłoby mapowanie obiektów zewnętrznych
na nasz wewnętrzny model danych, na najniższej war-
stwie. W dużej mierze uniezależniłoby to nasz system
od zmian pochodzących z zewnątrz.
Powszechnym rozwiązaniem powyższego problemu
jest pisanie ręcznych translatorów. Jednak implementa-
cja ich jest zbyt czasochłonna, a programista jest zmu-
szony do ręcznego odwzorowania wszystkich zależ-
ności. Nawet gdy klasy różnią się między sobą w nie-
wielkim stopniu. Tak zaimplementowany kod jest mało
przejrzysty i trudny w szybkiej analizie. Dodatkowo sto-
sunkowo łatwo o pomyłkę.
W niniejszym artykule przedstawię, w jaki sposób roz-
wiązać typowe problemy mapowania przy pomocy biblio-
teki Dozer. Biblioteka wprowadza jednolity sposób zapi-
sywania relacji pomiędzy obiektami w plikach XML. Nie
wymaga ona żadnej modyfikacji istniejących klas (np.
Szybki start w Eclipse
Koniguracja projektu w Eclipse:
1. Stworzyć w Eclipse nowy projekt Java;
2. Pobrać binarną dystrybucję biblioteki Dozer (patrz: ramka W sieci);
3. W zakładce do konigurowania bibliotek zewnętrznych ( Project -> Properties -> Java Build Path -> Libraries ) dodać bibliotekę
dozer.osgi-5.2.0.jar za pomocą przycisku Add external JARs ;
4. Powtórzyć kroki 2 i 3 dla bibliotek JAXB, Apache Commons Beanutils, Apache Commons Collections, Apache Commons Lang
i Apache Commons Logging.
Dodatkowo w przypadku biblioteki JAXB, po ściągnięciu jej z Internetu należy ją rozpakować, stosując się do instrukcji umieszczo-
nych na stronie domowej biblioteki. Spośród wypakowanych plików, do build path'a należy dodać plik o nazwie jaxb-api.jar.
Z przyczyn technicznych na niektórych Listingach nie znalazły się deinicje setter'ów i getter'ów, co zostało oznaczone stoso-
wanym komentarzem. Jednak w łatwy sposób Czytelnik może wygenerować wspomniane funkcje za pomocą narzędzi wbu-
dowanych w Eclipse'a. W kodzie źródłowym klasy wywołujemy menu kontekstowe i klikamy kolejno Source -> Generate getters
and setters .
6
6/2010
M apowanie obiektów jest to kopiowanie danych
441090793.028.png 441090793.029.png
 
441090793.030.png 441090793.001.png 441090793.002.png 441090793.003.png
Biblioteka Dozer
dodanie adnotacji). Dzięki temu jest to w pełni przeźro-
czyste rozwiązanie. Pierwsza wersja biblioteki Dozer zo-
stała wydana w 2005 roku. Od tego czasu jest intensyw-
nie rozwijana i optymalizowana. Ponadto cechuje się
przemyślanymi rozwiązaniami i dużą stabilnością.
Struktura niniejszego artykułu jest następująca: na
początek przedstawię sposób wykorzystania API Do-
zer'a oraz podstawową koncepcję zapisywania rela-
cji w plikach konfiguracyjnych. Następnie zaprezentu-
ję rozbudowane mechanizmy sterowania mapowaniem
i omówię szczegółowo zastosowane techniki. W dal-
szej kolejności zmierzę się z rzeczywistym problemem
– mapowaniem obiektów typu JAXB. Na koniec porów-
nam wydajność pomiędzy Dozer'em a ręcznie pisany-
mi translatorami.
wpisać i skompilować klasy znajdujące się na Listin-
gach od 1 do 5. Przykład uruchamiamy, wywołując kla-
DozerExample .
Przeanalizujmy krok po kroku kod programu znajdu-
jący się na Listingu 1. Na początku dołączamy niezbęd-
ne nagłówki. Następnie inicjalizujemy bibliotekę Dozer,
za co odpowiedzialna jest instrukcja:
DozerBeanMapper mapper = (DozerBeanMapper) DozerBeanMapp
erSingletonWrapper.getInstance();
Wykorzystaliśmy tutaj klasę DozerBeanMapperSingleton
Wrapper , ponieważ twórcy biblioteki zalecają, aby w ca-
łej naszej aplikacji występowała tylko jedna instancja
klasy DozerBeanMapper (tzw. singleton). Dalej inicjalizu-
jemy klasę JabberPerson testowymi wartościami:
Zaczynamy
Pierwszą czynnością, którą należy wykonać, jest konfi-
guracja środowiska pracy. Osoby korzystające z Eclip-
se'a zapraszam do przestudiowania zawartości ramki
Szybki start w Eclipse . Czytelników chcących wykorzy-
stać bibliotekę wraz ze Spring Framework odsyłam do
ramki Szybki start w Spring . Warto także wspomnieć,
że biblioteka Dozer jest dostępna w centralnym repozy-
torium Maven'a. W ramce W sieci umieściłem bardziej
szczegółowe informacje z tym związane.
Gdy środowisko pracy jest już gotowe, możemy roz-
począć przygodę z Dozer'em. Na potrzeby artykułu wy-
obraźmy sobie, że jesteśmy programistami multiko-
munikatora internetowego, Pidgin. Aplikacja jest zmu-
szona do mapowania obiektów konkretnego protoko-
łu na swój model danych. Tak więc należy utworzyć,
JabberPerson source = new JabberPerson("Jan",
"Kowalski", 40, new
JabberAddress("Wojska Poskiego",
"3"));
Ostatecznie zlecamy bibliotece przemapowanie obiek-
tu typu JabberPerson na obiekt typu PidginPerson za
pomocą instrukcji:
PidginPerson destination = mapper.map(source,
PidginPerson.class);
Listing 1. Najprostsze użycie biblioteki Dozer (plik
DozerExample.java)
Szybki start w Spring
Spójrzmy na prosty przykład, tego, jak wykorzystać Do-
zer'a w istniejącym serwisie zarządzanym przez Sprin-
g'a. W tym celu, w dowolnym serwisie, deiniujemy inter-
fejs org.dozer.Mapper w formie właściwości komponen-
tu JavaBean. Niech właściwość będzie miała nazwę map-
per. Następnie, w plikach XML deiniujących kontekst apli-
kacji, określamy, że właściwość mapper powinna być usta-
wiona na egzemplarz klasy org.dozer.DozerBeanMapper
(który deiniujemy jako element bean). Teraz w serwisie mo-
żemy korzystać z dobrodziejstw biblioteki Dozer, wykorzy-
stując zmienną mapper .
import java.util.*;
import org.dozer.*;
public class DozerExample {
public static void main ( String ... args ) {
DozerBeanMapper mapper = ( DozerBeanMapper )
DozerBeanMapperSingletonWrapper .
getInstance () ;
mapper . setMappingFiles ( new
ArrayList < String > ()) ;
JabberPerson source = new
JabberPerson ( "Jan" , "Kowalski" ,
40 ,
new JabberAddress ( "Wojska Poskiego" ,
"3" )) ;
PidginPerson destination =
mapper . map ( source ,
PidginPerson . class ) ;
System . out . println ( "JabberPerson-
>PidginPerson: " + destination ) ;
Plugin do Eclipse'a
W ramach projektu Dozer rozwijana jest również wtyczka
do Eclipse'a, usprawniająca edycję plików koniguracyjnych.
Możliwości, jakie oferuje wtyczka:
• podpowiadanie nazw klas i pól w trakcie pisania;
• walidacja nazw klas i pól;
• graiczne przedstawienie treści zawartej w plikach koni-
guracyjnych.
}
}
www.sdjournal.org
7
441090793.004.png 441090793.005.png 441090793.006.png 441090793.007.png 441090793.008.png 441090793.009.png 441090793.010.png 441090793.011.png
 
BIBLIOTEKA MIESIĄCA
Wynikiem działania naszego programu jest:
Listing 2. Źródłowe DTO (plik JabberPerson.java)
JabberPerson->PidginPerson: PidginPerson@c8769b[name=<nu
ll>,lastName=Kowalski,age=40,address
=PidginAddress@dc9766[road=<null>,nu
mber=3]]
import org.apache.commons.lang.builder.ToStringBui
lder;
public class JabberPerson {
private String irstName ;
private String lastName ;
private int age ;
private JabberAddress address ;
Zatrzymajmy się w tym miejscu na moment, aby dokład-
nie przeanalizować mapowanie zrealizowane przez Do-
zer'a. Porównując klasy z Listingu 2 i 4, widzimy, że róż-
nią się one jedynie polami irstName i name . Natomiast
klasy Listingu 3 i 5 różnią się jedynie polami street i ro-
ad . Spójrzmy raz jeszcze na wynik działania naszego
programu. Wszystkie pola zostały poprawnie przema-
powane, oprócz pól name i road . Tak więc, Dozer auto-
matycznie dopasowuje pola o tej samej nazwie. Jest to
domyślne zachowanie przy braku jakiejkolwiek konigu-
racji. Dodatkowo mapowanie przeprowadzone za po-
mocą Dozer'a ma charakter rekurencyjny.
Dozer domyślnie stara się wywoływać metody o na-
zewnictwie ustalonym przez Java Beans przy pobiera-
niu bądź zapisywaniu wartości. Biblioteka domyślnie
zawiera konwersję pomiędzy podstawowymi typami ję-
zyka Java (prymitywy, obiekty opakowujące, String, Da-
te itd.) oraz obsługę kolekcji.
public JabberPerson () {}
public JabberPerson ( String irstName , String
lastName , int age , JabberAddress
address ) {
this . irstName = irstName ;
this . lastName = lastName ;
this . age = age ;
this . address = address ;
}
/* Implementacja metod typu get i set zostala
pominieta*/
Pliki konfiguracyjne
Naprawmy niedociągnięcia z przykładu pierwsze-
go. Zmuśmy Dozer'a do poprawnego mapowania pól
name i road . Głównym założeniem Dozer'a jest zapisy-
wanie relacji pomiędzy klasami w plikach konfiguracyj-
nych, które są zwykłymi plikami XML. Dodajmy do na-
szego projektu plik konfiguracyjny znajdujący się na Li-
stingu 6. Pokrótce omówmy jego strukturę. Relacja po-
między dwoma klasami definiowana jest za pomocą ta-
ga mapping (wszystkie tagi mapping muszą znajdować
się w tagu mappings ). Każdy tag mapping musi posia-
dać tagi class-a i class-b definiujące odpowiednio kla-
sę źródłową i docelową. Dodatkowo tag mapping mo-
że zawierać dowolną ilość tagów field , reprezentują-
cych zależności pomiędzy polami obu tych klas. Z ko-
lei tag field musi posiadać tagi a i b odpowiednio defi-
niujące pole z klasy źródłowej i pole z klasy docelowej.
Przykładowy fragment zmusza Dozer'a do mapowania
pola firstName z klasy JabberPerson na pole name z kla-
sy PidginPerson :
public String toString () {
return ToStringBuilder . relectionToString ( this ) ;
}
}
Listing 3. Źródłowe DTO (plik JabberAddress.java)
import org.apache.commons.lang.builder.ToStringBui
lder;
public class JabberAddress {
private String street ;
private String number ;
public JabberAddress () {}
public JabberAddress ( String street , String number )
{
this . street = street ;
this . number = number ;
}
<mapping>
<class-b>JabberPerson</class-b>
<class-a>PidginPerson</class-a>
<ield>
<a>irstName</a>
<b>name</b>
</ield>
</mapping>
/* Implementacja metod typu get i set zostala
pominieta*/
public String toString () {
return ToStringBuilder . relectionToString ( this ) ;
}
}
8
6/2010
441090793.012.png 441090793.013.png
 
441090793.014.png 441090793.015.png 441090793.016.png
 
Biblioteka Dozer
Listing 4. Docelowe DTO (plik PidginPerson.java)
import org.apache.commons.lang.builder.ToStringBuilder;
public class PidginPerson {
private String name ;
private String lastName ;
private int age ;
private PidginAddress address ;
/* Implementacja metod typu get i set zostala pominieta*/
public String toString () {
return ToStringBuilder . relectionToString ( this ) ;
}
}
Listing 5. Docelowe DTO (plik PidginAddress.java)
import org.apache.commons.lang.builder.ToStringBuilder;
public class PidginAddress {
private String road ;
private String number ;
/* Implementacja metod typu get i set zostala pominieta*/
public String toString () {
return ToStringBuilder . relectionToString ( this ) ;
}
}
Listing 6. Podstawowy przykład pliku kon�guracyjnego (plik simple-example-mappings.xml)
< mappings xmlns= "http://dozer.sourceforge.net" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:
schemaLocation= "http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/
beanmapping.xsd" >
< mapping >
< class-a > JabberPerson < /class-a >
< class-b > PidginPerson < /class-b >
< ield >
< a > irstName < /a >
< b > name < /b >
< /ield >
< /mapping >
< mapping >
< class-a > JabberAddress < /class-a >
< class-b > PidginAddress < /class-b >
< ield >
< a > street < /a >
< b > road < /b >
< /ield >
< /mapping >
< /mappings >
www.sdjournal.org
9
441090793.017.png 441090793.018.png 441090793.019.png 441090793.020.png 441090793.021.png 441090793.022.png 441090793.023.png
 
BIBLIOTEKA MIESIĄCA
Wykorzystajmy nowo stworzony plik koniguracyjny
w naszym programie. W tym celu rozszerzmy dotych-
czasową implementację klasy DozerExample . Na po-
czątek zmodyikujemy instrukcję odpowiedzialną za
ustawianie listy plików koniguracyjnych. Należy za-
mienić starą instrukcję:
<ield-exclude>
<a>status</a>
<b>status</b>
</ield-exclude>
Atrybut type
Domyślnie definiowane mapowania mają charakter
dwukierunkowy. Jednak jesteśmy w stanie wymusić,
aby dana definicja mapowania była tylko jednokierun-
kowa. Realizuje się to poprzez dodanie atrybutu type
o wartości one-way do taga mapping :
mapper.setMappingFiles(new ArrayList<String>());
na nową:
mapper.setMappingFiles(Arrays.asList("simple-example-
mappings.xml"));
<mapping type="one-way">
Podczas ładowania pliku koniguracyjnego, Dozer
zweryikuje, czy użyte tam nazwy klas lub pól faktycz-
nie istnieją w rzeczywistości. Pozostaje jeszcze dodać
mapowanie odwrotne. Dokonamy tego, dodając poniż-
szą instrukcję na końcu metody main :
Atrybut wildcard
Standardowo Dozer próbuje automatycznie dopasować
do siebie pola, po ich nazwie. Oczywiście mamy moż-
liwość wyłączenia takiego zachowania. Realizujemy to
poprzez dodanie atrybutu wildcard o wartości false do
taga mapping :
System.out.println("PidginPerson->JabberPerson:
" + mapper.map(destination,
JabberPerson.class));
<mapping wildcard="false">
Zagnieżdżone odwołania
Dozer oferuje możliwość odwoływania się do zagnież-
dżonych pól. Dla przykładu posiadamy obiekt, który za-
wiera pole status . Natomiast nasz drugi obiekt posia-
da pole description , jednak znajduje się ono głęboko
w hierarchii:
Po uruchomieniu tak zmodyikowanego programu, na
wyjściu pojawia się następujący komunikat:
JabberPerson->PidginPerson: PidginPerson@96ad7c[name=J
an,lastName=Kowalski,age=40,addres
s=PidginAddress@9ea173[road=Wojska
Poskiego,number=3]]
PidginPerson->JabberPerson: JabberPerson@c9b196[irstName
=Jan,lastName=Kowalski,age=40,addres
s=JabberAddress@a9f87c[street=Wojska
Poskiego,number=3]]
<ield>
<a>status.description</a>
<b>status</b>
</ield>
Atrybut copy-by-reference
Dozer obsługuje kopiowanie pól poprzez referencje.
Nie są wykonywane wtedy żadne konwersje. Umoż-
liwia to zmniejszenie zapotrzebowania na pamięć,
a także zwiększa wydajność. Aby włączyć kopiowanie
pola poprzez referencje, należy dodać atrybut copy-by-
reference o wartości true do taga field :
Pola name i road zostały przemapowane w sposób po-
prawny. Jakby tego było mało, pokazaliśmy, że wszyst-
kie deiniowane zależności pomiędzy klasami mają
charakter dwukierunkowy (chyba że skonigurujemy in-
aczej, o czym później).
Zaawansowana konfiguracja
Zobaczmy, jakie możliwości do tej pory ukrywały przed
nami pliki konfiguracyjne. Pokrótce przedstawię wybra-
ne sposoby sterowania mapowaniem. Oczywiście nie
jest to kompletna lista. Szczegółowe informacje moż-
na znaleźć na stronie domowej biblioteki (patrz: ram-
ka W sieci ).
<ield copy-by-reference="true">
<a>ield1</a>
<b>ield2</b>
</ield>
Atrybut map-id
Mamy możliwość stworzenia wielu mapowań pomiędzy
tymi samymi klasami, nadając każdemu nazwę profi-
lu. Nazwę profilu definiujemy poprzez dodanie atrybu-
tu map-id (gdzie wartość atrybutu to nazwa profilu) do
taga mapping :
Tag field-exclude
Za pomocą taga field-exclude możemy wykluczyć
z mapowania konkretne pola. Dla przykładu, poniższy
fragment wyklucza mapowanie pola status :
10
6/2010
441090793.024.png 441090793.025.png
 
441090793.026.png 441090793.027.png
 
Zgłoś jeśli naruszono regulamin