如何使JAXB实例化泛型超类的子类列表元素

时间:2019-07-08 19:29:35

标签: java list generics inheritance jaxb

我正在尝试使用JAXB将通用超类ParentListElement的一个子类ChildListElement的多次出现变成列表。问题在于,JAXB取消了对超类而不是子类的编组。如何在不锁定一个子类ChildListElement的情况下让JAXB解组到ChildListElement而不是ParentListElement?

感谢您的时间和协助。 :-)

这些是文件/类:

main.java
ParentChildTests.java
ParentChildFactory.java
childBucket.xml
ParentBucket.java
ChildBucket.java
ParentListElement.java
ChildListElement.java

只有在以下情况下,才能在ParentBucket中使用包装器和元素注释: 指定了type=ChildListElement.class。但这违反了具有泛型超类的目的,因为一个子类ChildListElement被锁定。我希望能够拥有ParentListElement的多个子类。每个列表一次仅包含一种子类。

Works-解组到ChildListElement,但将一个子类锁定:

@XmlElementWrapper( name = "elements" )
@XmlElements( { @XmlElement( name="element", type=ChildListElement.class ) } )

不起作用-取消编组到ParentListElement:

@XmlElementWrapper( name = "elements" )
@XmlElements( { @XmlElement( name="element" ) } )

main.java:

import GenListVsJaxbTests.ParentChildTests;
import javax.xml.bind.JAXBException;

public class Main
{
    public static void main(String[] args) throws JAXBException
    {
        ParentChildTests.testChildBucket();
        ParentChildTests.testChildBucketFromXml();
    }
}

ParentChildTests.java:

package GenListVsJaxbTests;
import javax.xml.bind.JAXBException;

public class ParentChildTests
{
    public static void testChildBucket()
    {
        ChildBucket bucket = ParentChildFactory.getNewChildBucket();
        bucket.test();
    }

    public static void testChildBucketFromXml() throws JAXBException
    {
        ChildBucket bucket = ParentChildFactory.loadNewChildBucketFromXml ( "src/GenListVsJaxbTests/ChildBucket.xml" );
        bucket.test();
    }
}

ParentChildFactory.java:

package GenListVsJaxbTests;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ParentChildFactory
{
    private static int MAX_ELEMENTS = 3;

    public static ChildBucket getNewChildBucket()
    {
        ChildBucket childBucket = new ChildBucket();
        List<ChildListElement> list = new ArrayList<>();

        for ( int i = 0; i < MAX_ELEMENTS; i ++ )
        {
            ChildListElement el = new ChildListElement();
            el.setParentListElMember ( String.valueOf ( i ) );
            el.setChildListElMember ( String.valueOf ( i + 10 ) );
            list.add ( el );
        }
        childBucket.setElementList ( list );
        return childBucket;
    }

    public static ChildBucket loadNewChildBucketFromXml ( String fileName ) throws JAXBException
    {
        File inFile = new File( fileName );
        JAXBContext jaxbContext = JAXBContext.newInstance ( ChildBucket.class );
        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
        ChildBucket bucket = (ChildBucket) jaxbUnmarshaller.unmarshal(inFile);
        return bucket;
    }
}

childBucket.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<childBucket
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation='ChildBucket.xsd'
        >
    <childBucketMember>child bucket</childBucketMember>
    <parentBucketMember>parent bucket</parentBucketMember>
    <elements>
        <element>
            <childListElMember>child element 1</childListElMember>
            <parentListElMember>parent element 1</parentListElMember>
        </element>
        <element>
            <childListElMember>child element 2</childListElMember>
            <parentListElMember>parent element 2</parentListElMember>
        </element>
        <element>
            <childListElMember>child element 3</childListElMember>
            <parentListElMember>parent element 3</parentListElMember>
        </element>
        <element>
            <childListElMember>child element 4</childListElMember>
            <parentListElMember>parent element 4</parentListElMember>
        </element>
    </elements>
</childBucket>

ParentBucket.java:

package GenListVsJaxbTests;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import java.util.List;

public class ParentBucket<LE extends ParentListElement>
{
    protected String parentBucketMember = "parentBucketMember";
    List<LE> elementList;


    public String getParentBucketMember()
    {
        return parentBucketMember;
    }

    public void setParentBucketMember(String parentBucketMember)
    {
        this.parentBucketMember = parentBucketMember;
    }

