近日在把程序从jboss移动到jetty服务器时,使用jaxb的地方在Marshal的时候会抛上面的异常,堆栈信息如下:
ERROR [com.cmri.mcss.web.servlet.confmgmt.ConfPicServlet:65] Could not parse the XML stream.
javax.xml.ws.soap.SOAPFaultException: Could not parse the XML stream.
at org.apache.cxf.jaxws.DispatchImpl.mapException(DispatchImpl.java:235)
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:264)
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:195)
.....(程序调用的地方,省略部分)
Caused by: org.apache.cxf.interceptor.Fault: Could not parse the XML stream.
at org.apache.cxf.databinding.source.XMLStreamDataWriter.write(XMLStreamDataWriter.java:85)
at org.apache.cxf.databinding.source.XMLStreamDataWriter.write(XMLStreamDataWriter.java:49)
at org.apache.cxf.databinding.source.XMLStreamDataWriter.write(XMLStreamDataWriter.java:45)
at org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor.writeParts(AbstractOutDatabindingInterceptor.java:114)
at org.apache.cxf.interceptor.BareOutInterceptor.handleMessage(BareOutInterceptor.java:68)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:236)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:471)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:301)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:253)
at org.apache.cxf.endpoint.ClientImpl.invokeWrapped(ClientImpl.java:288)
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:257)
... 38 more
Caused by: javax.xml.stream.XMLStreamException: javax.xml.bind.MarshalException
- with linked exception:
[javax.xml.stream.XMLStreamException: Prefix cannot be null]
at org.apache.cxf.staxutils.StaxUtils.copy(StaxUtils.java:339)
at org.apache.cxf.databinding.source.XMLStreamDataWriter.write(XMLStreamDataWriter.java:82)
... 48 more
Caused by: javax.xml.bind.MarshalException
- with linked exception:
[javax.xml.stream.XMLStreamException: Prefix cannot be null]
at javax.xml.bind.util.JAXBSource$1.parse(JAXBSource.java:225)
at javax.xml.bind.util.JAXBSource$1.parse(JAXBSource.java:210)
at org.apache.cxf.staxutils.StaxUtils.copy(StaxUtils.java:335)
... 49 more
Caused by: javax.xml.bind.MarshalException
- with linked exception:
[javax.xml.stream.XMLStreamException: Prefix cannot be null]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:254)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:103)
at javax.xml.bind.util.JAXBSource$1.parse(JAXBSource.java:222)
... 51 more
Caused by: javax.xml.stream.XMLStreamException: Prefix cannot be null
at org.apache.cxf.staxutils.StreamWriterContentHandler.startElement(StreamWriterContentHandler.java:223)
at org.xml.sax.helpers.XMLFilterImpl.startElement(XMLFilterImpl.java:527)
at com.sun.xml.bind.v2.runtime.output.SAXOutput.endStartTag(SAXOutput.java:124)
at com.sun.xml.bind.v2.runtime.XMLSerializer.endAttributes(XMLSerializer.java:302)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:588)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:312)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:490)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:325)
... 54 more
Caused by: javax.xml.stream.XMLStreamException: Prefix cannot be null
at com.sun.xml.internal.stream.writers.XMLStreamWriterImpl.writeStartElement(XMLStreamWriterImpl.java:1284)
at com.sun.xml.internal.stream.writers.XMLStreamWriterImpl.writeStartElement(XMLStreamWriterImpl.java:1262)
at org.apache.cxf.staxutils.StreamWriterContentHandler.startElement(StreamWriterContentHandler.java:197)
... 61 more
看问题好像是使用sun默认的Marshaller实现来转换Object和xml时出了问题。google上给出的答案不一,但是基本上的意思是要替换Marshaller的实现类。网上碰到类似问题的回答基本有下面几种:
1.javase6(没有细版本,我用的是1.6.0_29)的rt.jar中默认jaxb的API是2.0版本(看了一下本地jdk中jaxb的源代码,代码中没有标注版本号,但是代码中作者注释的日期是2003年,jaxb2.1的实现貌似是06年发布的),需要把jaxb2.1 api的包放到classpath中,但是事实上这个jar包已经在我的classpath中(2.1的实现中,类名和JDK的实现完全相同),于是猜想可能是class加载顺序的问题(jetty在加载class时,指定了某些类是属于System Class,jetty关于classloading的解释:http://docs.codehaus.org/display/JETTY/Classloading),查看jetty部分源码发现jetty定义的system class规则如下(源码在org.eclipse.jetty.webapp.WebAppContext类中):
// System classes are classes that cannot be replaced by
// the web application, and they are *always* loaded via
// system classloader.
public final static String[] __dftSystemClasses =
{
"java.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
"javax.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
"org.xml.", // needed by javax.xml
"org.w3c.", // needed by javax.xml
"org.apache.commons.logging.", // TODO: review if special case still needed
"org.eclipse.jetty.continuation.", // webapp cannot change continuation classes
"org.eclipse.jetty.jndi.", // webapp cannot change naming classes
"org.eclipse.jetty.plus.jaas.", // webapp cannot change jaas classes
"org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
"org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
"org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets
} ;
因为system class不能被overload,所以在classpath中就另外的实现类,也是会被加载的。于是想是不是把jaxb-api-2.1.jar放到jre的endorsed目录下(默认没有,自己建)是不是会优先加载,就可以覆盖掉JDK自带的实现。试了一下,还是不行。(顺便贴一篇文章名字叫: Using JAXB 2.1 with j2se 6 http://matrix.iteye.com/blog/284128)
2.用Woodstox替代JDK自带的实现
从Woodstox的网站下到依赖的jar包,放到jetty的classpath中(在jetty的lib下建一个woodstox目录,在start.ini中添加该目录),一试果然成功了。把Woodstox的jar包放到endorsed目录,不添加到jetty的classpath目录,测试,不行!这个问题就有点纠结了(没想通为什么,难道是放到jre的endorsed目录的jar包没有被load?)。放到classpath下面程序在找JAXB的实现时会自动使用Woodstox的原因是因为在Woodstox的jar包中META-INF目录下的services目录下定义有javax.xml.stream.XMLEventFactory
javax.xml.stream.XMLInputFactory
javax.xml.stream.XMLOutputFactory
说实话META-INF中定义一些信息这个地方,我也没还是没弄清楚!
程序可以run起来了,还剩2个问题需要继续深入:
1.jre的endorsed的目录中的类会被当成System class优先被加载吗?会的话,为什么会出现上面的状况(woodstox的jar包放到jre的endorsed目录下不行,放到classpath下反而可以)。
2.Woodstox是怎么做到被优化作为JAXB的实现的