Serializacja XML Java

W tym podrozdziale obiekty będziemy modyfikować za pomocą JAXB (ang. Java Architecture for XML Binding). Jak się przekonasz, JAXB umożliwia serializację XML — określaną przez JAXB terminem marshaling. Obiekt po serializacji to dane obiektu przedstawione w formacie XML. Po zapisie obiektu do pliku można go w dowolnym terminie odczytać i zdeserializować — dane zawarte w XML posłużą do odtworzenia oryginalnego obiektu w pamięci.


Tworzenie sekwencyjnego pliku używającego serializacji XML

Serializacja, którą przedstawimy w tym punkcie, bazuje na strumieniach tekstowych, więc wynik znajdzie się w pliku, który można otworzyć dowolnym edytorem tekstu. Zacznijmy od utworzenia obiektów i zapisania ich w pliku.

 

Deklaracja klasy Account

Zaczniemy od zdefiniowana klasy Account (rysunek 15.9), która zawiera informacje o kliencie wykorzystywane w przykładzie. Klasa Account zawiera prywatne zmienne instancjiaccount, firstName, lastName i balance (wiersze od 4. do 7.) oraz metody dostępowe
dla nich. Choć metody ustawiające w tym przykładzie nie walidują danych, w aplikacji produkcyjnej powinny.

1 // Rysunek 15.9. Account.java
2 // Klasa Account do przechowywania rekordów jako obiektów
3 public class Account {
4     private int accountNumber;
5     private String firstName;
6     private String lastName;
7     private double balance;
8
9      // Inicjalizuj obiekt wartościami domyślnymi
10    public Account() {this(0, "", "", 0.0);}
11
12    // Inicjalizuj obiekt przekazanymi wartościami
13    public Account(int accountNumber, String firstName,
14        String lastName, double balance) {
15        this.accountNumber = accountNumber;
16        this.firstName = firstName;
17        this.lastName = lastName;
18        this.balance = balance;
19    }
20
21     // Pobierz numer konta
22     public int getAccountNumber() {return accountNumber;}
23
24     // Ustaw numer konta
25     public void setAccountNumber(int accountNumber)
26         {this.accountNumber = accountNumber;}
27
28     // Pobierz imię
29     public String getFirstName() {return firstName;}
30
31     // Ustaw imię
32     public void setFirstName(String firstName)
33         {this.firstName = firstName;}
34
35     // Pobierz nazwisko
36     public String getLastName() {return lastName;}
37
38     // Ustaw nazwisko
39     public void setLastName(String lastName) {this.lastName = lastName;}
40
41     // Pobierz saldo
42     public double getBalance() {return balance;}
43
44     // Ustaw saldo
45     public void setBalance(double balance) {this.balance = balance;}
46 }

Rysunek 15.9. Klasa Account do przechowywania rekordów jako obiektów

 

Zwykłe obiekty Javy

JAXB korzysta ze standardowych obiektów Javy, czyli tak zwanych POJO (ang. plain old Java objects) — nie są potrzebne żadne klasy nadrzędne i interfejsy, aby obsłużyć serializację XML. Domyślnie JAXB umieszcza w pliku tylko publiczne zmienne instancji, i to takie z publicznymi właściwościami do odczytu i zapisu. Przypomnijmy za punktem 13.5.1, że właściwość do odczytu i zapisu to
właściwość z metodami dostępowymi stosującymi określoną konwencję nazewniczą. W klasie Account metody getAccountNumber i setAccountNumber (wiersze od 22. do 26.) definiują właściwość do odczytu i zapisu o nazwie accountNumber. Podobnie działają metody dostępowe z wierszy od 29. do 45., definiując właściwości firstName, lastName i balance. Klasa musi również zapewniać publiczny konstruktor bezargumentowy lub domyślny, aby możliwe było odtworzenie obiektów w momencie odczytu pliku.


Deklaracja klasy Accounts

