美文网首页程序员
Mybatis源码剖析 -- 初始化过程(传统方式)

Mybatis源码剖析 -- 初始化过程(传统方式)

作者: Travis_Wu | 来源:发表于2021-01-31 18:54 被阅读0次

一、读取配置文件,读成字节输入流,注意:现在还没解析

  1. 入口使用Resources.getResourceAsStream()方法获取字节输入流
    public class MybatisTest {
        /**
         * 传统方式
         * @throws IOException
         */
        @Test
        public void test1() throws IOException {
          // 1. 读取配置文件,读成字节输入流,注意:现在还没解析
          InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        }
    }
    
  2. 点进去看getResourceAsStream()其实传了一个 null 的类加载器和核心配置文件的路劲下去
    public static InputStream getResourceAsStream(String resource) throws IOException {
        return getResourceAsStream(null, resource);
    }
    
  3. 继续往下点,又调用了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;
    }
    
  4. 接着往下看,最终在 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 对象

  1. api入口,使用构建者模式创建一个 SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  2. 点进去之后发现,其实调用了一个重载的方法,传递三个参数,除了配置文件的字节流之外,其余都传了 null 值
    // 我们最初调用的build
    public SqlSessionFactory build(InputStream inputStream) {
        //调用了重载方法
        return build(inputStream, null, null);
    }
    
  3. 点进去查看这个重载方法
    // 调用的重载方法
    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.
            }
        }
    }
    
  4. 点进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;
    }
    
  5. 先获取一个顶层的 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);
        }
    }
    
  6. 重点看一个 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);
        }
    }
    
  7. 解析完成之后,返回一个 configuration 对象,该对象中包含了一个 mappedStatements,其数据结构就是一个 map,根据 namespace.id 存放一个 MappedStatement 对象,之前的自定义持久层框架也是借鉴了这个思路
    /**
     * MappedStatement 映射
     *
     * KEY:`${namespace}.${id}`
     */
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
    

相关文章

网友评论

    本文标题:Mybatis源码剖析 -- 初始化过程(传统方式)

    本文链接:https://www.haomeiwen.com/subject/dgurtltx.html