使用正确的dtd声明和entityResolver避免saxReader联网验证

来源:互联网 发布:windows系统怎么录屏 编辑:程序博客网 时间:2024/05/01 23:42

 在使用许多使用xml配置文件的框架时,都会碰到以下的问题。有时候项目运行起来,需要花费许多的时间,有时候项目甚至还启动不起来。如使用hibernate时,经常报以下的错误:

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
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

    而是hibernate3.6以上版本,所写的声明中的url地址就变了(当然以前的还继续有效),变成了

1
2
http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd

    这两个声明,在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
 publicSAXReader createSAXReader(String file, List errorsList, EntityResolver entityResolver) {
        SAXReader saxReader = resolveSAXReader();
        saxReader.setEntityResolver(entityResolver);
        saxReader.setErrorHandler(new ErrorLogger(file, errorsList) );
        returnsaxReader;
    }
  
    privateSAXReader resolveSAXReader() {
        if( saxReader == null) {
            saxReader =new SAXReader();
            saxReader.setMergeAdjacentText(true);
            saxReader.setValidation(true);
        }
        returnsaxReader;
    }

    取得一个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 );
                    returnsource;
            }
            elseif ( 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 );
                    returnsource;
            }
        }
        // use default behavior
        returnnull;
    }

    看其实现,即是首先判断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获取和下载了。
    关于如何使用entityResolver,还有一个网址可以参考下:http://www.ibm.com/developerworks/cn/xml/tips/x-tipent/index.html

转载请标明出处:i flym
本文地址:http://www.iflym.com/index.php/code/usr-right-dtd-declaration-and-entityresolver-to-avoid-saxreader-validate-by-network.html

原创粉丝点击