在使用mybaits时,首先会创建一个SqlSessionFactory对象,该对象是由SqlSessionFactoryBuilder对象,调用该对象的build方法加载全局XML配置的流文件构建出一个SqlSessionFactory对象。
//读取conf.xml Reader reader = Resources.getResourceAsReader("conf.xml"); //创建会话工厂 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); 复制代码
public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } 复制代码
SqlSessionFactoryBuilder只有一堆重载的build方法,除了build(Configuration)方法,其他方法的参数都是输入流,最终由build(Configuration)方法生成SqlSessionFactory对象,在其中会生成一个XMLConfigBuilder对象,下面来看如何构建Configuration对象。
XMLConfigBuilder类名就可以看出,这是用来解析XML配置文件的类,其父类为BaseBuilder。
public class XMLConfigBuilder extends BaseBuilder{ ... } 复制代码
其中BaseBuilder还包含了许多子类,这些子类都是用来解析MyBatis各个配置文件,他们通过BaseBuilder父类共同维护一个全局的Configuration对象, XMLConfigBuilder的作用就是解析全局配置文件,调用BaseBuilder其他子类解析其他配置文件,生成最终的Configuration对象。 值得注意的是,这里BaseBuilder构造方法参数是一个初始化的Configuration对象,Configuration对象初始化的时候,内置的别名注册器TypeAliasRegistry注册了默认的别名
public Configuration() { typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); typeAliasRegistry.registerAlias("FIFO", FifoCache.class); typeAliasRegistry.registerAlias("LRU", LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); } 复制代码
public XMLConfigBuilder(Reader reader, String environment, Properties props) { this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); } 复制代码
上述代码可以发现在SqlSessionFactoryBuilder.build方法中,new XMLConfigBuilder类时 environment和props值为null,所以,我们主要看new XPathParser方法
private Document document ; // Document 对象 private boolean validation; //是否开启验证 private EntityResolver entityResolver ; // 用于加载本地DTD 文件 pruvate Properties variables ; // mybatis -config.xml 中< propteries > 标签定义的键位对集合 private XPath xpath ; // XPath 对象 复制代码
XPathParser类提供了一系列的构造函数,所有的构造函数内部都通过通用的commonConstructor()方法实现对相关字段属性的初始化,部分构造函需要通过createDocument()方法把指定的数据源转换成Document对象。 大致可以分为四类
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); this.document = createDocument(new InputSource(reader)); } 复制代码
该方法主要实现根据输入源创建Document对象。创建Document对象的过程如下
具体代码如下:
private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { //创建DocumentBuilderFactory实例对象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //设置是否启用DTD验证 factory.setValidating(validation); //设置是否支持XML名称空间 factory.setNamespaceAware(false); //设置解析器是否忽略注释 factory.setIgnoringComments(true); /** * 设置必须删除元素内容中的空格(有时也可以称作“可忽略空格”,请参阅 XML Rec 2.10)。 * 注意,只有在空格直接包含在元素内容中,并且该元素内容是只有一个元素的内容模式时, * 才能删除空格(请参阅 XML Rec 3.2.1)。由于依赖于内容模式,因此此设置要求解析器处于验证模式。默认情况下,其值设置为 false。 */ factory.setIgnoringElementContentWhitespace(false); /** * 指定由此代码生成的解析器将把 CDATA 节点转换为 Text 节点,并将其附加到相邻(如果有)的 Text 节点。默认情况下,其值设置为 false。 */ factory.setCoalescing(false); /** * 指定由此代码生成的解析器将扩展实体引用节点。默认情况下,此值设置为 true。 */ factory.setExpandEntityReferences(true); //创建DocumentBuilder实例对象 DocumentBuilder builder = factory.newDocumentBuilder(); //指定使用 EntityResolver 解析要解析的 XML 文档中存在的实体。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。 builder.setEntityResolver(entityResolver); //指定解析器要使用的 ErrorHandler。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。 builder.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { } }); return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } } 复制代码
构造器通用代码块,用于初始化validation、entityResolver、variables、xpath等属性字段。其中,validation、entityResolver、variables三个参数通过参数传递过来;xpath属性是通过XPathFactory创建。具体代码片段:
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) { this.validation = validation; this.entityResolver = entityResolver; this.variables = variables; XPathFactory factory = XPathFactory.newInstance(); this.xpath = factory.newXPath(); } 复制代码
XPathParser类提供了一系列的evalXXX方法,见下图,这些方法主要用于解析boolean 、short、long 、int 、String 、Node等类型的信息。底层是通过evaluate()方法实现。其中,evalString()方法中,会通过调用PropertyParser.parse()处理占位符;evalNode()、evalNodes()方法中,根据解析结果会创建XNode对象。具体创建过程,在XNode类源码分析中学习。
XNode类对应了配置文件中一个元素节点的信息。
//org.w3c.dorn.Node对象 private final Node node; //Node节点名称 private final String name; //节点的内容 private final String body; //节点属性集合 private final Properties attributes; //配置文件中<properties>节点下定义的键位对 private final Properties variables; //XPathParser对象,当前XNode对象由此XPathParser对象生成 private final XPathParser xpathParser; 复制代码
此时我们已经得到了XMLConfigBuilder对象,再看SqlSessionFactoryBuilder的build方法,将XMLConfigBuilder实例对象parser调用parser()方法得到的Configuration实例对象config作为参数,调用SqlSessionFactory接口的实现类DefaultSqlSessionFactory构造出SqlSessionFactory对象。 XMLConfigBuilder对象在调用parser()方法时,会读出所有所有配置文件,将配置文件解析后保存在Configuration对象中。
再查看build方法,参数为Configuration,可以猜测处parse方法是返回一个Configuration对象
parse方法代码:
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //参数是<configuraton>标签根节点 parseConfiguration(parser.evalNode("/configuration")); return configuration; } 复制代码
可以发现这里通过parseConfiguration方法使用之前创建的Xparser类,把XML全局配置文件中每一个节点的信息都读取出来,保存在一个Configuration对象中,Configuration分别对以下内容做出了初始化:
private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } 复制代码
到了这里,已经可以开始加载配置文件了
总的来说,大概就是这个样子
很遗憾的说,推酷将在这个月底关闭。人生海海,几度秋凉,感谢那些有你的时光。
原文 https://juejin.im/post/5f193cea6fb9a07eaa40d26a