Как заставить JAXB создавать экземпляр списка подклассов универсального суперкласса - PullRequest
1 голос
/ 08 июля 2019

Я пытаюсь объединить несколько вхождений одного подкласса ChildListElement универсального суперкласса ParentListElement в список, используя JAXB.Проблема заключается в том, что JAXB отменяет вызов суперкласса вместо подкласса.Как заставить JAXB отменять вызов ChildListElement вместо ParentListElement без блокировки одного подкласса ChildListElement?

Спасибо за ваше время и помощь.: -)

Это файлы / классы:

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

Использование аннотаций оболочки и элементов в ParentBucket работает, только если указано type=ChildListElement.class.Но это противоречит цели иметь общий суперкласс, потому что один подкласс ChildListElement заблокирован. Я хочу иметь возможность иметь несколько подклассов ParentListElement.Каждый список будет содержать только один вид подкласса за один раз.

Работает - отменяет маршалинг для 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);
    }
}

Последовательность для цикла выдает эту ошибку во время выполнения (сокращенно):

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 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 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 голосов
/ 25 июля 2019

Этот вопрос ниже похож на мой:

Маршаллинг / демаршаллинг суперкласса и подклассов Java с использованием JAXB

С аннотацией @XmlElements вы можете указать несколько @XmlElement/ записи класса.Это означает, что у вас могут быть разные классы назначения для разных имен @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: выглядела многообещающе, казалось, что она могла бы позволить указать класс назначения для данного имени узла.Тем не менее, привязки в схеме XSD фактически игнорировались во время демаршаллинга.

Еще один способ потенциальной обработки проблемы заключался в следующем:

  1. В суперклассе сделайте абстрактным метод set / getter.
  2. Переместите реализованные установщики / получатели из суперкласса в подклассы.
  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», а метод получения / установки называется 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();
}
...