Mybatis源码剖析 -- 初始化过程(传统方式)
2021-01-31 本文已影响0人
Travis_Wu
一、读取配置文件,读成字节输入流,注意:现在还没解析
- 入口使用
Resources.getResourceAsStream()
方法获取字节输入流public class MybatisTest { /** * 传统方式 * @throws IOException */ @Test public void test1() throws IOException { // 1. 读取配置文件,读成字节输入流,注意:现在还没解析 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); } }
- 点进去看
getResourceAsStream()
其实传了一个 null 的类加载器和核心配置文件的路劲下去public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource); }
- 继续往下点,又调用了
classLoaderWrapper.getResourceAsStream()
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) { throw new IOException("Could not find resource " + resource); } return in; }
- 接着往下看,最终在 ClassLoaderWrapper 类中找到了类加载器和真正读成字节流的方法
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { return getResourceAsStream(resource, getClassLoaders(classLoader)); } ClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader}; } InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { // 遍历 ClassLoader 数组 for (ClassLoader cl : classLoader) { if (null != cl) { // 获得 InputStream ,不带 / // try to find the resource as passed InputStream returnValue = cl.getResourceAsStream(resource); // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource // 获得 InputStream ,带 / if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } // 成功获得到,返回 if (null != returnValue) { return returnValue; } } } return null; }
二、解析配置文件,封装 Configuration 对象,创建 DefaultSqlSessionFactory 对象
- api入口,使用构建者模式创建一个 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
- 点进去之后发现,其实调用了一个重载的方法,传递三个参数,除了配置文件的字节流之外,其余都传了 null 值
// 我们最初调用的build public SqlSessionFactory build(InputStream inputStream) { //调用了重载方法 return build(inputStream, null, null); }
- 点进去查看这个重载方法
// 调用的重载方法 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 执行 XML 解析 // 创建 DefaultSqlSessionFactory 对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
- 点进
parser.parse()
方法,看一下到底 Mybatis 它是怎么解析配置文件的/** * 解析 XML 成 Configuration 对象。 * * @return Configuration 对象 */ public Configuration parse() { // 若已解析,抛出 BuilderException 异常 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // 标记已解析 parsed = true; ///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签 // 解析 XML configuration 节点 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
- 先获取一个顶层的 configuration 节点,然后调用
parseConfiguration()
这个方法,可以看到,这里就是对各种标签进行解析/** * 解析 XML * * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html * * @param root 根节点 */ private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 解析 <properties /> 标签 propertiesElement(root.evalNode("properties")); // 解析 <settings /> 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); // 加载自定义的 VFS 实现类 loadCustomVfs(settings); // 解析 <typeAliases /> 标签 typeAliasesElement(root.evalNode("typeAliases")); // 解析 <plugins /> 标签 pluginElement(root.evalNode("plugins")); // 解析 <objectFactory /> 标签 objectFactoryElement(root.evalNode("objectFactory")); // 解析 <objectWrapperFactory /> 标签 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析 <reflectorFactory /> 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 <settings /> 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 <environments /> 标签 environmentsElement(root.evalNode("environments")); // 解析 <databaseIdProvider /> 标签 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析 <typeHandlers /> 标签 typeHandlerElement(root.evalNode("typeHandlers")); // 解析 <mappers /> 标签 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
- 重点看一个 properties 标签到底是怎么解析的吧,剩下的触类旁通
/** * 1. 解析 <properties /> 标签,成 Properties 对象。 * 2. 覆盖 configuration 中的 Properties 对象到上面的结果。 * 3. 设置结果到 parser 和 configuration 中 * * @param context 节点 * @throws Exception 解析发生异常 */ private void propertiesElement(XNode context) throws Exception { if (context != null) { // 读取子标签们,为 Properties 对象 Properties defaults = context.getChildrenAsProperties(); // 读取 resource 和 url 属性 String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { // resource 和 url 都存在的情况下,抛出 BuilderException 异常 throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } // 读取本地 Properties 配置文件到 defaults 中。 if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); // 读取远程 Properties 配置文件到 defaults 中。 } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } // 覆盖 configuration 中的 Properties 对象到 defaults 中。 Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } // 设置 defaults 到 parser 和 configuration 中。 parser.setVariables(defaults); configuration.setVariables(defaults); } }
- 解析完成之后,返回一个 configuration 对象,该对象中包含了一个 mappedStatements,其数据结构就是一个 map,根据 namespace.id 存放一个 MappedStatement 对象,之前的自定义持久层框架也是借鉴了这个思路
/** * MappedStatement 映射 * * KEY:`${namespace}.${id}` */ protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");