Статьи

JAXB и java.util.Map

Иронично ли, что может быть сложно отобразить класс 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.*;
 
@XmlRootElement
public 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.*;
 
@XmlRootElement
public 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(
    namespace='http://www.example.com',
    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:customer xmlns:ns2='http://www.example.com'>
    <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;
 
@XmlRootElement
public 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'?>
<customer xmlns='http://www.example.com'>
    <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 .