Jak zauważysz na rysunku 15.11, przykład przechowuje obiekty Account wewnątrz obiektu typu List, a następnie serializuje całą listę w ramach jednej operacji. Aby móc serializować listę, musimy ją umieścić jako zmienną instancji klasy. Z tego powodu zawarliśmy List wewnątrz klasy Accounts (rysunek 15.10).

1 // Rysunek 15.10. Accounts.java
2 // Klasa Accounts pozwalająca zapisać całą listę obiektów 3 import java.util.ArrayList;
4 import java.util.List;
5 import javax.xml.bind.annotation.XmlElement;
6
7 public class Accounts {
8     // Adnotacja @XmlElement określa element XML dla każdego obiektu z listy List
9     @XmlElement(name="account")
10    private List accounts = new ArrayList<>(); // Przechowuje Accounts
11 
12     // Zwraca List
13     public List getAccounts() {return accounts;}
14 }

Rysunek 15.10. Klasa Accounts pozwalająca zapisać całą listę obiektów

 

Wiersze 9. i 10. deklarują i inicjalizują zmienną instancji accounts typu List . JAXB umożliwia dostosowywanie wielu aspektów serializacji XML, w tym również serializację prywatnych zmiennych instancji lub właściwości tylko do odczytu. Adnotacja @XMLElement (wiersz 9.; pakiet javax.xml.bind.annotation) wskazuje, że tę zmienną prywatną należy zserializować. Argument name adnotacji omówimy za chwilę. Adnotacja jest niezbędna, bo zmienna instancji nie jest publiczna i nie stosuje publicznej właściwości do odczytu i zapisu. 


Zapis obiektów w postaci XML do pliku

Program z rysunku 15.11 zapisuje obiekt Accounts do pliku tekstowego. Program ten podobny jest do programu z podrozdziału 15.4, więc skupimy się tylko na opisie nowych elementów. Wiersz 9. importuje klasę JAXB pakietu javax.xml.bind. Pakiet zawiera wiele powiązanych klas implementujących serializację XML, ale klasa JAXB zapewnia łatwe w użyciu metody statyczne z większością popularnych operacji.

1 // Rysunek 15.11. CreateSequentialFile.java
2 // Zapisywanie obiektów w pliku za pomocą JAXB i BufferedWriter
3 import java.io.BufferedWriter;
4 import java.io.IOException;
5 import java.nio.file.Files;
6 import java.nio.file.Paths;
7 import java.util.NoSuchElementException;
8 import java.util.Scanner;
9 import javax.xml.bind.JAXB;
10
11 public class CreateSequentialFile {
12     public static void main(String[] args) {
13         // Otwiera clients.xml, zapisuje obiekty i zamyka plik
14         try(BufferedWriter output =
15             Files.newBufferedWriter(Paths.get("clients.xml"))) {
16
17             Scanner input = new Scanner(System.in);
18
19             // Przechowuje Accounts przed serializacją XML
20             Accounts accounts = new Accounts();
21
22             System.out.printf("%s%n%s%n? ",
23                 "Wpisz numer konta, imię, nazwisko i saldo.",
24                 "Wpisz wskaźnik końca danych, aby zakończyć.");
25
26             while (input.hasNext()) { // Bądź w pętli aż do wskaźnika końca danych
27                 try {
28                     // Utwórz nowy rekord
29                     Account record = new Account(input.nextInt(),
30                         input.next(), input.next(), input.nextDouble());
31 
32                         // Dodaj do AccountList
33                         accounts.getAccounts().add(record);
34                     }
35                     catch (NoSuchElementException elementException) {
36                         System.err.println("Niepoprawne dane. Spróbuj ponownie.");
37                         input.nextLine(); // Pomiń dane, aby użytkownik mógł spróbować ponownie
38                     }
39
40                     System.out.print("? ");
41                 }
42
43                 // Zapisz obiekt AccountList jako XML
44                 JAXB.marshal(accounts, output);
45         }
46         catch (IOException ioException) {
47             System.err.println("Błąd otwarcia pliku. Kończę działanie.");
48         }
49    }
50 }

