【问题标题】:Make a collection generic in javax.xml.bind在 javax.xml.bind 中创建一个通用集合
【发布时间】:2011-03-22 12:49:47
【问题描述】:

在我编写的 REST 服务器中,我有几个集合类,用于包装要从我的服务返回的单个项目:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "person_collection")
public final class PersonCollection {
    @XmlElement(name = "person")
    protected final List<Person> collection = new ArrayList<Person>();

    public List<Person> getCollection() {
        return collection;
    }
}

我想重构这些以使用泛型,以便可以在超类中实现样板代码:

public abstract class AbstractCollection<T> {
    protected final List<T> collection = new ArrayList<T>();

    public List<T> getCollection() {
        return collection;
    }
}

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "person_collection")
public final class PersonCollection extends AbstractCollection<Person> {}

如何在超类集合上设置@XmlElement 注解?我正在考虑涉及@XmlJavaTypeAdapter 和反射的东西,但希望有更简单的东西。如何创建JAXBContext?顺便说一句,我在 JAX-RS 前端使用 RestEasy 1.2.1 GA。

更新(对于 Andrew White):以下代码演示了获取类型参数的 Class 对象:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;

public class TestReflection
        extends AbstractCollection<String> {
    public static void main(final String[] args) {
        final TestReflection testReflection = new TestReflection();

        final Class<?> cls = testReflection.getClass();
        final Type[] types = ((ParameterizedType) cls.getGenericSuperclass()).getActualTypeArguments();
        for (final Type t : types) {
            final Class<?> typeVariable = (Class<?>) t;
            System.out.println(typeVariable.getCanonicalName());
        }
    }
}

class AbstractCollection<T> {
    protected List<T> collection = new ArrayList<T>();
}

这是输出:java.lang.String

【问题讨论】:

  • 您不必在@XmlElement 上指定name 属性,因此您只需将@XmlElement 添加到AbstractCollection.collection,并让JAXB 推断元素名称。
  • @skaffman:它不工作。我收到了javax.xml.bind.JAXBException: class com.example.Person nor any of its super class is known to this context
  • 嗯,这是您创建 JAXB 上下文的方式的错误。将其添加到您的问题中,我会发布答案。
  • @skaffman:已添加。见上文:-)。
  • +1 好问题 - 目前正在使用 EclipseLink JAXB (MOXy) 调试此问题。

标签: java jaxb jax-rs resteasy


【解决方案1】:

反射问题

以下是需要进行的反射测试。我相信类型擦除是防止这种情况发生的原因:

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class TestReflection extends AbstractCollection<String> {

    private List<Integer> childCollection = new ArrayList<Integer>();


    public List<Integer> getChildCollection() {
        return childCollection;
    }

    public static void main(final String[] args) throws Exception {
        final TestReflection testReflection = new TestReflection();

        final Class<?> cls = testReflection.getClass();
        Method method1 = cls.getMethod("getChildCollection", new Class[] {});
        System.out.println(method1.getGenericReturnType());

        Method method2 = cls.getMethod("getCollection", new Class[] {});
        System.out.println(method2.getGenericReturnType());
   }

}

上面的代码将输出如下所示的内容。这是因为“getCollection”方法是在 AbstractCollection 而不是 TestReflection 的上下文中。这是为了确保 Java 二进制文件的向后兼容性:

java.util.List<java.lang.Integer>
java.util.List<T>

替代方法

如果集合中的项目将使用@XmlRootElement 进行注释,那么您可以通过以下方式实现您想要做的事情:

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAnyElement;

public abstract class AbstractCollection<T> {

    protected List<T> collection = new ArrayList<T>();

    @XmlAnyElement(lax=true)
    public List<T> getCollection() {
        return collection;
    }

}

假设 Person 如下所示:

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Person {

}

然后是下面的演示代码:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(PersonCollection.class, Person.class);

        PersonCollection pc = new PersonCollection();
        pc.getCollection().add(new Person());
        pc.getCollection().add(new Person());

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(pc, System.out);
    }

}

将产生:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person_collection>
    <person/>
    <person/>
</person_collection>

欲了解更多信息,请参阅:

【讨论】:

  • 在这种情况下,我们不能只从子类(&lt;String&gt;)中读取泛型类型吗?我找不到不涉及 XML 模式的关于 JAXB 的好教程。我手动编码了我的类,需要对它们进行注释,以便它们正确编组/解组。如果我不重构为抽象超类,它会运行良好,但我不喜欢生成的样板代码量。
  • @Ralph - 诀窍是知道子类中的 可以应用于列表(我希望有办法)。我在我的答案中添加了一种可能对您有用的替代方法。我的博客是从类开始使用 JAXB 的一个很好的例子:bdoughan.blogspot.com
  • 您的编辑看起来很准确。我将不得不玩它。我会看看你的博客。
  • @Ralph - 您可能会发现以下文章很有用:bdoughan.blogspot.com/2010/08/…
【解决方案2】:

认为由于类型擦除,您的要求是不可能的。任何通用解决方案都会丢失解组所需的容器“类型”。

【讨论】:

  • 我相信我可以使用反射(如果需要)获得类型信息。方法java.lang.reflect.Field.getGenericType() 返回一个Type 对象(可能是TypeVariable 的实例),该对象可用于检索集合的实际类型。在最坏的情况下,我可以让每个子类构造函数将其“子”类型的单个 Class&lt;?&gt; 参数传递给 AbstractCollection
  • 我希望你是对的,但我认为那里有一个限制;老实说,我记不起反射和泛型的警告,只有一些存在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-12
  • 1970-01-01
  • 1970-01-01
  • 2021-08-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多