4.28.2016

Web Service - JAXB, что нужно знать, часть вторая …



В прошлой статье мы познакомились с простыми вещами, в этой усложним наши примеры, расмотрим  коллекции и так приступим.   Возьмем пример из предыдущей статьи и создадим класс Vit2:

@XmlRootElement(name = "vit2")
@XmlAccessorType(XmlAccessType.FIELD)
public class Vit2 {

    @XmlValue()
    protected String name;
    @XmlAttribute(name = "date")
    protected Date date;

    public String getName() {
        return name;
    }

    public void setName(String value) {
        this.name = value;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date value) {
        this.date = value;
    }
}

Вывод этого класса, будет таким:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vit2 date="2016-03-28T21:36:27.046+03:00">Vit</vit2>

Мы хотим получить коллекцию однотипных тегов <vit2…/> , для этого напишем класс Vit3, который будет содержать коллекцию классов Vit2, для этого нам нужно пометить аннотацией @XmlElement коллекцию List:

@XmlRootElement()
@XmlAccessorType(value = XmlAccessType.FIELD)
public class Vit3 {

    @XmlElement
    private List Vit1s;

    public List getVit1s() {
        return Vit1s;
    }

    public void setVit1s(List vit1s) {
        Vit1s = vit1s;
    }
}


Напишем главный класс MainVit1, который создает коллекцию и два элемента:

public class MainVit1 {
    static public void main(String args[]) {
        Vit3 vit3 = new Vit3();
        List v = new ArrayList();
        Vit2 vit1 = new Vit2();
        vit1.setName("Vit");
        vit1.setDate(new Date());
        v.add(vit1);
        Vit2 vit2 = new Vit2();
        vit2.setName("Vit1");
        vit2.setDate(new Date());
        v.add(vit2);
        vit3.setVit1s(v);
        try {
            File file = new File("vit.xml");
            JAXBContext jaxbContext = JAXBContext.newInstance(Vit3.class,Vit2.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);
            marshaller.marshal(vit3, file);
            marshaller.marshal(vit3, System.out);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}

В итоге получим такой вывод из двух тегов <Vit1s ../>:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vit3>
    <Vit1s xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vit2" date="2016-04-14T21:42:55.190+03:00">Vit</Vit1s>
    <Vit1s xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vit2" date="2016-04-14T21:42:55.190+03:00">Vit1</Vit1s>
</vit3>

Усложним наш пример, мне  не нравиться, как выводится тип Date, напишем Adapter который будет преобразовывать наш тип, в нужный нам формат вывода. Сделаю небольшое отступление adapter нужен для преобразования сложных типов данных в нужный нам тег, и наоборот из сложного xml тега  преобразовать в нужный нам тип данных java. В предыдущей статье я затронул тему, преобразования простых типов java в типы xml схемы <xs:schema  xmlns:xs="http://www.w3.org/2001/XMLSchema">, сдесь покажу, как преобразовывать специфические типы данных.  И так у нас есть класс XmlAdapter, и его два метода, marshal и unmarshal которые помогают преобразовывать входящий и исходящий объект в нужный нам формат данных. У класса нужно переопределить типы данных которые мы будем преобразовывать, и так он имеет такой вид XmlAdapter<Тип данных возвращаемый из xml файла - всегда String, Тип данных преобразуемый для Java>. В методах marshal и unmarshal прописываем преобразование и они имеют шаблонный вид:  Тип данных преобразуемый для Java  unmarshal(Тип данных возвращаемый из xml файла - всегда String)  и Тип данных возвращаемый из xml файла - всегда String marshal(Тип данных преобразуемый для Java) . Для того что бы все понять, напишем класс DateAdapter:

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateAdapter  extends XmlAdapter<String,Date> {
    @Override
    public Date unmarshal(String v) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.YYYY");
        return sdf.parse(v);
    }

    @Override
    public String marshal(Date v) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.YYYY");
        return sdf.format(v);
    }
}

Осталось последнее пометить тип данных, к которому будет применяться преобразование посредством нашего класса адаптера, это делается посредством аннотации @XmlJavaTypeAdapter(), и ей передается параметр наш класс адаптер. И так применим эту аннотацию к классу Vit2:

@XmlRootElement(name = "vit2")
@XmlAccessorType(XmlAccessType.FIELD)
public class Vit2 {

    @XmlValue()
    protected String name;
    @XmlAttribute(name = "date")
    @XmlJavaTypeAdapter(DateAdapter.class)
    protected Date date;
   ….
  Getter & Setter
   ….
}

В итоге получим такой вывод в консоли:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vit3>
    <Vit1s xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vit2" date="15.04.2016">Vit</Vit1s>
    <Vit1s xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vit2" date="15.04.2016">Vit1</Vit1s>
</vit3>             

Мы избавились с помощью адаптера от такого некрасивого вывода даты, пометил желтым цветом:

<Vit1s xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vit2" date="2016-04-14T21:42:55.190+03:00">Vit1</Vit1s>

Так далее мы познакомимся с аннотацией @XmlElementWrapper() которая помогает обернуть любой объект, в тег  который вы укажете в параметре name, добавим аннотацию к нашему примеру:

@XmlRootElement()
@XmlAccessorType(value = XmlAccessType.FIELD)
public class Vit3 {