Wpisz numer konta, imię, nazwisko i saldo.
Wpisz wskaźnik końca danych, aby zakończyć.
? 100 Jan Kowalski 24,98
? 200 Anna Nowak -345,67
? 300 Zofia Czekaj 0,00
? 400 Ola Rudnik -42,16
? 500 Jakub Sroka 224,62
? ^Z

Rysunek 15.11. Zapisywanie obiektów w pliku za pomocą JAXB i BufferedWriter

Aby otworzyć plik, wiersze 14. i 15. wywołują metodę statyczną newBufferedWriter klasy Files, która przyjmuje obiekt Path ze ścieżką do pliku ("clients.xml"), który ma być otwarty do zapisu. Zwraca BufferedWriter, którego klasa JAXB użyje do zapisu tekstu do pliku. Jeśli plik istnieje, zostanie on przycięty i nowa treść nadpisze istniejącą. Standardowym rozszerzeniem dla plików XML
jest .xml. Wiersze 14. i 15. zgłaszają wyjątek IOException, jeśli pojawi się błąd w trakcie otwierania pliku, bo na przykład program nie ma odpowiednich uprawnień lub plik istnieje, ale znajduje się w trybie tylko do odczytu. Program w takiej sytuacji wyświetla komunikat o błędzie (wiersze od 46. do 48.) i kończy działanie. W przeciwnym razie zmienna output służy do zapisywania danych do pliku. 

Wiersz 20. tworzy obiekt Accounts zawierający List. Wiersze od 26. do 41. wyświetlają każdy rekord, tworzą obiekt Account (wiersz 29. i 30.) i dodają go do listy (wiersz 33.).

Gdy użytkownik wprowadzi znacznik końca pliku, aby zakończyć wpisywanie danych, wiersz 44. używa metody statycznej marshal z JAXB, aby zserializować do formatu XML obiekt Accounts zawierający obiekt typu List. Drugim argumentem tej przeciążonej wersji metody marshal jest obiekt typu Writer (pakiet java.io), który służy do zapisania danych XML — BuffredWriter to podklasa Writer.
Zwróć uwagę, że wystarczy tylko jedna instrukcja, aby zapisać cały obiekt Accounts i znajdujące się w nim elementy, czyli List. Jako przykładowe wartości w programie z rysunku 15.11 wpisaliśmy pięć kont — dokładnie tych samych danych użyliśmy wcześniej na rysunku 15.5. 

 

Wynikowe dane w formacie XML

Rysunek 15.12 przedstawia zawartość pliku clients.xml. Choć nie musisz w tym przykładzie znać szczegółów działania XML, z pewnością zauważysz, że jest to format czytelny dla ludzi. Gdy JAXB serializuje obiekt klasy, używa nazwy klasy z małą pierwszą literą jako nazwy elementu XML, więc element accounts (wiersze od 2. do 33.) reprezentuje obiekt Accounts.


Rysunek 15.12. Zawartość pliku clients.xml

Przypomnijmy, że wiersz 9. klasy Accounts (rysunek 15.10) poprzedza instancję zmiennej typu List adnotacją:
@XmlElement(name="account")

Poza poinformowaniem JAXB o zamiarze serializacji zmiennej instancji adnotacja określa nazwę elementu XML ("account"), który posłuży do serializacji obiektów Account w wynikowym kodzie XML. Wiersze od 3. do 8. z rysunku 15.12 reprezentują obiekt Account dla klienta Jan Kowalski. Gdybyśmy nie użyli argumentu name w adnotacji, element XML stosowały w tym miejscu nazwę accounts. Można dostosowywać wiele aspektów serializacji XML. Więcej szczegółów znajdziesz pod adresem:

https://docs.oracle.com/javase/tutorial/jaxb/intro/

