MyBatis源码核心处理层:MyBatis初始化流程上

1 概述

       在学习完MyBatis的基础模块之后,我们就正式进入主题,开始分析MyBatis的初始化流程,对于MyBatis的初始化流程,涉及到的知识点比较多,如各种配置的初始化,映射文件的解析,特别是映射文件的解析比较麻烦,所以对于MyBatis的初始化流程,将分为两篇文章进行解析,本篇只分析配置的初始化,对于映射文件的解析就放在下篇文章进行解析。

2.SqlSessionFactoryBuilder——配置文件的解析入口

       不知道童鞋们还是否还记得,我们刚学习MyBatis的时候,第一步往往是先根据配置文件得到SqlSessionFactory;通过分析可知,SqlSessionFactoryBuilder就是是MyBatis解析配置文件的入口。其build函数对应的代码如下:

 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) {
            }
        }
    }

       通过上面的代码我们可以看出,配置文件的真正解析逻辑是在XMLConfigBuilder中的Parse()方法,在分析XMlConfigBuilder#parse()方法之前,我们先看一下XMLConfigBuilder的构造函数,这个对了解MyBatis默认别名的注册有一定的帮助。

3.XMLConfigBuilder

       XMLConfigBuilder继承与BaseBuilder,主要负责解析MyBatis的配置文件,其中的一个构造函数代码对应的代码如下:

 public XMLConfigBuilder(Reader reader, String environment, Properties props) {
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
    }
 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }

       XMlCOnfigBuilder的字段属性并不多,有我们在基础模块碰到过的XPathParser,比较重要的字段就是configuration,代表的就是我们的配置文件。在这里,我要说的 是configurauin的默认构造函数了。其对应的代码如下所示:

 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);
    }

        对于上面的配置,有些别名对应的实体类,有些童鞋应该很熟悉了,例如在我之前博客中的My-Batis的日志模块中的jdbc调试功能的代码展示中,有用到STDOUT_LOGGING这个别名,其实这些别名的对应具体类的介绍,在MyBatis的基础模块都有提到,如上面配置的缓存模块,日志模块。binding模块等。

4.配置文件解析流程

        对于Configuration类,这个不是本篇的重点,我们回归到正常解析流程,XMLC-onfurationBuilder类的Parse方法。如下,为对应的代码,关键性代码注释我已经标注。

public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        //属性配置文件加载,properties
        propertiesElement(root.evalNode("properties"));
        //解析setting节点
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        //虚拟文件系统
        loadCustomVfs(settings);
        //配置日志打印,
        loadCustomLogImpl(settings);
        //别名
        typeAliasesElement(root.evalNode("typeAliases"));
        //加载插件
        pluginElement(root.evalNode("plugins"));
        //objectFactory ->反射模块
        objectFactoryElement(root.evalNode("objectFactory"));
        //objectWrapperFactory
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        //reflectorFactory
        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"));
        //mapper文件解析
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

        如上,为一个完整的MyBatis配置的解析过程。对于每个不同的配置都封装在不同的方法中,本篇也是以这些方法为维度进行分析的,当然,mapper文件的解析会在下篇文章介绍。

4.1 #propertiesElement(XNode context) 属性加载

       MyBatis的属性配置可参考https://mybatis.org/mybatis-3/zh/configuration.html#properties,参考上面的配置,我们就来分析一下Properties节点的解析过程。解析过程的主要代码如下:

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        //(1).将Children转成对应的属性集合,方法由XNode提供
        Properties defaults = context.getChildrenAsProperties();
        // (2).获取resource节点值
        String resource = context.getStringAttribute("resource");
        //(3).url值
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
        }
        //(4)加载资源文件
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        //创建配置文件的时候传入
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

        上面解析过程的关键性代码已经打上了标注,整个过程来说要比较简单,需要注意的是,配置发生同名覆盖的问题;通过上面的配置可以知道,是先解析子节点,然后再解析配置文件的,最后是初始化的时候传入的变量。

4.2 settingsAsProperties settings节点的配置

       在MyBatis中,settings节点的配置是比较重要的,它可以改变MyBatis在运行时的行为,配置也比较多,对于不熟悉的童鞋,可参考https://mybatis.org/mybatis-3/zh/configuration.html#settings ,这里有详细的描述。
在这里插入图片描述
       如上图所示,为一个配置完整的 settings 元素配置,setting元素配置的解析代码如下:

private Properties settingsAsProperties(XNode context) {
    if (context == null) {
        return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    //(1) MetaClass -> 基础模块中的反射模块提供的功能
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
        //(2)Configuration 是否有对应的setter方法
        if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
    }
    return props;
}

