jaxb: 奇怪 class 在枚举列表上抛出异常
jaxb: strange class cast exception on enum list
我正在使用 jaxb
从 xsd
文件生成 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.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.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access[=11=]0(ParentRunner.java:53)
at org.junit.runners.ParentRunner.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>
测试reader
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<GConstNameType>
中,或者设置了 String
的列表而不是 GConstNameType
s 列表。无论如何,xml 文件中的 String
枚举名称未映射到 java 枚举常量。
java 运行时在这两种情况下是相同的,它是 jdk1.8
中的 jre
。
用于生成的命令:
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
?
您可以将 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>
我正在使用 jaxb
从 xsd
文件生成 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.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.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access[=11=]0(ParentRunner.java:53)
at org.junit.runners.ParentRunner.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>
测试reader
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<GConstNameType>
中,或者设置了 String
的列表而不是 GConstNameType
s 列表。无论如何,xml 文件中的 String
枚举名称未映射到 java 枚举常量。
java 运行时在这两种情况下是相同的,它是 jdk1.8
中的 jre
。
用于生成的命令:
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
?
您可以将 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>