Ich versuche, einen sehr einfachen REST Server zu erstellen. Ich habe nur eine Testmethode, die eine Liste von Strings zurückgibt. Hier ist der Code:
@GET
@Path("/test2")
public List test2(){
List list=new Vector();
list.add("a");
list.add("b");
return list;
}
Es gibt den folgenden Fehler:
SEVERE: Ein Nachrichtentextsteller für den Java-Typ Klasse Java.util.Vector und der MIME-Medientyp Application/octet-stream wurden nicht gefunden
Ich hoffte, JAXB hatte eine Standardeinstellung für einfache Typen wie String, Integer usw. Ich denke nicht. Folgendes habe ich mir vorgestellt:
<Strings>
<String>a</String>
<String>b</String>
</Strings>
Was ist der einfachste Weg, um diese Methode zum Laufen zu bringen?
Ich habe das Beispiel von @ LiorH verwendet und es erweitert:
@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;
}
}
Beachten Sie, dass es Generics verwendet, sodass Sie es mit anderen Klassen als String verwenden können. Nun ist der Anwendungscode einfach:
@GET
@Path("/test2")
public JaxbList test2(){
List list=new Vector();
list.add("a");
list.add("b");
return new JaxbList(list);
}
Warum gibt es diese einfache Klasse nicht im JAXB-Paket? Hat jemand anders etwas anderes gesehen?
@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();
}
Falls einer von Ihnen einen Listenwrapper für Listen schreiben möchte, der Elemente mehrerer Klassen enthält, und einen eigenen XmlElement-Namen gemäß dem Klassentyp angeben möchte, ohne Wrapper-Klassen zu schreiben, können Sie die @XmlMixed
-Annotation ..__ verwenden benennt die Elemente der Liste gemäß dem durch @XmlRootElement
..__ festgelegten Wert. Dabei müssen Sie angeben, welche Klassen möglicherweise in der Liste enthalten sind, indem Sie @XmlSeeAlso
verwenden.
Beispiel:
Mögliche Klassen in der Liste
@XmlRootElement(name="user")
public class User {/*...*/}
@XmlRootElement(name="entry")
public class LogEntry {/*...*/}
Wrapper-Klasse
@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;
}
}
Beispiel:
List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));
XStream xStream = new XStream();
String result = xStream.toXML(l);
Ergebnis:
<records>
<user>...</user>
<entry>...</entry>
</records>
Alternativ können Sie die XmlElement-Namen direkt in der Wrapper-Klasse mit der Annotation @XmlElementRef
angeben
@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;
}
}
Dies kann mit der wundervollen XStream Bibliothek SEHR VIEL einfacher gemacht werden. Keine Umschläge, keine Anmerkungen.
<Strings>
<String>a</String>
<String>b</String>
</Strings>
(String
Alias kann durch die Verwendung des string
Tags in Kleinbuchstaben vermieden werden, aber ich habe den OP-Code verwendet)
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);
Deserialisierung in 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);
Deserialisierung in String []
XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);
Beachten Sie, dass die XStream-Instanz thread-sicher ist und vorkonfiguriert werden kann, wodurch die Codemenge auf einzeilig reduziert wird.
XStream kann auch als Standard-Serialisierungsmechanismus für den JAX-RS-Dienst verwendet werden. Ein Beispiel für das Einstecken von XStream in Jersey finden Sie hier
Aus einem persönlichen Blog post ist es nicht erforderlich, ein bestimmtes JaxbList < T >
-Objekt zu erstellen.
Ein Objekt mit einer Liste von Strings annehmen:
@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;
}
}
Eine JAXB-Rundreise:
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(" ");
}
Produziert das Folgende:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
<MyList>
<list>Somewhere</list>
<list>This and that</list>
<list>Something</list>
</MyList>
</objectWithList>
Ich bin diesem Muster schon einige Male begegnet. Ich habe festgestellt, dass der einfachste Weg ist, eine innere Klasse mit JaxB-Annotationen zu definieren. (Auf jeden Fall möchten Sie wahrscheinlich den Namen des Root-Tags definieren.)
ihr Code würde also ungefähr so aussehen
@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);}
}
wenn Sie mit javax.rs (jax-rs) arbeiten, würde ich das Response-Objekt mit dem Wrapper als Entität zurückgeben
Zum Schluss habe ich es mit JacksonJaxbJsonProvider
gelöst. Es erfordert einige Änderungen in Ihrem Spring context.xml
und Maven pom.xml
_ In Ihrem Frühling context.xml
fügen Sie JacksonJaxbJsonProvider
zum <jaxrs:server>
hinzu:
<jaxrs:server id="restService" address="/resource">
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
</jaxrs:providers>
</jaxrs:server>
Fügen Sie in Ihrem Maven pom.xml Folgendes hinzu:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.0</version>
</dependency>
Das Beispiel von User1 hat für mich gut funktioniert. Als Warnung funktioniert es jedoch nur mit einfachen String-/Integer-Typen, es sei denn, Sie fügen eine @XmlSeeAlso-Anmerkung hinzu:
@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
protected List<MovieTicket> list;
Dies funktioniert in Ordnung, obwohl es mich davon abhält, eine einzige generische Listenklasse in meiner gesamten Anwendung zu verwenden. Dies könnte auch erklären, warum diese scheinbar offensichtliche Klasse im JAXB-Paket nicht vorhanden ist.
Ich hätte Zeit gespart, wenn ich Resteasy Jackson Provider früher gefunden hätte.
Fügen Sie einfach den Resteasy Jackson Provider JAR hinzu. Keine Entity-Wrapper Keine XML-Anmerkungen. Keine benutzerdefinierten Nachrichtentexter.
Wenn Sie maven im Jersey-Projekt verwenden, fügen Sie unten in pom.xml hinzu und aktualisieren Sie die Projektabhängigkeiten, sodass Jaxb die Modellklasse erkennen und die Liste in eine XML-Typanwendung konvertieren kann:
<dependency>
<groupId>com.Sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
Stellen Sie sicher, dass Sie das @XmlSeeAlso-Tag mit Ihren spezifischen Klassen in JaxbList hinzufügen. Es ist sehr wichtig, sonst wird HttpMessageNotWritableException ausgelöst
Um eine allgemeinere Lösung zu finden, lesen Sie zur JAXB-XML-Serialisierung einer Liste auf oberster Ebene, für die nur eine neue Klasse geschrieben werden muss, die in dieser Frage angegebene Lösung:
Kann man JAXB programmatisch konfigurieren?
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();
}