代码分析:

  • 代码(1):该处涉及到MyBatis的基础模块中反射模块提供的类,童鞋可自行阅读,后面我也会再写一篇文政分析该反射模块。
  • 代码(2):利用MetaClass类,通过判断Configuration类是否有对应的setter方法来判断配置是否合法

总的来说,这个地方代码比较简单,唯一的是要对MetaClass需要熟悉,该类是反射模块涉及到的,有疑问的童鞋可留下评论;博主为进行解答的,后面也会有文章分析MyBatis的反射模块。

4.3 setting节点的应用

       对于setting节点的应用,主要体现在XMLConfiguration#parseConfiguration()方法的loadCustomVfs,loadCustomLogImpl,settingsElement的调用,对于虚拟文件系统的加载,用的比较少,就不再说明,主要是说一下余下的两个方法。

4.3.1 loadCustomLogImpl

        loadCustomLogImpl 其实在博文另一篇文章基础模块的日志模块(文章连接:MyBatis基础层日志模块)里已经讲解到过,主要是通过logFactory来选择日志框架。对应的代码如下

 private void loadCustomLogImpl(Properties props) {
        Class <? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
        configuration.setLogImpl(logImpl);
    }

public void setLogImpl(Class <? extends Log> logImpl) {
    if (logImpl != null) {
        this.logImpl = logImpl;
        LogFactory.useCustomLogging(this.logImpl);
    }
}

4.3.2 settingsElement

        settingsElement 主要是进行默认配置的加载,童鞋门可选择性的记住一些默认配置,其对应的代码如下:

private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

4.4:typeAliases - 别名注册

        别名的配置可以大大简化我们的代码,在MyBatis中,对别名的配置,通常有两种方式,一种是配置包名,另一种是通过配置typeAlias 进行配置,具体的可以参考https://mybatis.org/mybatis-3/zh/configuration.html#settings,上面有对这两种配置有详细的介绍。如下为两种配置方式的解析代码.:

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            //(1):包名解析
            if ("package".equals(child.getName())) {
                String typeAliasPackage = child.getStringAttribute("name");
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                //(2):从typeAlias 节点中解析别名和类型的映射
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class <?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}

代码分析:

  • 代码(1):包名解析,会扫描指定包下的所有类,如果有Alias注解,则别名是Alias注解的Value值,否则,则将类名首字母转小写作为别名。
  • 代码(2):通过typeAlias注册别名,代码比较简单,就不赞阐述。

总的来说,两种方式的别名解析,最终都会注入到TypeAliasRegistry中。

4.5 插件加载

        插件是MyBatis提供的扩展接口之一,通过插件,我们可以在MyBitia的特定组件运行过程中做一些扩展功能,如博主公司项目里,就利用插件做过一些业务上的SQL监控。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

  • ParameterHandler(getParameterObject, setParameters)

  • ResultSetHandler (handleResultSets, handleOutputParameters)

  • StatementHandler (prepare, parameterize, batch, update, query)

           对于插件的运行原理,后面会在MyBatis的执行流程中进行分析,此处暂不说明,童鞋只需要知道,该处的功能是将我们配置的插件记载到Configuration的interceptorChain成员变量即可。

4.6 typeHandlerElement 类型转换配置

        typeHandler涉及到的有MyBatis基础模块的类型转换模块,该模块主要是实现对数据库类型和Java类型的转换,转换模式如下图所示:
在这里插入图片描述
        MyBatis 提供了一些常见类型的类型处理器,除此之外,我们还可以自定义类型处理器以非常见类型转换的需求。对于类型转换模块的配置可参考https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers。里面有比较详细的描述。如下所示,为解析类型转换模块的代码:

private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                String handlerTypeName = child.getStringAttribute("handler");
                Class <?> javaTypeClass = resolveClass(javaTypeName);
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class <?> typeHandlerClass = resolveClass(handlerTypeName);
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }

解析过程总的来说,同别名的加载代码差不多,都有两种注册方式,童鞋可自行研究阅读代码。

4.7 :其它配置的加载

        除映射文件的加载之外,其它配置的加载,如environments的加载,基本上在实际中用得很少,感兴趣的读者可自行研究也可参考https://mybatis.org/mybatis-3/zh/configuration.html#environments 对应上面有比较详细的说明。本篇就不再阐述。

5总结

在这里插入图片描述
       如图,为MyBatis的初始化流程图,博主只是简单画了一下相关流程,本本篇只是简单的分析了红色部分,后面mapper文件的加载将在下篇文章分析,由于个人水平有限,若文章有错误不妥之处,也请大家多多指教。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值