framework - restful services java example
Usar JAXB para unificar/ordenar una lista<String> (12)
Asegúrese de agregar la etiqueta @XmlSeeAlso con sus clases específicas utilizadas dentro de JaxbList. Es muy importante que arroje HttpMessageNotWritableException
Estoy tratando de crear un servidor REST muy simple. Solo tengo un método de prueba que devolverá una lista de cadenas. Aquí está el código:
@GET
@Path("/test2")
public List test2(){
List list=new Vector();
list.add("a");
list.add("b");
return list;
}
Da el siguiente error:
SEVERE: A message body writer for Java type, class java.util.Vector, and MIME media type, application/octet-stream, was not found
Esperaba que JAXB tuviera una configuración predeterminada para tipos simples como String, Integer, etc. Supongo que no. Esto es lo que imaginé:
<Strings>
<String>a</String>
<String>b</String>
</Strings>
¿Cuál es la forma más fácil de hacer que este método funcione?
Desde una publicación de blog personal, no es necesario crear un objeto específico JaxbList < T >
.
Asumiendo un objeto con una lista de cadenas:
@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;
}
}
Un viaje redondo de 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(" ");
}
Produce lo siguiente:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
<MyList>
<list>Somewhere</list>
<list>This and that</list>
<list>Something</list>
</MyList>
</objectWithList>
El ejemplo de User1 funcionó bien para mí. Pero, como advertencia, no funcionará con nada más que con los tipos String / Integer simples, a menos que agregue una anotación @XmlSeeAlso:
@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
protected List<MovieTicket> list;
Esto funciona bien, aunque me impide usar una sola clase de lista genérica en toda mi aplicación. También podría explicar por qué esta clase aparentemente obvia no existe en el paquete JAXB.
En caso de que alguno de ustedes quiera escribir un contenedor de listas para listas que contengan elementos de múltiples clases y quieran dar un nombre de XmlElement individual según el tipo de Clase sin escribir clases X Wrapper, podría usar la anotación @XmlMixed
. Al hacerlo, JAXB nombra los elementos de la lista según el valor establecido por @XmlRootElement
. Al hacerlo, debe especificar qué clases posiblemente podrían estar en la lista usando @XmlSeeAlso
Ejemplo:
Posibles clases en la lista
@XmlRootElement(name="user")
public class User {/*...*/}
@XmlRootElement(name="entry")
public class LogEntry {/*...*/}
Clase de envoltura
@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;
}
}
Ejemplo:
List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));
XStream xStream = new XStream();
String result = xStream.toXML(l);
Resultado:
<records>
<user>...</user>
<entry>...</entry>
</records>
Alternativamente, podría especificar los nombres de XmlElement directamente dentro de la clase contenedora utilizando la anotación @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;
}
}
Esto se puede hacer MUCHO más fácil usando la maravillosa biblioteca XStream . Sin envoltorios, sin anotaciones.
Target XML
<Strings>
<String>a</String>
<String>b</String>
</Strings>
Publicación por entregas
(Se puede evitar el alias de String
usando string
etiqueta de string
minúscula, pero utilicé el código de 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);
Deserialización
Deserialización en 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);
Deserialización en cadena []
XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);
Tenga en cuenta que la instancia de XStream es segura para subprocesos y se puede preconfigurar, reduciendo el monto del código a líneas únicas.
XStream también se puede usar como un mecanismo de serialización predeterminado para el servicio JAX-RS. Ejemplo de conectar XStream en Jersey se puede encontrar here
Finalmente lo he solucionado usando JacksonJaxbJsonProvider
Requiere pocos cambios en Spring context.xml
y Maven pom.xml
En Spring context.xml
agregue JacksonJaxbJsonProvider
a <jaxrs:server>
:
<jaxrs:server id="restService" address="/resource">
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
</jaxrs:providers>
</jaxrs:server>
En su Maven pom.xml, agregue:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.0</version>
</dependency>
He encontrado este patrón varias veces, encontré que la manera más fácil es definir una clase interna con anotaciones JaxB. (De todos modos, es probable que desee definir el nombre de la etiqueta raíz)
para que tu código se vea algo así
@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);}
}
si trabajas con javax.rs (jax-rs) devolvería el objeto Response con el conjunto contenedor como su entidad
Hubiera ahorrado tiempo si hubiera encontrado a Resteasy Jackson Provider antes.
Solo agregue el proveedor JAR de Resteasy Jackson . Sin envoltorios de entidades. Sin anotaciones XML. No hay escritores de cuerpo de mensaje personalizados.
Para una solución más general, para la serialización de JAXB-XML de cualquier lista de nivel superior, que solo requiere la escritura de una nueva clase, revise la solución dada en esta pregunta:
¿Es posible configurar JAXB programáticamente?
public class Wrapper<T> {
private List<T> items = new ArrayList<T>();
@XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
//JAXBContext is thread safe and so create it in constructor or
//setter or wherever:
...
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
...
public String marshal(List<T> things, Class clazz) {
//configure JAXB and marshaller
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create wrapper based on generic list of objects
Wrapper<T> wrapper = new Wrapper<T>(things);
JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);
StringWriter result = new StringWriter();
//marshal!
m.marshal(wrapperJAXBElement, result);
return result.toString();
}
Si está usando maven en el proyecto de jersey, agregue a continuación en pom.xml y actualice las dependencias del proyecto para que Jaxb pueda detectar la clase de modelo y la lista de conversión a XML de aplicación de tipo de medio:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
Usé el ejemplo de @LiorH y lo expandí a:
@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;
}
}
Tenga en cuenta que utiliza genéricos para que pueda usarlo con otras clases aparte de String. Ahora, el código de la aplicación es simplemente:
@GET
@Path("/test2")
public JaxbList test2(){
List list=new Vector();
list.add("a");
list.add("b");
return new JaxbList(list);
}
¿Por qué no existe esta clase simple en el paquete JAXB? ¿Alguien ve algo así en otro lado?
@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();
}