我們知道mybatis或者spring都是使用xml文件作為配置文件,配置文件的格式都是定義在叫做.dtd或者.xsd文件中的,當工具在解析用戶自己定義的xml文件的時候,如何才能知道用戶自定義的文件是否正確的呢?我們不能在xml文件中亂寫一些框架不認識的標簽,比如在spring的xml文件中寫如下<user>標簽,毫無疑問會報錯。那么框架是怎么來驗證我們所寫的標簽是否正確的呢?
<user> <id>100</id> </user>
由於mybatis使用的是dom解析,利用JDK的dom解析API,如下:
1 @Test 2 public void docFactoryTest() throws Exception { 3 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 4 factory.setValidating(true); 5 factory.setNamespaceAware(false); 6 factory.setIgnoringComments(true); 7 factory.setIgnoringElementContentWhitespace(false); 8 factory.setCoalescing(false); 9 factory.setExpandEntityReferences(true); 10 DocumentBuilder builder = factory.newDocumentBuilder(); 11 builder.setEntityResolver(new XMLMapperEntityResolver());
...(此處省略一部分代碼) 30 Document doc = builder.parse(Resources.getResourceAsStream("mybatis-config.xml")); 31 System.err.println(doc);
在第11行中,用到了XMLMapperEntityResolver對象,這個對象定義如下:
1 public class XMLMapperEntityResolver implements EntityResolver { 2 3 private static final Map<String, String> doctypeMap = new HashMap<String, String>(); 4 5 private static final String IBATIS_CONFIG_PUBLIC = "-//ibatis.apache.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH); 6 private static final String IBATIS_CONFIG_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-config.dtd".toUpperCase(Locale.ENGLISH); 7 8 private static final String IBATIS_MAPPER_PUBLIC = "-//ibatis.apache.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH); 9 private static final String IBATIS_MAPPER_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH); 10 11 private static final String MYBATIS_CONFIG_PUBLIC = "-//mybatis.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH); 12 private static final String MYBATIS_CONFIG_SYSTEM = "http://mybatis.org/dtd/mybatis-3-config.dtd".toUpperCase(Locale.ENGLISH); 13 14 private static final String MYBATIS_MAPPER_PUBLIC = "-//mybatis.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH); 15 private static final String MYBATIS_MAPPER_SYSTEM = "http://mybatis.org/dtd/mybatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH); 16 17 private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd"; 18 private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd"; 19 20 static { 21 doctypeMap.put(IBATIS_CONFIG_SYSTEM, MYBATIS_CONFIG_DTD); 22 doctypeMap.put(IBATIS_CONFIG_PUBLIC, MYBATIS_CONFIG_DTD); 23 24 doctypeMap.put(IBATIS_MAPPER_SYSTEM, MYBATIS_MAPPER_DTD); 25 doctypeMap.put(IBATIS_MAPPER_PUBLIC, MYBATIS_MAPPER_DTD); 26 27 doctypeMap.put(MYBATIS_CONFIG_SYSTEM, MYBATIS_CONFIG_DTD); 28 doctypeMap.put(MYBATIS_CONFIG_PUBLIC, MYBATIS_CONFIG_DTD); 29 30 doctypeMap.put(MYBATIS_MAPPER_SYSTEM, MYBATIS_MAPPER_DTD); 31 doctypeMap.put(MYBATIS_MAPPER_PUBLIC, MYBATIS_MAPPER_DTD); 32 } 33 34 /* 35 * Converts a public DTD into a local one 36 * 37 * @param publicId The public id that is what comes after "PUBLIC" 38 * @param systemId The system id that is what comes after the public id. 39 * @return The InputSource for the DTD 40 * 41 * @throws org.xml.sax.SAXException If anything goes wrong 42 */ 43 @Override 44 public InputSource resolveEntity(String publicId, String systemId) throws SAXException { 45 46 if (publicId != null) { 47 publicId = publicId.toUpperCase(Locale.ENGLISH); 48 } 49 if (systemId != null) { 50 systemId = systemId.toUpperCase(Locale.ENGLISH); 51 } 52 53 InputSource source = null; 54 try { 55 String path = doctypeMap.get(publicId); 56 source = getInputSource(path, source); 57 if (source == null) { 58 path = doctypeMap.get(systemId); 59 source = getInputSource(path, source); 60 } 61 } catch (Exception e) { 62 throw new SAXException(e.toString()); 63 } 64 return source; 65 } 66 67 private InputSource getInputSource(String path, InputSource source) { 68 if (path != null) { 69 InputStream in; 70 try { 71 in = Resources.getResourceAsStream(path); 72 source = new InputSource(in); 73 } catch (IOException e) { 74 // ignore, null is ok 75 } 76 } 77 return source; 78 } 79 80 }
可以看到,當框架在解析xml文件的時候,會把xml文件開頭的publicId和systemId傳給EntityResolver,而EntityResolver對象就是從classpath中去尋找.dtd文件(文件就在org/apache/ibatis/builder/xml/目錄下),在spring中還會存在.xsd文件,原理都是一樣的,然后利用classpath中的.dtd文件進行驗證。如果不指定這個.dtd文件,那么會從互聯網上面下載.dtd文件,性能不好。