简体   繁体   English

使用 JAXB 解组/编组列表<String>

[英]Using JAXB to unmarshal/marshal a List<String>

I'm trying to create a very simple REST server.我正在尝试创建一个非常简单的 REST 服务器。 I just have a test method that will return a List of Strings.我只有一个返回字符串列表的测试方法。 Here's the code:这是代码:


@GET
@Path("/test2")
public List test2(){
    List list=new Vector();
    list.add("a");
    list.add("b");
    return list;
}

It gives the following error:它给出了以下错误:

SEVERE: A message body writer for Java type,
class java.util.Vector, and MIME media type,
application/octet-stream, was not found

I was hoping JAXB had a default setting for simple types like String, Integer, etc. I guess not.我希望 JAXB 对 String、Integer 等简单类型有一个默认设置。我猜不是。 Here's what I imagined:这是我想象的:


<Strings>
  <String>a</String>
  <String>b</String>
</Strings>

What's the easiest way to make this method work?使这种方法起作用的最简单方法是什么?

I used @LiorH's example and expanded it to:我使用了@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;
    }
}

Note, that it uses generics so you can use it with other classes than String.请注意,它使用泛型,因此您可以将它与 String 之外的其他类一起使用。 Now, the application code is simply:现在,应用程序代码很简单:


    @GET
    @Path("/test2")
    public JaxbList test2(){
        List list=new Vector();
        list.add("a");
        list.add("b");
        return new JaxbList(list);
    }

Why doesn't this simple class exist in the JAXB package?为什么 JAXB 包中不存在这个简单的类? Anyone see anything like it elsewhere?有人在其他地方看到类似的东西吗?

@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();
}

In case anyone of you wants to write a list wrapper for lists containing elements of multiple classes and want to give an individual XmlElement name according to the Class type without Writing X Wrapper classes you could use the @XmlMixed annotation.如果你们中的任何人想要为包含多个类的元素的列表编写一个列表包装器,并且想要根据 Class 类型给出一个单独的 XmlElement 名称而不编写 X Wrapper 类,则可以使用@XmlMixed注释。 By doing so JAXB names the items of the list according to the value set by the @XmlRootElement .通过这样做,JAXB 根​​据@XmlRootElement设置的值命名列表中的项目。 When doing so you have to specify which classes could possibly be in the list using @XmlSeeAlso这样做时,您必须使用@XmlSeeAlso指定哪些类可能在列表中

Example:例子:

Possible Classes in the list列表中可能的类

@XmlRootElement(name="user")
public class User {/*...*/}

@XmlRootElement(name="entry")
public class LogEntry {/*...*/}

Wrapper class包装类

@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;
    }
}

Example:例子:

List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));


XStream xStream = new XStream();
String result = xStream.toXML(l);

Result:结果:

<records>
    <user>...</user>
    <entry>...</entry>
</records>

Alternatevily you could specify the XmlElement names directly inside the wrapper class using the @XmlElementRef annotation或者,您可以使用@XmlElementRef注释直接在包装类中指定 XmlElement 名称

@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;
    }
}

From a personal blog post , it is not necessary to create a specific JaxbList < T > object.从个人博客文章来看,没有必要创建特定的JaxbList < T >对象。

Assuming an object with a list of strings:假设一个带有字符串列表的对象:

@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 round trip: 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(" ");

}

Produces the following:产生以下内容:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
    <MyList>
        <list>Somewhere</list>
        <list>This and that</list>
        <list>Something</list>
    </MyList>
</objectWithList>

This can be done MUCH easier using wonderful XStream library.使用出色的XStream库可以更轻松地完成此操作 No wrappers, no annotations.没有包装,没有注释。

Target XML目标 XML

<Strings>
  <String>a</String>
  <String>b</String>
</Strings>

Serialization序列化

( String alias can be avoided by using lowercase string tag, but I used OP's code) String别名可以通过使用小写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);

Deserialization反序列化

Deserialization into ArrayList反序列化为 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);

Deserialization into String[]反序列化为 String[]

XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);

Note, that XStream instance is thread-safe and can be pre-configured, shrinking code amount to one-liners.请注意,XStream 实例是线程安全的,可以预先配置,将代码量缩减为单行。

XStream can also be used as a default serialization mechanism for JAX-RS service. XStream 也可以用作 JAX-RS 服务的默认序列化机制。 Example of plugging XStream in Jersey can be found here在泽西岛插入 XStream 的示例可以在这里找到

I have encountered this pattern a few times, I found that the easiest way is to define an inner class with JaxB annotations.我遇到过几次这种模式,我发现最简单的方法是使用 JaxB 注释定义一个内部类。 (anyways, you'll probably want to define the root tag name) (无论如何,您可能想要定义根标签名称)

so your code would look something like this所以你的代码看起来像这样

@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);}
 }

if you work with javax.rs (jax-rs) I'd return Response object with the wrapper set as its entity如果您使用 javax.rs (jax-rs),我将返回 Response 对象,并将包装器设置为其实体

Finally I've solved it using JacksonJaxbJsonProvider It requires few changes in your Spring context.xml and Maven pom.xml最后,我使用JacksonJaxbJsonProvider解决了它需要在 Spring context.xml和 Maven pom.xml中进行一些更改

In your Spring context.xml add JacksonJaxbJsonProvider to the <jaxrs:server> :在您的 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>

In your Maven pom.xml add:在你的 Maven pom.xml 添加:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.0</version>
</dependency>

User1's example worked well for me. User1 的示例对我来说效果很好。 But, as a warning, it won't work with anything other than simple String/Integer types, unless you add an @XmlSeeAlso annotation:但是,作为警告,除了简单的 String/Integer 类型之外,它不能用于任何其他类型,除非您添加 @XmlSeeAlso 注释:

@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
    protected List<MovieTicket> list;

This works OK, although it prevents me from using a single generic list class across my entire application.这可以正常工作,尽管它阻止我在整个应用程序中使用单个通用列表类。 It might also explain why this seemingly obvious class doesn't exist in the JAXB package.这也可以解释为什么 JAXB 包中不存在这个看似显而易见的类。

Make sure to add @XmlSeeAlso tag with your specific classes used inside JaxbList.确保使用 JaxbList 中使用的特定类添加 @XmlSeeAlso 标记。 It is very important else it throws HttpMessageNotWritableException这很重要,否则它会抛出 HttpMessageNotWritableException

I would've saved time if I found Resteasy Jackson Provider sooner.如果我早点找到Resteasy Jackson Provider ,我会节省时间。

Just add the Resteasy Jackson Provider JAR .只需添加Resteasy Jackson Provider JAR No entity wrappers.没有实体包装器。 No XML annotations.没有 XML 注释。 No custom message body writers.没有自定义消息正文作者。

If you are using maven in the jersey project add below in pom.xml and update project dependencies so that Jaxb is able to detect model class and convert list to Media type application XML:如果您在 jersey 项目中使用 maven,请在 pom.xml 中添加以下内容并更新项目依赖项,以便 Jaxb 能够检测模型类并将列表转换为媒体类型应用程序 XML:

<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.2.11</version>
</dependency>

For a more general solution, for JAXB-XML serialization of any top level list , which only requires 1 new class to be written, check out the solution given in this question:对于更通用的解决方案,对于任何顶级列表的 JAXB-XML 序列化,只需要编写 1 个新类,请查看此问题中给出的解决方案:

Is it possible to programmatically configure JAXB? 是否可以以编程方式配置 JAXB?

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();

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM