【问题标题】:jaxb: strange class cast exception on enum listjaxb:枚举列表上的奇怪类强制转换异常
【发布时间】:2015-05-25 13:58:53
【问题描述】:

我正在使用jaxbxsd 文件生成java 类。 xsd 包含一个元素的定义,其内容是在与枚举相同的xsd 中定义的常量列表。

当使用来自 oracle 的 jdk1.7 (v2.2.4-2) 的 JAXB 参考实现生成类时,可以遍历枚举列表并为它们分配相同类型的变量。

但是,当使用 oracle 的 jdk1.8(构建 1.8.0_45-b15 - 发布日期最新)生成类时,JAXB 参考实现 (v2.2.8-b130911.1802) 不再可以将列表的元素分配给变量枚举类型。

任何使用增强的 for 循环分配或迭代的尝试都以 ClassCastException 结束

java.lang.ClassCastException: java.lang.String cannot be cast to so.jaxb.enums.generated.GConstNameType
    at so.jaxb.enums.domain.TestReader.readTest(TestReader.java:36)
    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:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

列表本身在这两种情况下都使用正确的枚举类型参数化。

这是重现上述问题的代码:

XSD 文件

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns="http://www.w3.org/2001/XMLSchema" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:tns="http://www.foo.com/xmlns/test"
    targetNamespace="http://www.foo.com/xmlns/test"
    attributeFormDefault="unqualified"
    elementFormDefault="qualified">

    <xs:simpleType name="GConstType">
        <xs:list itemType="tns:GConstNameType" />
    </xs:simpleType>
    <xs:simpleType name="GConstNameType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="FOO" />
            <xs:enumeration value="BAR" />
            <xs:enumeration value="BAZ" />
        </xs:restriction>
    </xs:simpleType>

    <xs:complexType name="TestType">
        <xs:all>
            <xs:element name="const-name-list" 
                type="tns:GConstType" minOccurs="0" maxOccurs="1" />
        </xs:all>
    </xs:complexType>

    <xs:element name="test" type="tns:TestType" />

</xs:schema>

测试 XML 文件

<?xml version="1.0" encoding="UTF-8"?>
<t:test xmlns="http://www.w3.org/2001/XMLSchema"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:t="http://www.foo.com/xmlns/test"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <t:const-name-list>FOO BAR</t:const-name-list>

</t:test>

测试阅读器

public class TestReader {

    @Test
    public void readTest() throws IOException {
        try (InputStream xml = TestReader.class
                .getResourceAsStream("/so/jaxb/enums/resources/test.xml");
            InputStream xsd = TestReader.class
                .getResourceAsStream("/so/jaxb/enums/resources/test.xsd")) {
            TestType testType = fromXML(TestType.class, xml, xsd);
            List<GConstNameType> constNameList = testType.getConstNameList();
            for(Object constName : constNameList) {
                System.out.println(constName.getClass().getName());
            }
            for(GConstNameType constName : constNameList) {
                System.out.println(constName);
            }
        }
    }

    public static <T> T fromXML(Class<T> _class, InputStream xml, InputStream xsd) {
        XMLStreamReader xsr = null;
        try {
            Source xmlSource = new StreamSource(xml);
            Source xsdSource = new StreamSource(xsd);
            JAXBContext jaxbContext = JAXBContext.newInstance(_class);
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            Schema schema = SchemaFactory
                .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(xsdSource);
            unmarshaller.setSchema(schema);
            xsr = XMLInputFactory.newInstance().createXMLStreamReader(xmlSource);
            JAXBElement<T> jaxbElement = unmarshaller.unmarshal(xsr, _class);
            return jaxbElement.getValue();
        } catch (JAXBException | SAXException | XMLStreamException | FactoryConfigurationError e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if(xsr != null) {
                    xsr.close();   
                }
            } catch(XMLStreamException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

输出jdk1.7

so.jaxb.enums.generated.GConstNameType
so.jaxb.enums.generated.GConstNameType
FOO
BAR

输出jdk1.8

java.lang.String
java.lang.String
<ClassCastException>

从上面的输出可以清楚地看出java.lang.String 类型的元素被偷运到列表List&lt;GConstNameType&gt; 中,或者设置了Strings 的列表而不是GConstNameTypes 列表。无论如何,xml 文件中的 String 枚举名称未映射到 java 枚举常量。

java 运行时在这两种情况下都是相同的,它是来自jdk1.8jre

用于生成的命令:

C:\Oracle\Java\jdk1.7\bin\xjc.exe D:\dev\java\Tests\src\so\jaxb\enums\resources\test.xsd -b D:\dev\java\Tests\src\so\jaxb\enums\resources -d D:\dev\java\Tests/src -p so.jaxb.enums.generated -extension 

C:\Oracle\Java\jdk1.8\bin\xjc.exe D:\dev\java\Tests\src\so\jaxb\enums\resources\test.xsd -b D:\dev\java\Tests\src\so\jaxb\enums\resources -d D:\dev\java\Tests/src -p so.jaxb.enums.generated -extension 
  • 这怎么可能/这里发生了什么?
  • 上面显示的架构定义是否不适合定义枚举?
  • 如何在不使用XmlAdapter(适用于每个 jdk 版本的方法)的情况下解决此问题?

编辑

两个生成的包之间唯一的代码区别

删除注释

@XmlSchemaType(name = "anySimpleType")

使jdk1.8 生成的代码完全正常运行。

  • 为什么较新的JAXB 实现将枚举映射到anySimpleType

【问题讨论】:

  • 你试过用 代替 我> ?似乎 Java8 对 XML 字符串可以或不可以是什么不那么宽容。
  • @medveshonok117 按照建议尝试使用xs:token,但xcj 仍在生成@XmlSchemaType(name = "anySimpleType"),从而导致ClassCastException
  • JAXB GH 项目中的问题:github.com/gf-metro/jaxb/issues/21

标签: java xml jaxb java-8 oxm


【解决方案1】:

你可以把你的xsd改成:

 <xs:complexType name="TestType">
    <xs:sequence>
        <xs:element name="const-name-list">
            <xs:simpleType>
                <xs:list itemType="tns:GConstNameType"/>
            </xs:simpleType>
        </xs:element>
    </xs:sequence>
</xs:complexType>

<xs:simpleType name="GConstNameType">
    <xs:restriction base="xs:string">
        <xs:enumeration value="FOO"/>
        <xs:enumeration value="BAR"/>
        <xs:enumeration value="BAZ"/>
    </xs:restriction>
</xs:simpleType>

这适用于 java 8。

新的解析器有一些新的限制。

更新:对于您的评论,您可以使用:

<xs:complexType name="TestType">
    <xs:complexContent>
        <xs:extension base="tns:ListType">
            <xs:sequence/>
        </xs:extension>
    </xs:complexContent>
</xs:complexType>

<xs:complexType name="ListType">
    <xs:sequence>
        <xs:element name="const-name-list">
            <xs:simpleType>
                <xs:list itemType="tns:GConstNameType"/>
            </xs:simpleType>
        </xs:element>
    </xs:sequence>
</xs:complexType>

<xs:complexType name="SecondTestType">
    <xs:complexContent>
        <xs:extension base="tns:ListType">
            <xs:sequence/>
        </xs:extension>
    </xs:complexContent>
</xs:complexType>

<xs:simpleType name="GConstNameType">
    <xs:restriction base="xs:string">
        <xs:enumeration value="FOO"/>
        <xs:enumeration value="BAR"/>
        <xs:enumeration value="BAZ"/>
    </xs:restriction>
</xs:simpleType>

【讨论】:

  • 感谢您的回答。虽然这可以解决这个问题,但我发现每次需要时都必须内联定义类型而不是定义一次并使用属性 type="..." 引用它并不是很聪明。您能否详细说明这些限制?是否有任何规范表明现在必须像这样定义枚举列表(恕我直言,两个定义是等效的)?无论如何,我认为生成Strings 列表并注入GConstNameType 列表是一个错误而不是限制。限制则相反;-)
  • @A4L 你的意思是列表的定义?
  • 是的,简单类型 GConstType 在您的版本中是匿名的,因为内联了。
  • @A4L 我放了最新的 xsd。
  • 嘿,我看到了您的更新,但没有时间评估它。您的第二个建议也有效,但我发现第一个变体更好,因为与第二个相比,它仅以最小的方式改变了原始的 xsd。我会接受你的回答,因为它提供了一种解决方法,可以从xjc 的两个版本中生成工作代码。由于没有提供进一步的解释,因此没有奖励 :(,但无论如何再次感谢您的时间和帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-19
相关资源
最近更新 更多