Иронично ли, что может быть сложно отобразить класс java.util.Map в JAXB (JSR-222) ? В этом посте я расскажу о некоторых вещах, которые сделают это намного проще.
Модель Java
Ниже приведена модель Java, которую мы будем использовать для этого примера.
Клиент
Класс Customer имеет свойство типа Map . Я выбрал эту Карту специально, поскольку ключ является объектом домена, а значение — объектом домена.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package blog.map;import java.util.*;import javax.xml.bind.annotation.*;@XmlRootElementpublic class Customer { private Map<String, Address> addressMap = new HashMap<String, Address>(); public Map<String, Address> getAddressMap() { return addressMap; } public void setAddressMap(Map<String, Address> addressMap) { this.addressMap = addressMap; }} |
Адрес
Класс Address — это просто типичный POJO.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
package blog.map;public class Address { private String street; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; }} |
Демонстрационный код
В демонстрационном коде ниже мы создадим экземпляр Customer и заполним его свойство Map . Затем мы перенесем это в XML.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package blog.map;import javax.xml.bind.*;public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); Address billingAddress = new Address(); billingAddress.setStreet('1 A Street'); Address shippingAddress = new Address(); shippingAddress.setStreet('2 B Road'); Customer customer = new Customer(); customer.getAddressMap().put('billing', billingAddress); customer.getAddressMap().put('shipping', shippingAddress); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); }} |
Вариант использования № 1 — Представление по умолчанию
Ниже приведен пример XML, соответствующего нашей модели предметной области. Мы видим, что у каждого элемента на карте есть элементы ключа и значения, заключенные в элемент ввода .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<?xml version='1.0' encoding='UTF-8'?><customer> <addressMap> <entry> <key>shipping</key> <value> <street>2 B Road</street> </value> </entry> <entry> <key>billing</key> <value> <street>1 A Street</street> </value> </entry> </addressMap></customer> |
Вариант использования № 2 — переименование элемента
Ссылочная реализация JAXB использует аннотацию @XmlElementWrapper для переименования элемента, соответствующего свойству Map (мы добавили эту поддержку в MOXy в EclipseLink 2.4.2 и 2.5.0). В предыдущих версиях MOXy должна использоваться аннотация @XmlElement .
Клиент
Мы будем использовать аннотацию @XmlElementWrapper для переименования элемента, соответствующего свойству addressMap, в адреса .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
package blog.map;import java.util.*;import javax.xml.bind.annotation.*;@XmlRootElementpublic class Customer { private Map<String, Address> addressMap = new HashMap<String, Address>(); @XmlElementWrapper(name='addresses') public Map<String, Address> getAddressMap() { return addressMap; } public void setAddressMap(Map<String, Address> addressMap) { this.addressMap = addressMap; }} |
Выход
Теперь мы видим, что элемент addressMap был переименован в address .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<?xml version='1.0' encoding='UTF-8'?><customer> <addresses> <entry> <key>shipping</key> <value> <street>2 B Road</street> </value> </entry> <entry> <key>billing</key> <value> <street>1 A Street</street> </value> </entry> </addresses></customer> |
Вариант использования № 3 — Добавить квалификацию пространства имен
В этом сценарии использования мы рассмотрим влияние применения квалификации пространства имен к классу со свойством типа java.util.Map . Была ошибка MOXy, связанная с квалификацией пространства имен свойств Map, которая была исправлена в EclipseLink 2.4.2 и 2.5.0 (см .: http://bugs.eclipse.org/399297 ).
Пакет-инфо
Мы будем использовать аннотацию уровня пакета @XmlSchema, чтобы указать, что все поля / свойства, принадлежащие классам в этом пакете, должны быть квалифицированы с пространством имен http://www.example.com (см .: JAXB & Namespaces ).
|
1
2
3
4
5
6
|
@XmlSchema( elementFormDefault=XmlNsForm.QUALIFIED)package blog.map;import javax.xml.bind.annotation.*; |
Выход
Мы видим, что элементы, соответствующие классам Customer и Address , квалифицированы в пространстве имен, а элементы, соответствующие классу Map, — нет. Это связано с тем, что класс Map относится к пакету java.util , и поэтому информация, указанная нами на аннотации уровня @XmlSchema , неприменима.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<?xml version='1.0' encoding='UTF-8'?> <ns2:addresses> <entry> <key>shipping</key> <value> <ns2:street>2 B Road</ns2:street> </value> </entry> <entry> <key>billing</key> <value> <ns2:street>1 A Street</ns2:street> </value> </entry> </ns2:addresses></ns2:customer> |
Вариант использования № 4 — исправление квалификации пространства имен с помощью XmlAdapter
Мы можем использовать XmlAdapter для настройки квалификации пространства имен из предыдущего варианта использования.
XmlAdapter (MapAdapter)
Механизм XmlAdapter позволяет вам преобразовывать класс в другой с целью влияния на отображение (см .: XmlAdapter — Секретное оружие JAXB ). Чтобы получить соответствующую квалификацию пространства имен, мы будем использовать XmlAdapter для преобразования карты в объекты в пакете для нашей модели домена.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package blog.map;import java.util.*;import javax.xml.bind.annotation.adapters.XmlAdapter;public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, Address>> { public static class AdaptedMap { public List<Entry> entry = new ArrayList<Entry>(); } public static class Entry { public String key; public Address value; } @Override public Map<String, Address> unmarshal(AdaptedMap adaptedMap) throws Exception { Map<String, Address> map = new HashMap<String, Address>(); for(Entry entry : adaptedMap.entry) { map.put(entry.key, entry.value); } return map; } @Override public AdaptedMap marshal(Map<String, Address> map) throws Exception { AdaptedMap adaptedMap = new AdaptedMap(); for(Map.Entry<String, Address> mapEntry : map.entrySet()) { Entry entry = new Entry(); entry.key = mapEntry.getKey(); entry.value = mapEntry.getValue(); adaptedMap.entry.add(entry); } return adaptedMap; }} |
Клиент
Аннотация @XmlJavaTypeAdapter используется для указания XmlAdapter в свойстве Map . Обратите внимание, что с примененным XmlAdaper нам нужно изменить аннотацию @XmlElementWrapper на @XmlElement (свидетельство того, что @XmlElement следует использовать для аннотирования элемента для свойства Map ).
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package blog.map;import java.util.*;import javax.xml.bind.annotation.*;import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;@XmlRootElementpublic class Customer { private Map<String, Address> addressMap = new HashMap<String, Address>(); @XmlJavaTypeAdapter(MapAdapter.class) @XmlElement(name='addresses') public Map<String, Address> getAddressMap() { return addressMap; } public void setAddressMap(Map<String, Address> addressMap) { this.addressMap = addressMap; }} |
Выход
Теперь все элементы в выводе XML имеют квалификацию пространства имен http://www.example.com .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<?xml version='1.0' encoding='UTF-8'?> <addresses> <entry> <key>shipping</key> <value> <street>2 B Road</street> </value> </entry> <entry> <key>billing</key> <value> <street>1 A Street</street> </value> </entry> </addresses></customer> |
Ссылка: JAXB и java.util.Map от нашего партнера по JCG Блейза Дафана в блоге Java XML & JSON Binding .