Использование JAXB для отмены/маршалирования списка <String>
Я пытаюсь создать очень простой сервер REST. У меня просто есть метод тестирования, который вернет список строк. Здесь код:
@GET
@Path("/test2")
public List test2(){
List list=new Vector();
list.add("a");
list.add("b");
return list;
}
Он выдает следующую ошибку:
SEVERE: A message body writer for Java type,
class java.util.Vector, and MIME media type,
application/octet-stream, was not found
Я надеялся, что JAXB имеет настройку по умолчанию для простых типов, таких как String, Integer и т.д. Я думаю, нет. Вот что я себе представлял:
<Strings>
<String>a</String>
<String>b</String>
</Strings>
Какой самый простой способ заставить этот метод работать?
Ответы
Ответ 1
Я использовал пример @LiorH и расширил его до:
@XmlRootElement(name="List")
public class JaxbList<T>{
protected List<T> list;
public JaxbList(){}
public JaxbList(List<T> list){
this.list=list;
}
@XmlElement(name="Item")
public List<T> getList(){
return list;
}
}
Обратите внимание, что он использует generics, поэтому вы можете использовать его с другими классами, кроме String. Теперь код приложения просто:
@GET
@Path("/test2")
public JaxbList test2(){
List list=new Vector();
list.add("a");
list.add("b");
return new JaxbList(list);
}
Почему этот простой класс не существует в пакете JAXB? Кто-нибудь видит что-нибудь подобное в другом месте?
Ответ 2
@GET
@Path("/test2")
public Response test2(){
List<String> list=new Vector<String>();
list.add("a");
list.add("b");
final GenericEntity<List<String>> entity = new GenericEntity<List<String>>(list) { };
return Response.ok().entity(entity).build();
}
Ответ 3
В случае, если кто-то из вас хочет написать оболочку списка для списков, содержащих элементы нескольких классов, и хочет дать отдельное имя XmlElement в соответствии с типом класса без ввода классов Wrapper, вы можете использовать аннотацию @XmlMixed
.
Таким образом, JAXB называет элементы списка в соответствии со значением, заданным @XmlRootElement
.
При этом вам нужно указать, какие классы могут быть в списке, используя @XmlSeeAlso
Пример:
Возможные классы в списке
@XmlRootElement(name="user")
public class User {/*...*/}
@XmlRootElement(name="entry")
public class LogEntry {/*...*/}
Класс обертки
@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
@XmlMixed
public List<T> getRecords(){
return records;
}
}
Пример:
List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));
XStream xStream = new XStream();
String result = xStream.toXML(l);
Результат:
<records>
<user>...</user>
<entry>...</entry>
</records>
Альтернативно вы можете указать имена XmlElement непосредственно внутри класса оболочки с помощью аннотации @XmlElementRef
@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
@XmlElementRefs({
@XmlElementRef(name="item", type=Object.class),
@XmlElementRef(name="user", type=User.class),
@XmlElementRef(name="entry", type=LogEntry.class)
})
public List<T> getRecords(){
return records;
}
}
Ответ 4
Это можно сделать MUCH проще, используя замечательную библиотеку XStream. Нет оберток, никаких аннотаций.
Целевой XML
<Strings>
<String>a</String>
<String>b</String>
</Strings>
Сериализация
(String
alias можно избежать, используя строчный тег String
, но я использовал OP-код)
List <String> list = new ArrayList <String>();
list.add("a");
list.add("b");
XStream xStream = new XStream();
xStream.alias("Strings", List.class);
xStream.alias("String", String.class);
String result = xStream.toXML(list);
Десериализация
Делиализация в ArrayList
XStream xStream = new XStream();
xStream.alias("Strings", ArrayList.class);
xStream.alias("String", String.class);
xStream.addImplicitArray(ArrayList.class, "elementData");
List <String> result = (List <String>)xStream.fromXML(file);
Десалиализация в String []
XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);
Обратите внимание, что экземпляр XStream является потокобезопасным и может быть предварительно настроен, сокращая количество кода на однострочные.
XStream также может использоваться как механизм сериализации по умолчанию для службы JAX-RS. Пример подключения XStream в Джерси можно найти здесь
Ответ 5
Из личного блога post, нет необходимости создавать конкретный объект JaxbList < T >
.
Предполагая объект со списком строк:
@XmlRootElement
public class ObjectWithList {
private List<String> list;
@XmlElementWrapper(name="MyList")
@XmlElement
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
A JAXB в оба конца:
public static void simpleExample() throws JAXBException {
List<String> l = new ArrayList<String>();
l.add("Somewhere");
l.add("This and that");
l.add("Something");
// Object with list
ObjectWithList owl = new ObjectWithList();
owl.setList(l);
JAXBContext jc = JAXBContext.newInstance(ObjectWithList.class);
ObjectWithList retr = marshallUnmarshall(owl, jc);
for (String s : retr.getList()) {
System.out.println(s);
} System.out.println(" ");
}
Производит следующее:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
<MyList>
<list>Somewhere</list>
<list>This and that</list>
<list>Something</list>
</MyList>
</objectWithList>
Ответ 6
Я встречал этот шаблон несколько раз, я обнаружил, что самый простой способ - определить внутренний класс с аннотациями JaxB. (в любом случае вы, вероятно, захотите определить имя корневого тега)
чтобы ваш код выглядел примерно так.
@GET
@Path("/test2")
public Object test2(){
MyResourceWrapper wrapper = new MyResourceWrapper();
wrapper .add("a");
wrapper .add("b");
return wrapper ;
}
@XmlRootElement(name="MyResource")
private static class MyResourceWrapper {
@XmlElement(name="Item")
List<String> list=new ArrayList<String>();
MyResourceWrapper (){}
public void add(String s){ list.add(s);}
}
Если вы работаете с javax.rs(jax-rs), я бы возвращаю объект Response с установленной оболочкой как своей сущностью
Ответ 7
Пример User1 работал хорошо для меня. Но, как предупреждение, оно не будет работать ни с чем, кроме простых типов String/Integer, если вы не добавите аннотацию @XmlSeeAlso:
@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
protected List<MovieTicket> list;
Это работает нормально, хотя это мешает мне использовать один общий класс списка во всем моем приложении. Это также может объяснить, почему этот очевидный класс не существует в пакете JAXB.
Ответ 8
Наконец, я решил это с помощью JacksonJaxbJsonProvider
Он требует нескольких изменений в ваших Spring context.xml
и Maven pom.xml
В Spring context.xml
добавьте JacksonJaxbJsonProvider
в <jaxrs:server>
:
<jaxrs:server id="restService" address="/resource">
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
</jaxrs:providers>
</jaxrs:server>
В вашем Maven pom.xml добавьте:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.0</version>
</dependency>
Ответ 9
Обязательно добавьте тег @XmlSeeAlso к вашим конкретным классам, используемым внутри JaxbList. Это очень важно, иначе он выбрасывает HttpMessageNotWritableException
Ответ 10
Я бы сэкономил время, если раньше нашел Resteasy Jackson Provider.
Просто добавьте JS . Нет оберток объектов. Нет аннотаций XML. Нет собственных авторов сообщений сообщений.
Ответ 11
Если вы используете maven в проекте jersey, добавьте ниже в pom.xml и обновите зависимости проекта, чтобы Jaxb смог обнаружить класс модели и преобразовать список в приложение типа Media XML:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>