    @XmlElement
    @XmlElementWrapper(name = "ListVit1")
    private List Vit1s;

    public List getVit1s() {
        return Vit1s;
    }

    public void setVit1s(List vit1s) {
        Vit1s = vit1s;
    }
}

И так получим на выходе:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vit3>
    <ListVit1>
        <Vit1s xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vit2" date="20.04.2016">Vit</Vit1s>
        <Vit1s xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="vit2" date="20.04.2016">Vit1</Vit1s>
    </ListVit1>
</vit3>

Так в прошлой статье мы генерировали java  классы, сейчас я вас познакомлю с обратным процессом, как из класса помеченного аннотациями JAXB, сгенерировать xml схему. В этом поможет нам утилита schemagen, она входит в поставку вашего JDK, давайте сгенерируем наш java  класс Vit3.java. перейдите в каталог где находится ваш исходник, наберите такую строчку в командной строке:

#/ schemagen -classpath . Vit3.java

На выходе вы получите файл schema1.xsd:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="vit3" type="vit3"/>
  <xs:complexType name="vit3">
    <xs:sequence>
      <xs:element name="ListVit1" minOccurs="0">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="Vit1s" type="xs:anyType" minOccurs="0" maxOccurs="unbounded"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

В связи с тем, что много различий между типами данных XML Schema и Java  типами, не возможно просто соотнести интерфейсы java  на типы xml схемы. Для этого придумали следующие аннотации @XmlElements, @XmlAnyElement которые помогают нам работать с java интерфейсами. И так расмотрим их применение, для этого напишем интерфейс и два класса которые будут наследоваться от него.  Напишем интерфейс и два метода которые будут  возвращать/устанавливать номер телефона:

public interface Test {
    void setTel(int Tel);
    int getTel();
}

Реализуем класс домашний телефон и унаследуем наш интефейс:

@XmlRootElement
public class homeTel implements Test {
    private int tel;

    @Override
    public void setTel(int Tel) {
        tel = Tel;
    }

    @Override
    public int getTel() {
        return tel;
    }
}

Реализуем следующий класс рабочий телефон:

@XmlRootElement
public class WorkTel implements Test  {
    private int tel;

    @Override
    public void setTel(int Tel) {
        tel = Tel;
    }

    @Override
    public int getTel() {
        return tel;
    }
}

Применим аннотацию @XmlElements к коллекции, она содержит в себе аннотации @XmlElement где вы указываете в параметре name  имя тега в какой нужно обернуть класс указанный в параметре type:

@XmlRootElement
@XmlAccessorType(value = XmlAccessType.FIELD)
public class Vit4 {

    @XmlElements({
            @XmlElement(name = "homeTel", type = homeTel.class),
            @XmlElement(name = "workTel", type = WorkTel.class)
    })
    private List<Test> contacts = new ArrayList<>();

    public List<Test> getContacts() {
        return contacts;
    }

    public void setContacts(List<Test> contacts) {
        this.contacts = contacts;
    }
}

Напишем главный класс MainVit1, который создает коллекцию и два элемента:

public class MainVit1 {
    static public void main(String args[]) {       
        Vit4 vit4 = new  Vit4();
        Test t1 = new homeTel();
        t1.setTel(12345);
        Test t2 = new WorkTel();
        t2.setTel(543678);
        List<Test> L = new ArrayList<>();
        L.add(t1);
        L.add(t2);
        vit4.setContacts(L);
        try {
            File file = new File("vit.xml");
            JAXBContext jaxbContext = JAXBContext.newInstance(Vit4.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);           
            marshaller.marshal(vit4, System.out);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}

На выходе получим такой вывод:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vit4>
    <homeTel>
        <tel>12345</tel>
    </homeTel>
    <workTel>
        <tel>543678</tel>
    </workTel>
</vit4>

Теперь давайте поработаем с аннотацией @XmlAnyElement, применим её к нашей коллекции вместо аннотации @XmlElements, напишем новый класс, эффект будет тот же, вы можете добавлять в коллекцию любой класс реализующий интерфейс Test.

@XmlRootElement
@XmlAccessorType(value = XmlAccessType.FIELD)
public class Vit5 {

    @XmlAnyElement
    private List<Test> contacts = new ArrayList<>();

    public List<Test> getContacts() {
        return contacts;
    }

    public void setContacts(List<Test> contacts) {
        this.contacts = contacts;
    }
}

Напишем главный класс MainVit1, который создает коллекцию и два элемента:

public class MainVit1 {
    static public void main(String args[]) {       
        Vit5 vit5 = new  Vit5();
        Test t1 = new homeTel();
        t1.setTel(12345);
        Test t2 = new WorkTel();
        t2.setTel(543678);
        List<Test> L = new ArrayList<>();
        L.add(t1);
        L.add(t2);
        vit5.setContacts(L);
        try {
            File file = new File("vit.xml");
            JAXBContext jaxbContext = JAXBContext.newInstance(Vit5.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);           
            marshaller.marshal(vit5, System.out);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}

На выходе получим такой вывод, практически не отличимый от предыдущего вывода:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vit5>
    <homeTel>
        <tel>12345</tel>
    </homeTel>
    <workTel>
        <tel>543678</tel>
    </workTel>
</vit5>

продолжение часть 3

Комментариев нет:

Отправить комментарий