dtd声明和entityResolver避免saxReader联网验证


转自:http://blog.csdn.net/jie1336950707/article/details/48956727

在使用许多使用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
    "-//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获取和下载了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值