Иногда нам необходимо, чтобы в java классах тип полей отличался от типа полей описанных в xsd схеме. Например, в xsd мы имеем такое описание:
<xs:element name="date" type="xs:string"/>
Этот элемент имеет тип String, хотя реально туда передается отформатированная дата:<date>06.02.2014 13:02:50</date>
В результате генерации классов java мы получим поле date типа String:@XmlElement(name="date")
private String date;
public void setDate(String date){
this.date = date;
}
public String getDate(){
return date;
}
Но что, если нам необходимо чтобы поле date имело тип java.utl.Date?Для решения этого класса задач в java есть аннотация @XmlJavaTypeAdapter. Эта аннотация может быть применена для полей, методов, параметров, классов (интерфейсов, пречислений) и пакетов. У данной аннотации есть 2 атрибута:
- value - указывает на класс адаптера который должен раcширять XmlAdapter. В нем и будет реализована лоигика конвертации типов. Этот атрибут является обязательным;
- type - является обязательным, если аннотация применена на пакет. Указывает на класс который мы хотим преобразовать. Например если мы хотим преобразовать все поля типа XMLGregorianCalendar в поля типа Date, то в качестве значения атрибута type, мы должны передать туда XMLGregorianCalendar.class;
<?xml version="1.0" encoding="UTF-8"?>
<film>
<title>The Great Dictator</title>
<description>
Dictator Adenoid Hynkel tries to expand his empire while a
poor Jewish barber tries to avoid persecution from Hynkel's regime.
</description>
<releaseDate>07.03.1941</releaseDate>
<actors>
<actor>
<name>Charles</name>
<surname>Chaplin</surname>
<character>
Hynkel - Dictator of Tomania / A Jewish Barber
</character>
</actor>
<actor>
<name>Jack</name>
<surname>Oakie</surname>
<character>Napaloni - Dictator of Bacteria</character>
</actor>
<actor>
<name>Reginald</name>
<surname>Gardiner</surname>
<character>Schultz</character>
</actor>
<actor>
<name>Henry</name>
<surname>Daniell</surname>
<character>Garbitsch</character>
</actor>
<actor>
<name>Billy</name>
<surname>Gilbert</surname>
<character>Herring</character>
</actor>
</actors>
</film>
И набор классов с xml аннотациями:Actor.class
package com.vinichenkosa.javaxmladapter.models;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
@XmlAccessorType(XmlAccessType.NONE)
public class Actor {
@XmlElement(name = "name")
private String name;
@XmlElement(name = "surname")
private String surname;
@XmlElement(name = "character")
private String character;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getCharacter() {
return character;
}
public void setCharacter(String character) {
this.character = character;
}
@Override
public String toString() {
return "Actor{" + "name=" + name + ", surname=" + surname +
", character=" + character + '}';
}
}
Film.classpackage com.vinichenkosa.javaxmladapter.models;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="film")
@XmlAccessorType(XmlAccessType.NONE)
public class Film {
@XmlElement(name = "title")
private String title;
@XmlElement(name = "description")
private String description;
@XmlElement(name = "releaseDate")
private String releaseDate;
@XmlElementWrapper(name="actors")
@XmlElement(name="actor")
private List<Actor> actors;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getReleaseDate() {
return releaseDate;
}
public void setReleaseDate(String releaseDate) {
this.releaseDate = releaseDate;
}
public List<Actor> getActors() {
if(actors == null){
actors = new ArrayList();
}
return actors;
}
@Override
public String toString() {
return "Film{" + "title=" + title + ", description=" +
description + ", releaseDate=" + releaseDate + ", actors=" +
actors.toString() + '}';
}
}
XmlUtils.classpackage com.vinichenkosa.javaxmladapter.utils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class XmlUtils {
@SuppressWarnings("unchecked")
public static <T> T unmarshall(InputStream xml, Class<T> cl)
throws JAXBException{
JAXBContext jaxbCtx = JAXBContext.newInstance(cl);
Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
return (T) unmarshaller.unmarshal(xml);
}
}
App.classpublic class App
{
public static void main( String[] args ) throws JAXBException
{
InputStream xml = App.class.getResourceAsStream("/films.xml");
Film film = XmlUtils.unmarshall(xml, Film.class);
System.out.println(film.toString());
}
}
Если мы запустить сейчас приложение, то мы увидим:Film{
title=The Great Dictator,
description=Dictator Adenoid Hynkel tries to expand his ...,
releaseDate=07.03.1941,
actors=[
Actor{name=Charles, surname=Chaplin, character=Hynkel ...},
Actor{name=Jack, surname=Oakie, character=Napaloni ...},
Actor{name=Reginald, surname=Gardiner, character=Schultz},
Actor{name=Henry, surname=Daniell, character=Garbitsch},
Actor{name=Billy, surname=Gilbert, character=Herring}
]
}
Все хорошо, но что если мы хотим чтобы поле releaseDate имело тип java.util.Date? Без проблем. Создадим адаптер, который будет преобразовывать строку определенного формата в дату:StringToDateAdapter.class:
package com.vinichenkosa.javaxmladapter.adapters;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringToDateAdapter extends XmlAdapter<String, Date> {
private final SimpleDateFormat dateFormatter;
public StringToDateAdapter() {
this.dateFormatter = new SimpleDateFormat("dd.MM.yyyy");
}
@Override
public Date unmarshal(String dateAsString) throws Exception {
return dateFormatter.parse(dateAsString);
}
@Override
public String marshal(Date date) throws Exception {
return dateFormatter.format(date);
}
}
Добавим аннотацию @XmlJavaTypeAdapter к полю releaseDate и изменим тип поля на Date:@XmlJavaTypeAdapter(StringToDateAdapter.class)
@XmlElement(name = "releaseDate")
private Date releaseDate;
Запустим приложение: Film{
title=The Great Dictator,
description=Dictator Adenoid Hynkel tries to expand his ...,
release date=Fri Mar 07 00:00:00 MSK 1941
actors=[
Actor{name=Charles, surname=Chaplin, character=Hynkel ...},
Actor{name=Jack, surname=Oakie, character=Napaloni ...},
Actor{name=Reginald, surname=Gardiner, character=Schultz},
Actor{name=Henry, surname=Daniell, character=Garbitsch},
Actor{name=Billy, surname=Gilbert, character=Herring}
]
}
Из вывода видно, что теперь у нас полноценная дата вместо строки. Круто. Но что если у нас много полей, которые надо преобразовать к определенному типу?Давайте посмотрим что делает адаптер на уровне пакета. Наш адапетр будет переводить все строки в верхний регистр. На самом деле тип тут не меняется, но это не сложно сделать по аналогии с первым примером. Создадим файл адаптера и package-info.java:
UppercaseAdapter.java:
package com.vinichenkosa.javaxmladapter.adapters;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class UppercaseAdapter extends XmlAdapter<String, String>{
@Override
public String unmarshal(String v) throws Exception {
return v.toUpperCase();
}
@Override
public String marshal(String v) throws Exception {
return v;
}
}
package-info.java
@XmlJavaTypeAdapter(
value = UppercaseAdapter.class, type = String.class
)
package com.vinichenkosa.javaxmladapter.models;
import com.vinichenkosa.javaxmladapter.adapters.UppercaseAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
Как видно мы воспользовались аннотацией @XmlJavaTypeAdapter для нашего пакета. Но что если надо определить несколько адаптеров для пакета? Для этого можно воспользоваться аннотацией @XmlJavaTypeAdapters(@XmlJavaTypeAdapter()[]), которая позволяет объединить несколько адапетров. Например, мы можем перенести наш адаптер StringToDate с поля releaseDate на уровень пакета и убедиться, что он по прежнему работает:@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(
value = UppercaseAdapter.class,
type = String.class
),
@XmlJavaTypeAdapter(
value = StringToDateAdapter.class,
type = Date.class
)
})
package com.vinichenkosa.javaxmladapter.models;
import com.vinichenkosa.javaxmladapter.adapters.StringToDateAdapter;
import com.vinichenkosa.javaxmladapter.adapters.UppercaseAdapter;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
Запустим программу:Film{
title=THE GREAT DICTATOR,
description=DICTATOR ADENOID HYNKEL TRIES TO EXPAND HIS ...,
releaseDate=Fri Mar 07 00:00:00 MSK 1941,
actors=[
Actor{name=CHARLES, surname=CHAPLIN, character=HYNKEL ...},
Actor{name=JACK, surname=OAKIE, character=NAPALONI ...},
Actor{name=REGINALD, surname=GARDINER, character=SCHULTZ},
Actor{name=HENRY, surname=DANIELL, character=GARBITSCH},
Actor{name=BILLY, surname=GILBERT, character=HERRING}
]
}
Как видно, значение всех полей имевщих тип String, было приведено к верхнему регистру, а значение поля releaseDate не изменилось.Добавим данные о составе команды, принимавшей участие в съемках фильма:
<film>
…
<crew>
<member>Carter DeHaven</member>
<member>Meredith Willson</member>
<member>Karl Struss</member>
<member>>Roland Totheroh</member>
<member>Willard Nico</member>
<member>>Harold Rice</member>
</crew>
</film>
Как видите это просо набор строк, но нам надо преобразовать их в объекты типа Member. Для этого создадим класс Member с адапетром уровня класса. Адаптер будет вызываться для всех полей имеющих тип Member:
package com.vinichenkosa.javaxmladapter.models;
import com.vinichenkosa.javaxmladapter.adapters.CrewMemberAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlJavaTypeAdapter(CrewMemberAdapter.class)
public class Member {
private String name;
private String surname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
@Override
public String toString() {
return "Member{" + "name=" + name + ", surname=" + surname + '}';
}
}
В самом адаптере мы будем принимать строку и создавать объект Member:package com.vinichenkosa.javaxmladapter.adapters;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import com.vinichenkosa.javaxmladapter.models.Member;
public class CrewMemberAdapter extends XmlAdapter<String, Member> {
@Override
public Member unmarshal(String v) throws Exception {
Member member = new Member();
String[] a = v.split(" ");
member.setName(a[0]);
member.setSurname(a[1]);
return member;
}
@Override
public String marshal(Member v) throws Exception {
return v.getName()+" "+v.getSurname();
}
}
В результате мы должны из строк, получить объекты типа Member. Проверим:Film{
title=THE GREAT DICTATOR,
description=DICTATOR ADENOID HYNKEL TRIES TO EXPAND HIS ...,
releaseDate=Fri Mar 07 00:00:00 MSK 1941,
actors=[...],
crew=[
Member{name=Carter, surname=DeHaven},
Member{name=Meredith, surname=Willson},
Member{name=Karl, surname=Struss},
Member{name=Roland, surname=Totheroh},
Member{name=Willard, surname=Nico},
Member{name=Harold, surname=Rice}
]
}
Адапетры могут быть написаны не только для простых типов, но и для сложных. Вообще xml элемент вначале преобразуется в экземпляр класса по умолчанию, а потом уже передается в метод unmarshall адапетра. Например, мы можем написать адапетр для класса Actor, чтобы каждый раз когда создается экземпляр этого класса вызывался метод адапетра. package com.vinichenkosa.javaxmladapter.adapters;
import com.vinichenkosa.javaxmladapter.models.Actor;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class ActorAdapter extends XmlAdapter<Actor, Actor>{
@Override
public Actor unmarshal(Actor v) throws Exception {
v.setFullname(v.getName() + " " + v.getSurname());
return v;
}
@Override
public Actor marshal(Actor v) throws Exception {
return v;
}
}
Как видите этот адапетр принимает уже созданный и заполненный данными объект типа Actor, заполняет в нем поле fullName на основаии данных из xml, которого в самом xml нет и возвращает его. Интересно, а можно ли принимать объект одного типа, а возвращать в класс совсем другого? Можно, но об этом в другой раз :)))Код на GitHub: https://github.com/winechess/JavaXmlAdapter
Комментариев нет:
Отправить комментарий