Każda właściwość klasy Account ma w kodzie XML element o takiej samej nazwie jak właściwość. Wiersze od 4. do 7. zawierają elementy XML klienta Jan Kowalski — accountNumber, balance, firstName i lastName, które JAXB umieścił w kolejności alfabetycznej, ale nie jest to wymagane lub gwarantowane. Elementy zawierają wartości odpowiadające wartościom właściwości: 100 dla accountNumber, 24.98 dla balance, Jan dla firstName i Kowalski dla lastName. Wiersze od 9. do 32. zawierają pozostałe cztery obiekty Account wpisane w przykładowej aplikacji.


Odczyt i deserializacja danych z pliku sekwencyjnego

W poprzednim punkcie przedstawiliśmy tworzenie pliku XML zawierającego dane obiektów. W tym punkcie przyjrzymy się odczytowi zserializowanych danych z pliku. Rysunek 15.13 przedstawia odczyt obiektów z pliku utworzonego przez program z punktu 15.5.1 i wyświetla ich zawartość. Program otwiera plik w celu odczytu, wywołując metodę statyczną newBufferedReader klasy Files, która otrzymuje obiekt Path ze ścieżką pliku. Jeśli plik istnieje i nie pojawi się żaden wyjątek, metoda zwraca obiekt BufferedReader.


1 // Rysunek 15.13. ReadSequentialFile.java
2 // Odczyt obiektów serializowanych do XML za pomocą JAXB
3 // i BufferedReader i ich wyświetlenie
4 import java.io.BufferedReader;
5 import java.io.IOException;
6 import java.nio.file.Files;
7 import java.nio.file.Paths;
8 import javax.xml.bind.JAXB;
9
10 public class ReadSequentialFile {
11     public static void main(String[] args) {
12         // Spróbuj otworzyć plik w celu deserializacji
13         try(BufferedReader input =
14             Files.newBufferedReader(Paths.get("clients.xml"))) {
15             // Zdekoduj zawartość pliku
16             Accounts accounts = JAXB.unmarshal(input, Accounts.class);
17
18             // Wyświetl zawartość
19             System.out.printf("%-10s%-12s%-12s%10s%n", "Konto",
20                 "Imię", "Nazwisko", "Saldo");
21
22             for (Account account : accounts.getAccounts()) {
23                 System.out.printf("%-10d%-12s%-12s%10.2f%n",
24                     account.getAccountNumber(), account.getFirstName(),
25                         account.getLastName(), account.getBalance());
26                     }
27                 }
28                 catch (IOException ioException) {
29                     System.err.println("Błąd otwarcia pliku.");
30                }
31         }
32   }


Konto Imię Nazwisko Saldo
100 Jan Kowalski 24,98
200 Anna Nowak -345,67
300 Zofia Czekaj 0,00
400 Ola Rudnik -42,16
500 Jakub Sroka 224,62

Rysunek 15.13. Odczyt obiektów serializowanych do XML za pomocą JAXB i BufferedReader i ich wyświetlenie

 

Wiersz 16. używa metody statycznej unmarshal klasy JAXB, aby odczytać zawartość pliku clients.xml i zamienić dane XML na obiekt Accounts. Przeciążona wersja metody unmarshal odczytuje dane XML z obiektu Reader (pakiet java.io) i tworzy obiekty typu wskazanego jako drugi argument. Klasa BufferedReader jest  podklasą klasy Reader. Obiekt BufferedReader uzyskany w wierszach 13. i 14. odczytuje tekst z pliku. Drugi argument metody to tak naprawdę obiekt typu Class (pakiet java.lang) reprezentujący obiekty do utworzenia z XML — zapis Accounts.class to skrót dla:

new Class

Zwróć uwagę, że jedna instrukcja odczytuje cały plik i odtwarza obiekt Accounts. Jeśli nie pojawi się żaden wyjątek, wiersze od 19. do 26. wyświetlą zawartość obiektu Accounts.

 

Programowanie w Javie. Solidna wiedza w praktyce. Wydanie XI, Autorzy: Paul Deitel, Harvey Deitel, Wydawnictwo: Helion

Podobne artykuły

Podziel się ze znajomymi tym artykułem - udostępnij na FB lub wyślij e-maila korzystając z poniższych opcji:

wszystkie oferty