Java JAXB Unmarshaller抛出org.xml.sax.SAXParseException:文件提前结束异常求助
解决JAXB反序列化时的“文件提前结束”异常
嘿,我来帮你搞定这个问题!你遇到的org.xml.sax.SAXParseException: 文件提前结束异常,根源其实在你自己的代码里——你两次读取了同一个输入流,导致第二次读取时流已经没有内容了。
先看你的代码片段:
private Course unmarshalCourse(InputStream is) throws CourseServiceException, IOException{ Course c=null; try{ JAXBContext jc = JAXBContext.newInstance( "com.sakai.domain" ); Unmarshaller u = jc.createUnmarshaller(); String theString = IOUtils.toString(is, "UTF-8"); // 第一次读取输入流,指针移到末尾 log.debug("---------xml"+theString); Object value = u.unmarshal(is); // 第二次读取,此时流已经空了! c=(Course)value; log.debug("---------course"+c); }catch(JAXBException e){ throw new CourseServiceException(e, Error.CONFIG); } return c; }
问题出在IOUtils.toString(is, "UTF-8")这一行:当你把输入流转换成字符串时,输入流的读取指针会移动到流的末尾。后续调用u.unmarshal(is)时,流里已经没有任何XML内容可以读取了,JAXB自然会抛出“文件提前结束”的异常。
几种可行的解决方案
方案1:重置输入流(仅适用于支持mark/reset的流)
如果你的InputStream支持标记和重置操作(比如ByteArrayInputStream),可以在读取前标记起始位置,读完日志后重置指针:
private Course unmarshalCourse(InputStream is) throws CourseServiceException, IOException{ Course c=null; try{ JAXBContext jc = JAXBContext.newInstance( "com.sakai.domain" ); Unmarshaller u = jc.createUnmarshaller(); if (is.markSupported()) { // 标记流的起始位置 is.mark(Integer.MAX_VALUE); String theString = IOUtils.toString(is, "UTF-8"); log.debug("---------xml"+theString); // 重置指针到起始位置 is.reset(); } Object value = u.unmarshal(is); c=(Course)value; log.debug("---------course"+c); }catch(JAXBException e){ throw new CourseServiceException(e, Error.CONFIG); } return c; }
注意:不是所有流都支持mark(),比如网络输入流、某些自定义流可能不支持,这时候就用下面的方案。
方案2:将读取到的字符串转为新的输入流(通用方案)
把读取到的XML字符串转换成新的ByteArrayInputStream,传给JAXB反序列化,这样就完全不依赖原输入流的状态了:
import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; private Course unmarshalCourse(InputStream is) throws CourseServiceException, IOException{ Course c=null; try{ JAXBContext jc = JAXBContext.newInstance( "com.sakai.domain" ); Unmarshaller u = jc.createUnmarshaller(); String theString = IOUtils.toString(is, "UTF-8"); log.debug("---------xml"+theString); // 用字符串生成新的输入流 InputStream xmlStream = new ByteArrayInputStream(theString.getBytes(StandardCharsets.UTF_8)); Object value = u.unmarshal(xmlStream); c=(Course)value; log.debug("---------course"+c); }catch(JAXBException e){ throw new CourseServiceException(e, Error.CONFIG); } return c; }
这个方案兼容性最好,不管原输入流是什么类型都能用,非常推荐。
方案3:移除调试用的流读取代码(临时调试用)
如果这段日志只是调试用的,调试完成后可以直接删掉IOUtils.toString相关代码,直接反序列化原输入流:
private Course unmarshalCourse(InputStream is) throws CourseServiceException, IOException{ Course c=null; try{ JAXBContext jc = JAXBContext.newInstance( "com.sakai.domain" ); Unmarshaller u = jc.createUnmarshaller(); Object value = u.unmarshal(is); c=(Course)value; log.debug("---------course"+c); }catch(JAXBException e){ throw new CourseServiceException(e, Error.CONFIG); } return c; }
额外排查点
如果上面的方案都试过还是报错,那要检查:
- 上游调用方传递的输入流是不是本身就为空或者不完整?
- 输入流在传到这个方法之前,是不是已经被其他代码读取过了?
内容的提问来源于stack exchange,提问作者Niranga Sandaruwan
相关产品推荐
相关产品推荐

