1
2
3
|
org.dom4j.DocumentException: XXXXXXX
at org.dom4j.io.SAXReader.read(SAXReader.java:484)
at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:2211)
|
这是由于在hibernate中在解析xml时,默认会对所需要解析的xml进行validate,即进行验证,验证所写的xml是否符合声明格式要求。在网上搜索相关的帖子,提出的办法就是禁用掉这个验证。但是hibernate解析xml工作是在它的内部进行的,程序上不能对其进行修改;同时,既然hibernate内部使用了这个验证,即有它验证的需要,仅仅禁用掉这个验证并不能解决实际的问题。
在hibernate3.X版本一直到hibernate3.5版本,在hibernate.xml配置文件中,一直使用的xml声明即是如下:
1
2
3
|
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
而是hibernate3.6以上版本,所写的声明中的url地址就变了(当然以前的还继续有效),变成了
这两个声明,在3.6以上版本都是可以接受了。前一声明在3.5及以前版本是正确的。但对于这个声明,如果修改了其中任意一个字符,在启动时都会报错。究其原因,则由于在对xml进行验证时,hibernate提供了一个自己实现的entityResolver。
首先,我们来hibernate是如何加载配置文件的,在类Configuration.doConfigure(InputStream stream, String resourceName)方法中,hibernate首先使用xmlHelper创建一个saxReader,然后使用这个saxReader来取得一个document,最后根据这个document再进行以下的处理。我们来看相应的代码实现:
1
2
|
Document document = xmlHelper.createSAXReader( resourceName, errors, entityResolver ).read( new InputSource( stream ) );
doConfigure( document );
|
再看创建saxReader的过程:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public SAXReader createSAXReader(String file, List errorsList, EntityResolver entityResolver) {
SAXReader saxReader = resolveSAXReader();
saxReader.setEntityResolver(entityResolver);
saxReader.setErrorHandler( new ErrorLogger(file, errorsList) );
return saxReader;
}
private SAXReader resolveSAXReader() {
if ( saxReader == null ) {
saxReader = new SAXReader();
saxReader.setMergeAdjacentText( true );
saxReader.setValidation( true );
}
return saxReader;
}
|
取得一个saxReader并设置其验证为true,即要对xml进行结构验证,然后设置其中一个entityResolver,再处理文档。这里的这个entityResolver,就是我们在具体项目中需要使用的一个文档声明解析器。
何为entityResolver,在官方的doc上有如下的说明,现copy如下: 如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver 方法向 SAX 驱动器注册一个实例。
1
2
|
然后 XML 阅读器将允许应用程序在包含外部实体之前截取任何外部实体(包括外部 DTD 子集和外部参数实体,如果有)。
许多 SAX 应用程序不需要实现此接口,但对于从数据库或其他特定的输入源中构建 XML 文档的应用程序,或者对于使用 URI 类型(而不是 URL )的应用程序,这特别有用。
|
这就是说,对于解析一个xml,sax首先读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明的dtd的uri地址)来下载相应的dtd声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里就会报错的,就是因为相应的dtd声明没有被找到的原因。
entityResolver的作用就是项目本身就可以提供一个如何寻找dtd声明的方法,即由程序来实现寻找dtd声明的过程,比如我们将dtd文件放到项目中某处,在实现时直接将此文档读取并返回给sax,即可。这样就避免了通过网络来寻找相应的声明。
首先看entityResolver的接口方法声明:
1 | InputSource resolveEntity(String publicId, String systemId) |
这里,它接收两个参数publicId和systemId,并返回一个inputSource对象。这里的publicId对应于上面的hibernate.cfg.xml,它就是-//Hibernate/Hibernate Configuration DTD 3.0//EN,而systemId则是http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd。此接口定义为,如果程序实现能够找到本地的一个inputSource实现,即本地的一个dtd,并返回之,则sax就使用此inputSource进行验证解析;否则程序实现返回null,那么sax将执行默认的查找规则,即通过uri地址(即systemId)进行查找了。
那么,我们来看hibernate的entityResolver实现,此实现为类DTDEntityResolver,继续参看其实现:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public InputSource resolveEntity(String publicId, String systemId) {
if ( systemId != null ) {
if ( systemId.startsWith( HIBERNATE_NAMESPACE ) ) {
String path = "org/hibernate/" + systemId.substring( HIBERNATE_NAMESPACE.length() );
InputStream dtdStream = resolveInHibernateNamespace( path );
InputSource source = new InputSource( dtdStream );
source.setPublicId( publicId );
source.setSystemId( systemId );
return source;
}
else if ( systemId.startsWith( USER_NAMESPACE ) ) {
String path = systemId.substring( USER_NAMESPACE.length() );
InputStream stream = resolveInLocalNamespace( path );
InputSource source = new InputSource( stream );
source.setPublicId( publicId );
source.setSystemId( systemId );
return source;
}
}
// use default behavior
return null ;
}
|
看其实现,即是首先判断systemId是否以内置的http://hibernate.sourceforge.net/或classpath://开始。前一个对应于常用配置,后一个对应于以classpath方式的配置。只有将systemId以这两个字符串开头时,才会根据systemId进一步查找相应的dtd,并返回inputSource实现,否则即返回默认的null。以前一个实现,hibernate会寻找到包org/hibernate包下的一个hibernate-configuration-3.0.dtd,此文件被封装到hibernate-core.jar中。这样就可以正常地进行解析和验证了。
实际上很多的sax解析器都是使用此种解析方式来进行解析,包括struts2等,这种解析方式,即保证了xml格式能够被验证,同时又保证了不需要通过网络下载相应的dtd声明,在程序内部就可以实现dtd获取和下载了。