Статьи

Удаление JAXBElement из вашей доменной модели

JAXBElement — это механизм JAXB ( JSR-222 ), который хранит информацию об имени и пространстве имен в ситуациях, когда это не может быть определено по значению или сопоставлению. Например, в классе ниже элементы billing-address и shipping-address оба соответствуют классу Address . Для того, чтобы иметь возможность передавать данные в обоих направлениях, нам необходимо отслеживать, какой элемент мы раскрыли.

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlElementRefs({
        @XmlElementRef(name = "billing-address"),
        @XmlElementRef(name = "shipping-address")
    })
    private JAXBElement<Address> address;

}

Полезный JAXBElement может помешать, если вы хотите использовать модель вашего домена с чем-то вроде JPA (который не знает, что с этим делать). В этом посте я продемонстрирую, как можно устранить необходимость в JAXBElement с помощью XmlAdapter.

Адрес


Имя элемента и информация о пространстве имен из JAXBElement должны где-то храниться. Вместо использования JAXBElement мы установим имя элемента и пространство имен в свойстве QName объекта Address (строка 10). Поскольку мы не будем отображать поле qName, оно было помечено @XmlTransient (строка 9, см.
JAXB и Unmapped Properties ).

package blog.jaxbelement.remove;

import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlAccessorType(XmlAccessType.FIELD)
public class Address {

    @XmlTransient
    private QName qName;

    private String street;

    private String city;

    public QName getQName() {
        return qName;
    }

    public void setQName(QName name) {
        this.qName = name;
    }

}

AddressAdapter


Мы будем использовать XmlAdapter для преобразования экземпляра Address в / из экземпляра JAXBElement . Во время этого преобразования мы должны переместить имя и информацию о пространстве имен между Address и JAXBElement .

package blog.jaxbelement.remove;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class AddressAdapter extends XmlAdapter<JAXBElement<Address>, Address>{

    @Override
    public JAXBElement<Address> marshal(Address address) throws Exception {
        return new JAXBElement<Address>(address.getQName(), Address.class, address); 
    }

    @Override
    public Address unmarshal(JAXBElement<Address> jaxbElement) throws Exception {
        Address address = jaxbElement.getValue();
        address.setQName(jaxbElement.getName());
        return address;
    }

}

Заказчик
@XmlJavaTypeAdapter аннотации используются для указания XMLAdapter . XMLAdapter отвечает за преобразование экземпляра адреса в JAXBElement , чтобы удовлетворить потребности @XmlElementRefs отображения. Название собственность на @XmlElementRef аннотации должна соответствовать имени , указанному в @XmlRootElement или @XmlElementDecl аннотации.


package blog.jaxbelement.remove;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlElementRefs({
        @XmlElementRef(name = "billing-address"),
        @XmlElementRef(name = "shipping-address")
    })
    @XmlJavaTypeAdapter(AddressAdapter.class)
    private Address address;

    public Address getAddress() {
        return address;
    }

}

ObjectFactory
@XmlElementDecl аннотации используются , когда класс связан с несколькими элементами (если класс связан только с одним элементом тогда @XmlRootElement может быть использовано). Он помещается в метод фабрики в классе, аннотированном @XmlRegistry (при создании из схемы XML этот класс всегда называется ObjectFactory ). Метод фабрики возвращает объект домена, завернутый в экземпляр JAXBElement. JAXBElement имеет QName , представляющее имя элементов и URI пространства имен.



package blog.jaxbelement.remove;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {

    static final String BILLING_ADDRESS = "billing-address";
    static final String SHIPPING_ADDRESS = "shipping-address";

    @XmlElementDecl(name=BILLING_ADDRESS)
    public JAXBElement<Address> createBillingAddress(Address address) {
        return new JAXBElement<Address>(new QName(BILLING_ADDRESS), Address.class, address);
    }

    @XmlElementDecl(name=SHIPPING_ADDRESS)
    public JAXBElement<Address> createShippingAddress(Address address) {
        return new JAXBElement<Address>(new QName(SHIPPING_ADDRESS), Address.class, address);
    }

}

input.xml


Ниже приведен ввод демо-кода. Обратите внимание, как адресные данные помещаются в элемент billing-address .

  Элемент billing-address был одним из имен элементов, которые мы указали в аннотации @XmlElementRef в классе Customer . В демонстрационном коде мы заменим это на элемент shipping-address , имя другого элемента, которое мы указали в аннотации @XmlElementRef .

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <billing-address>
        <street>123 A Street</street>
        <city>Any Town</city>
    </billing-address>
</customer>

Демо
В демо коде ниже мы будем:



  1. Демонстрация входного документа (строка 14)
  2. Установите новое QName для объекта Address . QName должен соответствовать одному из @XmlElementDecl аннотаций на ObjectFactory класса (строка 17).
  3. Маршал объекта Customer возвращается в XML (строка 21).
package blog.jaxbelement.remove;

import java.io.File;
import javax.xml.bind.*;
import javax.xml.namespace.QName;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class,ObjectFactory.class);

        Unmarshaller u = jc.createUnmarshaller();
        File xml = new File("src/blog/jaxbelement/remove/input.xml");
        Customer customer = (Customer) u.unmarshal(xml);

        // Change the Wrapper Element
        customer.getAddress().setQName(new QName(ObjectFactory.SHIPPING_ADDRESS));

        Marshaller m = jc.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        m.marshal(customer, System.out);
    }

}

Выходные данные

Ниже приведен результат запуска демонстрационного кода. Обратите внимание, как данные адреса теперь обернуты в элемент shipping-address .

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <shipping-address>
        <street>123 A Street</street>
        <city>Any Town</city>
    </shipping-address>
</customer>