    public List<LE> getElementList()
    {
        return elementList;
    }

    @XmlElementWrapper( name = "elements" )
    @XmlElements( { @XmlElement( name="element" ) } )
//    @XmlElements( { @XmlElement( name="element", type=ChildListElement.class ) } )
    public void setElementList(List<LE> elementList)
    {
        this.elementList = elementList;
    }

    public void test()
    {
        System.out.println("ParentBucket.test");
        System.out.println("parentBucketMember: " + parentBucketMember);

        for ( LE el : elementList  )
        {
            el.test();
        }
    }
}

ChildBucket.java:

package GenListVsJaxbTests;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ChildBucket extends ParentBucket<ChildListElement>
{
    protected String childBucketMember = "childBucketMember";

    public String getChildBucketMember()
    {
        return childBucketMember;
    }

    public void setChildBucketMember(String childBucketMember)
    {
        this.childBucketMember = childBucketMember;
    }

    public void test()
    {
        System.out.println("ChildBucket.test");
        System.out.println("childBucketMember: " + childBucketMember);
        super.test();
        System.out.println("---");

        for ( ChildListElement el : elementList  )
        {
            el.test();
        }
        System.out.println("===");
    }
}

ParentListElement.java:

package GenListVsJaxbTests;

public class ParentListElement
{
    protected String parentListElMember = "parentListElMember";

    public String getParentListElMember()
    {
        return parentListElMember;
    }

    public void setParentListElMember(String parentListElMember)
    {
        this.parentListElMember = parentListElMember;
    }

    public void test()
    {
        System.out.println("ParentListElement.test");
        System.out.println("parentListElMember: " + parentListElMember);
    }
}

ChildListElement.java:

package GenListVsJaxbTests;

public class ChildListElement extends ParentListElement
{
    protected String childListElMember = "childListElMember";

    public String getChildListElMember()
    {
        return childListElMember;
    }

    public void setChildListElMember(String childListElMember)
    {
        this.childListElMember = childListElMember;
    }

    @Override
    public void test()
    {
        super.test();
        System.out.println("ChildListElement.test");
        System.out.println("childListElMember: " + childListElMember);
    }
}

随后的for循环在运行时抛出此错误(缩写):

ClassCastException: ParentListElement cannot be cast to ChildListElement

正确编组时,输出如下所示:

"C:\Program Files\Java\jdk1.8.0_211\bin\java" -Didea.launcher.port=7538 "-Didea.launcher.bin.path=[SNIP] com.intellij.rt.execution.application.AppMain com.caci.irma.experiment.Main
ChildBucket.test
childBucketMember: childBucketMember
ParentBucket.test
parentBucketMember: parentBucketMember
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
---
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
===
ChildBucket.test
childBucketMember: child bucket
ParentBucket.test
parentBucketMember: parent bucket
ParentListElement.test
parentListElMember: parent element 1
ChildListElement.test
childListElMember: child element 1
ParentListElement.test
parentListElMember: parent element 2
ChildListElement.test
childListElMember: child element 2
ParentListElement.test
parentListElMember: parent element 3
ChildListElement.test
childListElMember: child element 3
ParentListElement.test
parentListElMember: parent element 4
ChildListElement.test
childListElMember: child element 4
---
ParentListElement.test
parentListElMember: parent element 1
ChildListElement.test
childListElMember: child element 1
ParentListElement.test
parentListElMember: parent element 2
ChildListElement.test
childListElMember: child element 2
ParentListElement.test
parentListElMember: parent element 3
ChildListElement.test
childListElMember: child element 3
ParentListElement.test
parentListElMember: parent element 4
ChildListElement.test
childListElMember: child element 4
===

Process finished with exit code 0

错误编组时,输出如下所示:

"C:\Program Files\Java\jdk1.8.0_211\bin\java" -Didea.launcher.port=7542 "-Didea.launcher.bin.path=[SNIP] com.intellij.rt.execution.application.AppMain com.caci.irma.experiment.Main
ChildBucket.test
childBucketMember: childBucketMember
ParentBucket.test
parentBucketMember: parentBucketMember
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
---
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
===
ChildBucket.test
childBucketMember: child bucket
ParentBucket.test
parentBucketMember: parent bucket
ParentListElement.test
parentListElMember: parent element 1
ParentListElement.test
parentListElMember: parent element 2
ParentListElement.test
parentListElMember: parent element 3
ParentListElement.test
parentListElMember: parent element 4
---
Exception in thread "main" java.lang.ClassCastException: GenListVsJaxbTests.ParentListElement cannot be cast to GenListVsJaxbTests.ChildListElement
    at GenListVsJaxbTests.ChildBucket.test(ChildBucket.java:27)
    at GenListVsJaxbTests.ParentChildTests.testChildBucketFromXml(ParentChildTests.java:17)
    at com.caci.irma.experiment.Main.main(Main.java:14)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Process finished with exit code 1

