Make a collection generic in javax.xml.bind

2019-04-10 08:14发布

问题:

In a REST server that I've written, I have several collection classes that wrap single items to be returned from my services:

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

I would like to refactor these to use generics so the the boilerplate code can be implemented in a superclass:

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> {}

How do I set the @XmlElement annotation on the superclass collection? I am thinking of something involving a @XmlJavaTypeAdapter and reflection, but was hoping for something simpler. How do I create the JAXBContext? BTW, I am using RestEasy 1.2.1 GA for the JAX-RS front-end.

UPDATE (for Andrew White): Here is code that demonstrates getting the Class object for the type parameter(s):

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

Here is the output: java.lang.String.

回答1:

Reflection Issue

The following is the reflection test that needs to be made work. I believe type erasure is what is preventing this from happening:

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

}

The above code will output what is shown below. This is because the "getCollection" method is in the context of AbstractCollection and not TestReflection. This is to ensure backwards compatibility of the Java binaries:

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

Alternative Approach

If the items in the collection will be annotated with @XmlRootElement, then you could achieve what you want to do with the following:

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

}

And assuming Person looks like the following:

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Person {

}

Then the following demo code:

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

}

Will produce:

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

For more information see:

  • http://bdoughan.blogspot.com/2010/08/using-xmlanyelement-to-build-generic.html


回答2:

I think what you are asking is not possible due to type erasure. Any generic solution would lose the "type" of the container needed for unmarshalling.