在JAXP1.3以前的实现中,进行XML的约束验证都是调用SAXParserFactory或DocumentBuilderFactory对象的setValidating(true)方法来处理的。
这种方式有许多不足的地方,它要求正在被验证的XML实例文档中必须要显示的引入相关的约束(Schema或DTD文件),这无形之中就产生了XML实例文档与约束之间的耦合。尤其在分布式或中间件技术应用中,往往在客户端提供的信息交换实体的描述中,并不一定会引入相关的约束,而在服务端进行实体处理之前又需要验证客户端发送来的实体描述是否合法。因此在这种情况下,采用这种老的验证方式就无法实现这一点。
一、 JAXP验证框架
JAXP1.3以后推出了一个全新的验证框架,其核心的接口和实现类都主要在javax.xml.validation包中,最常用的有如下几个:
1)SchemaFactory:创建某种模式(Schema)类型的工厂类。其模式类型需要在其静态的工厂方法newInstance(String schemaLanguage)方法的参数中指出,可用于验证的模式类型在XMLContants中有所提供,分别是XMLConstants.W3C_XML_SCHEMA_NS_URI和XMLConstants.RELAXNG_NS_URI。也就是说,SchemaFactory能用于Shchema和RELAX NG验证;
2)Schema:表示某种验证模式的对象;
3)Validator:验证器。
结合如上所述的三个核心类,其验证的一般步骤如下:
1)创建某种模式类型的工厂类;
2)指定模式的资源并调用工厂对象的newSchema()后获取Schema对象;
3)从Schema对象中生成验证器;
4)指定需要被验证的实例资源并调用验证器的validate()方法。
方式一:利用Validator对象进行显示验证
- package com.daniele.appdemo.xml.jaxp.validation;
-
- import java.io.File;
- import java.io.IOException;
-
- import javax.xml.XMLConstants;
- import javax.xml.transform.Source;
- import javax.xml.transform.stream.StreamSource;
- import javax.xml.validation.Schema;
- import javax.xml.validation.SchemaFactory;
- import javax.xml.validation.Validator;
-
- import org.xml.sax.SAXException;
-
-
- /**
- * <p>XML Schema约束验证测试类</p>
- * @author <a href="mailto:code727@gmail.com">Daniele</a>
- * @version 1.0.0, 2013-7-14
- * @see
- * @since AppDemo1.0.0
- */
- public class SchemaValidationTest {
-
- public static void main(String[] args) throws Exception {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- File schemaSource = new File("resource/xml/schema/schema_test_nns.xsd");
- try {
- Schema schema = factory.newSchema(schemaSource);
- // 从模式中生成验证器
- Validator validator = schema.newValidator();
- // 如果用于验证的Schema文件中定义了命名空间,则被验证的XML实例文件中必须要引用相同的空间
- Source source = new StreamSource(new File("resource/xml/schema/schema_test_copy.xml"));
- validator.validate(source);
- System.out.println("XML有效!");
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- }
除了上述方式外,还可以将Schema对象与某一类型的JAXP解析器工厂(DocumentBuilderFactory或SAXParserFactory)建立联系后, 再让工厂后续创建的解析器在解析XML实例的过程中,隐式的进行验证。
方式二:解析器隐式验证
- package com.daniele.appdemo.xml.jaxp.validation;
-
- import java.io.File;
- import java.io.IOException;
-
- import javax.xml.XMLConstants;
- import javax.xml.parsers.DocumentBuilder;
- import javax.xml.parsers.DocumentBuilderFactory;
- import javax.xml.parsers.ParserConfigurationException;
- import javax.xml.validation.Schema;
- import javax.xml.validation.SchemaFactory;
-
- import org.xml.sax.SAXException;
-
-
- /**
- * <p>XML Schema约束验证测试类</p>
- * @author <a href="mailto:code727@gmail.com">Daniele</a>
- * @version 1.0.0, 2013-7-14
- * @see
- * @since AppDemo1.0.0
- */
- public class SchemaValidationTest {
-
- public static void main(String[] args) throws Exception {
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- File schemaSource = new File("resource/xml/schema/schema_test_nns.xsd");
- try {
- Schema schema = factory.newSchema(schemaSource);
- DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
- builderFactory.setSchema(schema);
- DocumentBuilder builder = builderFactory.newDocumentBuilder();
- builder.parse(new File("resource/xml/schema/schema_test_copy.xml"));
- System.out.println("XML有效!");
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- }
- }
-
- }
二、验证错误后的处理
在上述方式一中,当执行validate()后,如果发现被验证的实例不符合约束规范,则会抛出SAXException异常,从而被后续对应的catch块所捕获并在控制台中简单的显示出异常信息。如果需要集中处理显示出更多精确详细的异常信息,则直接在这里的catch块中进行显然是不合适的。
在方式二中更为严重的问题是:当不符合约束规范时,parse()方法只是简单的显示一个错误信息,将不会抛出异常,因此不会被SAXException对应的catch块所捕获,从而仍然会提示“XML有效!”。
为了解决方式一中集中处理错误和方式二中严重缺陷的问题,这时就需要用到org.xml.sax.ErrorHandler,它是一个接口,其作用在于当进行SAX解析的过程中,如果发生warning、error或
fatalError级别的错误时,则会回调此接口中相关的同名方法,用户需要提供一个实现类,实现对这三种错误类型的处理细节。
- package com.daniele.appdemo.xml.sax.handler;
-
- import org.xml.sax.ErrorHandler;
- import org.xml.sax.SAXException;
- import org.xml.sax.SAXParseException;
-
- /**
- * <p>SAX错误处理器示例类</p>
- * @author <a href="mailto:code727@gmail.com">Daniele</a>
- * @version 1.0.0, 2013-6-18
- * @see
- * @since AppDemo1.0.0
- */
- public class SAXExampleErrorHandler implements ErrorHandler {
-
- /**
- * <p>解析过程中遇到警告性错误时被触发</p>
- * @author <a href="mailto:code727@gmail.com">Daniele</a>
- * @param exception:SAXParseException对象,封装了异常产生时的所在行号和信息
- * @throws SAXException:当遇到警告性错误要求停止解析时,可直接向上抛出此异常
- * @since AppDemo1.0.0
- */
- public void warning(SAXParseException exception) throws SAXException {
- StringBuffer message = new StringBuffer();
- message.append("解析到第").append(exception.getLineNumber())
- .append("行时发生如下警告:\n").append("messge:")
- .append(exception.getMessage());
- System.out.println(message);
- }
-
- /**
- * <p>解析过程中遇到可恢复性错误(如不符合约束规则)时被触发</p>
- * @author <a href="mailto:code727@gmail.com">Daniele</a>
- * @param exception:SAXParseException对象,封装了异常产生时的所在行号和信息
- * @throws SAXException: 当遇到可恢复性错误要求停止解析时,可直接向上抛出此异常
- * @since AppDemo1.0.0
- */
- public void error(SAXParseException exception) throws SAXException {
- StringBuffer message = new StringBuffer();
- message.append("解析到第").append(exception.getLineNumber())
- .append("行时发生如下错误:\n").append("messge:")
- .append(exception.getMessage());
- throw new SAXException(message.toString());
- }
-
- /**
- * <p>解析过程中遇到不可恢复的致命错误(如不符合XML文档的基本规则)时被触发</p>
- * @author <a href="mailto:code727@gmail.com">Daniele</a>
- * @param exception:SAXParseException对象,封装了异常产生时的所在行号和信息
- * @throws SAXException:当遇到不可恢复的严重错误要求停止解析时,可直接向上抛出此异常
- * @since AppDemo1.0.0
- */
- public void fatalError(SAXParseException exception) throws SAXException {
- StringBuffer message = new StringBuffer();
- message.append("解析到第").append(exception.getLineNumber())
- .append("行时发生如下致命错误:\n").append("messge:")
- .append(exception.getMessage());
- throw new SAXException(message.toString());
- }
-
- }
这里需要重点关注的是error方法,因为验证错误一般都属于这类级别的。在此方法的最后一步抛出SAXException,其目的在于一旦发生错误就让解析器停止解析过程,从而可以解决方式二所述的缺陷并且可以在SAXException的catch块中捕获自定义的异常信息。
上述实现类定义好后,就需要在验证或解析之前,设置这个错误处理器,可以设置错误处理器的对象有如下几种:
1)SchemaFactory;
2)Validator;
3)DocumentBuilder;
4)SAXParser;
其中,SchemaFactory是全局的,即后续创建的schema所生成的Validator对象都采用这个全局的处理器来处理验证错误时的逻辑。另外,如果是用SAXParser对象来进行,则上述自定义的错误处理器就应该修改为通过继承org.xml.sax.helpers.DefaultHandler的方式来实现,因为它是在调用parse()方法时传递一个DefaultHandler对象来进行的,而DefaultHandler实现了org.xml.sax.ErrorHandler接口。例如:
schemaFactory.setErrorHandler(new SAXExampleErrorHandler()); 或
saxParser.parse(source,new SAXExampleErrorHandler());
三、DTD验证
SchemaFactory目前并不支持DTD的验证,当我们执行SchemaFactory.newInstance(XMLConstants.XML_DTD_NS_URI)时将会抛出java.lang.IllegalArgumentException。
对于DTD验证,采用原有的验证方式,启用JAXP相关解析器工厂类的验证功能即可。
- package com.daniele.appdemo.xml.jaxp.validation;
-
- import java.io.File;
- import java.io.IOException;
-
- import javax.xml.parsers.ParserConfigurationException;
- import javax.xml.parsers.SAXParser;
- import javax.xml.parsers.SAXParserFactory;
-
- import org.xml.sax.SAXException;
-
- import com.daniele.appdemo.xml.sax.handler.SAXExampleHandler;
-
-
- /**
- * <p>XML DTD约束验证测试类</p>
- * @author <a href="mailto:code727@gmail.com">Daniele</a>
- * @version 1.0.0, 2013-7-16
- * @see
- * @since AppDemo1.0.0
- */
- public class DTDValidationTester {
-
- public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
-
- SAXParserFactory factory = SAXParserFactory.newInstance();
- // 这里只需开启相关解析工厂的验证功能,并在后续的解析过程中隐式的执行验证
- factory.setValidating(true);
- SAXParser parser = factory.newSAXParser();
- parser.parse(new File("resource/xml/dtd/dtd_test.xml"), new SAXExampleHandler());
- }
-
- }
在XML1.0的规范中,没有考虑过要使用DTD之外的方式进行验证。即XML1.0规范硬性规定使用DTD进行验证,文档中必须使用DOCTYPE引入要用于验证的DTD文件。