Статьи

MOXy — новый поставщик JSON-привязки по умолчанию в GlassFish 4

Теперь GlassFish 4 предлагает полную платформу Java EE 7 (JSR-342) . EclipseLink сделал несколько важных вкладов в этот выпуск. Первый обеспечивает реализацию JPA 2.1 (JSR-338) . Второе, о чем я расскажу в этом посте, — EclipseLink MOXy — теперь поставщик JSON-привязки по умолчанию для приложений JAX-RS.

RESTful Service

Обслуживание клиентов

Обычно реальный сервис будет поддерживаться JPA для выполнения операций персистентности (см. Создание веб-сервиса RESTful — часть 4/5 ). Но для этого поста я буду использовать «Hello World»

служба стиля, которая возвращает Customer на основе идентификатора в виде XML и JSON, чтобы проиллюстрировать некоторые моменты, связанные с привязкой

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
package org.example.service;
 
import javax.ejb.*;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import org.example.model.*;
 
@Stateless
@LocalBean
@Path("/customers")
public class CustomerService {
 
    @GET
    @Produces({
        MediaType.APPLICATION_XML,
        MediaType.APPLICATION_JSON
    })
    @Path("{id}")
    public Customer read(@PathParam("id") int id) {
        Customer customer = new Customer();
        customer.setId(id);
        customer.setName("Jane Doe");
 
        PhoneNumber pn = new PhoneNumber();
        pn.setType("work");
        pn.setValue("5551111");
        customer.getPhoneNumbers().add(pn);
 
        return customer;
     }
 
}

CustomerApplication

Я использовал класс Application, чтобы указать путь к сервису.

1
2
3
4
5
6
7
8
9
package org.example.service;
 
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
 
@ApplicationPath("rest/*")
public class CustomerApplication  extends Application {
 
}

Модель Java

Ниже приведена модель Java, которую мы будем использовать для этого примера. Целью является создание хороших представлений XML и JSON с использованием единого набора метаданных.

Клиент

Есть несколько интересных вещей, которые стоит отметить в классе Customer .

  1. Свойство id — это int . Поскольку JSON имеет другое представление для чисел и текста, мы посмотрим, как это значение будет представлено в выводе JSON.
  2. Свойство phoneNumbers имеет тип List <PhoneNumber> . Он был аннотирован @XmlElementWrapper для получения хорошего вывода XML. Мы рассмотрим влияние этого на вывод JSON.
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
package org.example.model;
 
import java.util.*;
import javax.xml.bind.annotation.*;
 
@XmlRootElement
public class Customer {
 
    private int id;
    private String name;
    private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @XmlElementWrapper
    @XmlElement(name="phoneNumber")
    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }
 
}

Номер телефона

В XML класс PhoneNumber отображается на сложный тип с простым содержимым (см. JAXB и Сложные типы с простым содержимым ). Это означает, что он отображается на элемент XML с атрибутами и текстом. Эти концепции XML не соответствуют непосредственно концепциям JSON, поэтому мы рассмотрим представление JSON.

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
package org.example.model;
 
import javax.xml.bind.annotation.*;
 
public class PhoneNumber {
 
    private String type;
    private String value;
 
    @XmlAttribute
    public String getType() {
        return type;
    }
 
    public void setType(String type) {
        this.type = type;
    }
 
    @XmlValue
    public String getValue() {
        return value;
    }
 
    public void setValue(String value) {
        this.value = value;
    }
 
}

Использование Сервиса

Запрос — ПОЛУЧИТЬ

Ниже приведен URL, который мы будем использовать для доступа к нашему сервису:

1
http://localhost:8080/CustomerResource/rest/customers/1

Ответ (приложение / XML)

Поскольку мы сопоставили нашу модель домена с
Метаданные JAXB (JSR-222), представленные ниже в формате XML, не являются неожиданностью.

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer id="1">
    <name>Jane Doe</name>
    <phoneNumbers>
        <phoneNumber type="work">5551111</phoneNumber>
    </phoneNumbers>
</customer>

Ответ (приложение / JSON) — GlassFish 3.1.2

Давайте взглянем на то, как выглядело представление JSON в GlassFish 3.1.2 до того, как MOXy был поставщиком JSON-привязки по умолчанию. Есть несколько вещей, на которые следует обратить внимание:

  1. Значение int для свойства id представляется в виде текста JSON.
  2. Свойство phoneNumbers типа List <PhoneNumber>
    поскольку у него был один элемент, он был представлен как объект JSON вместо массива JSON. Также, даже если этот ключ содержит коллекцию, он все еще называется phoneNumber .
  3. Ключ JSON, соответствующий свойству типа, поскольку он был аннотирован @XmlAttribute, имеет префикс @ .
  4. Ключ JSON, соответствующий свойству value, поскольку он аннотирован @XmlValue , называется $ .
01
02
03
04
05
06
07
08
09
10
{
    "id": "1",
    "name": "Jane Doe",
    "phoneNumbers": {
        "phoneNumber": {
            "@type": "work",
            "$": "5551111"
        }
    }
}

Ответ (приложение / JSON) — GlassFish 4

Используя конфигурацию по умолчанию, вы увидите, что обновление до GlassFish 4 устраняет две самые большие проблемы:

  1. Числовые значения — теперь свойство id правильно маршалируется в JSON как числовое значение. Это не уловка, MOXy основывает представление JSON на типе свойства Java. Свойство value типа String в экземпляре PhoneNumber содержит только цифры, и оно правильно отображается в JSON как текст.
  2. Коллекции размера 1 — ключ phoneNumber теперь является массивом JSON, к сожалению, он по-прежнему называется phoneNumberпокажу, как это исправить).
01
02
03
04
05
06
07
08
09
10
{
    "id": 1,
    "name": "Jane Doe",
    "phoneNumbers": {
        "phoneNumber": [
            "@type": "work",
            "$": "5551111"
        ]
    }
}

Настройка JSON-Binding

MOXy теперь можно настроить, используя механизм JAX-RS ContextResolver . Вам просто нужно вернуть экземпляр MOXyJsonConfig . Мы будем использовать это, чтобы сделать следующее:

  1. Укажите, что мы не хотим ставить префикс JSON-ключей, соответствующих свойствам, сопоставленным с @XmlAttribute .
  2. Используйте значение ключа JSON вместо $ для свойств, сопоставленных с @XmlValue .
  3. MOXyJsonConfig также может использоваться для передачи свойств маршаллеру / маршаллеру. Мы сделаем это, чтобы очистить ключ JSON для свойств коллекции (см .: Привязка к JSON и 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
package org.example.service;
 
import javax.ws.rs.ext.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
 
@Provider
public class MOXyJsonContextResolver implements ContextResolver<MoxyJsonConfig> {
 
    private final MoxyJsonConfig config;
 
    public MOXyJsonContextResolver() {
        config = new MoxyJsonConfig()
            .setAttributePrefix("")
            .setValueWrapper("value")
            .property(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);
    }
 
    @Override
    public MoxyJsonConfig getContext(Class<?> objectType) {
        return config;
    }
 
}

Новый ответ

Наконец, у нас есть JSON-представление, которым мы можем быть довольны. Он не содержит никаких артефактов, связанных с XML, даже если он был создан с теми же метаданными, что и представление XML (которое не изменилось). Все это заняло немного МОКСИ.

01
02
03
04
05
06
07
08
09
10
{
    "id": 1,
    "name": "Jane Doe",
    "phoneNumbers": [
        {
            "type": "work",
            "value": "5551111"
        }
    ]
}