1 个答案:

答案 0 :(得分:0)

以下问题与我的类似:

Marshalling/Unmarshalling Java superclass and subclasses using JAXB

使用@XmlElements批注,可以指定多个@ XmlElement / class条目。这意味着您可以为不同的@XmlElement名称使用不同的目标类。

@XmlElementWrapper( name = "elements" )
@XmlElements
( {
    @XmlElement( name="el1", type=ChildListElement1.class ),
    @XmlElement( name="el2", type=ChildListElement2.class )
} )

但是,这仍然意味着超类必须了解子类。每次创建子类时,也都需要修改超类,以使该子类成为JAXB目标类。似乎有点紧密耦合。同样,每个类都需要有自己唯一的元素名称。这使得每个XML文件都需要其自己的架构,或者,如果架构更复杂并且允许同一结构使用不同的元素名称,那么它们之间可以共享一个架构。

我阅读了有关IDResolver.bind,jaxb:bindings,SchemaFactory,AnnotationHelper,StreamReaderDelegate等的各种文章。jaxb:bindings看起来很有前途,似乎它可以允许为给定节点名称指定目标类。但是,在解组期间实际上会忽略XSD架构中的绑定。

另一种可能解决此问题的方法是:

  1. 在超类中,将setter / getter进行抽象。
  2. 将已实现的setters / getters从超类移动到子类。
  3. 在子类中注释设置器/获取器。
  4. 不需要在@XmlElement批注中指定子类。

超类:

public abstract class ParentBucket<LE extends ParentListElement>
{
    List<LE> elementList;

    public abstract void setElementList(List<LE> elementList);
    public abstract List<LE> getElementList();
}

子类1:

@XmlRootElement
public class ChildBucket1 extends ParentBucket<ChildListElement1>
{
    @Override
    @XmlElementWrapper( name = "elements" )
    @XmlElements ( { @XmlElement( name="element" ) } )
//    @XmlElements ( { @XmlElement( name="element", type=ChildListElement1.class ) } )
    public void setElementList(List<ChildListElement1> elementList)
    {
        super.setElementListCore ( elementList );
    }
    @Override
    public List<ChildListElement1> getElementList()
    {
        return super.getElementListCore();
    }
}

子类2:

@XmlRootElement
public class ChildBucket2 extends ParentBucket<ChildListElement2>
{
    @Override
    @XmlElementWrapper( name = "elements" )
    @XmlElements ( { @XmlElement( name="element" ) } )
//    @XmlElements ( { @XmlElement( name="element", type=ChildListElement2.class ) } )
    public void setElementList(List<ChildListElement2> elementList)
    {
        super.setElementListCore ( elementList );
    }
    @Override
    public List<ChildListElement2> getElementList()
    {
        return super.getElementListCore();
    }
}

最初,它在我的实验代码中起作用,但在“真实”代码中却不起作用。花了一段时间才弄清楚为什么它在一个项目中起作用,而不在另一个项目中起作用。 几乎放弃给超类添加注释并继续前进。

但是:-)我发现了问题所在。注释子类,如果Java成员列表名称为“ fields”,并且将getter / setter命名为getFields / setFields,则JAXB无法“查找”子类并尝试实例化超类。 JAXB不会警告任何冲突。尝试实例化抽象类时,运行时将失败。或者,如果超类不是抽象的,则以后带有具体子类的“ for”循环将因ClassCastException而失败。通过将列表变量的名称从“ fields”更改为“ listOfFields”,以及相应的抽象和已实现的getter / setter,就可以了!

问题超类:

public abstract class ParentBucketOops<FE extends FieldElement>
{
    List<FE> fields; // oops, JAXB has a runtime problem later

    public abstract void setFields(List<FE> fields);
    public abstract List<FE> getFields();
}