2025年4月12日 星期六 乙巳(蛇)年 正月十三 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > MyBatis

Chapter2 MyBatis 配置文件解析过程

时间:12-14来源:作者:点击数:4

本章对 MyBatis 解析配置文件的过程进行分析。

配置文件解析过程分析

我们在使用 MyBatis 时,第一步要做的事情一般是根据配置文件构建 SqlSessionFactory对象。 相关代码大致如下:

  • String resource = "mybatis-config.xml";
  • InputStream inputStream = Resources.getResourceAsStream(resource);
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

在上面代码中, 我们首先会使用 MyBatis 提供的工具类 Resources 加载配置文件,得到一个输入流。然后再通过 SqlSessionFactoryBuilder 对象的 build 方法构建 SqlSessionFactory对象。 这里的 build 方法是我们分析配置文件解析过程的入口方法。 下面我们来看一下这个方法的代码:

  • // org.apache.ibatis.session.SqlSessionFactoryBuilder.java
  • public SqlSessionFactory build(InputStream inputStream) {
  • return this.build((InputStream)inputStream, (String)null, (Properties)null);
  • }
  • public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  • SqlSessionFactory var5;
  • try {
  • XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  • var5 = this.build(parser.parse());
  • } catch (Exception var14) {
  • throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
  • } finally {
  • ErrorContext.instance().reset();
  • try {
  • inputStream.close();
  • } catch (IOException var13) {
  • }
  • }
  • return var5;
  • }
  • public SqlSessionFactory build(Configuration config) {
  • return new DefaultSqlSessionFactory(config);
  • }

从上面的代码中,我们大致可以猜出 MyBatis 配置文件是通过 XMLConfigBuilder 进行解析的。不过目前这里还没有非常明确的解析逻辑,所以我们继续往下看。这次来看一下 XMLConfigBuilder 的 parse 方法,如下:

  • public Configuration parse() {
  • if (this.parsed) {
  • throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  • } else {
  • this.parsed = true;
  • this.parseConfiguration(this.parser.evalNode("/configuration"));
  • return this.configuration;
  • }
  • }
  • private void parseConfiguration(XNode root) {
  • try {
  • this.propertiesElement(root.evalNode("properties"));
  • Properties settings = this.settingsAsProperties(root.evalNode("settings"));
  • this.loadCustomVfs(settings);
  • this.loadCustomLogImpl(settings);
  • this.typeAliasesElement(root.evalNode("typeAliases"));
  • this.pluginElement(root.evalNode("plugins"));
  • this.objectFactoryElement(root.evalNode("objectFactory"));
  • this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  • this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
  • this.settingsElement(settings);
  • this.environmentsElement(root.evalNode("environments"));
  • this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  • this.typeHandlerElement(root.evalNode("typeHandlers"));
  • this.mapperElement(root.evalNode("mappers"));
  • } catch (Exception var3) {
  • throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
  • }
  • }

从上面的代码中,首先找到了 XPath 为 /configuration 的节点,并对改节点的子节点进行解析,包括 properties,settings,typeAliases 等等,每一个子节点都有一个对应的解析方法。接下来,我们就来看看几个常用的节点解析的逻辑。

解析 <properties> 节点

<properties> 节点的解析工作由 propertiesElement 这个方法完成的, 在分析方法的源码前,我们先来看一下 <properties> 节点是如何配置的。如下:

  • <properties resource="jdbc.properties">
  • <property name="jdbc.username" value="coolblog"/>
  • <property name="hello" value="world"/>
  • </properties>

该 xml 中的 properties 节点包括了一个 resource 属性、两个子节点,我们参考这个配置,来分析 propertiesElement 方法的逻辑:

  • // XMLConfigBuilder.java
  • private void propertiesElement(XNode context) throws Exception {
  • if (context != null) {
  • // 解析子节点为Properties对象
  • Properties defaults = context.getChildrenAsProperties();
  • // 获取properties节点的resource和url属性
  • String resource = context.getStringAttribute("resource");
  • String url = context.getStringAttribute("url");
  • // resource和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.");
  • }
  • // resource或url放入Properties对象中
  • if (resource != null) {
  • // 解析指定路径的属性文件
  • defaults.putAll(Resources.getResourceAsProperties(resource));
  • } else if (url != null) {
  • // 解析指定url的属性文件
  • defaults.putAll(Resources.getUrlAsProperties(url));
  • }
  • Properties vars = this.configuration.getVariables();
  • if (vars != null) {
  • defaults.putAll(vars);
  • }
  • // 将解析的值设置到XPathParser和configuration中
  • this.parser.setVariables(defaults);
  • this.configuration.setVariables(defaults);
  • }
  • }
  • // XNode.java
  • public Properties getChildrenAsProperties() {
  • Properties properties = new Properties();
  • Iterator var2 = this.getChildren().iterator();
  • // 获取所有 property 节点的 name 和 value 属性
  • while(var2.hasNext()) {
  • XNode child = (XNode)var2.next();
  • String name = child.getStringAttribute("name");
  • String value = child.getStringAttribute("value");
  • if (name != null && value != null) {
  • properties.setProperty(name, value);
  • }
  • }
  • return properties;
  • }
  • // XNode.java
  • public List<XNode> getChildren() {
  • // 将链表格式的子节点数据转换为List格式,并返回
  • List<XNode> children = new ArrayList();
  • NodeList nodeList = this.node.getChildNodes();
  • if (nodeList != null) {
  • int i = 0;
  • for(int n = nodeList.getLength(); i < n; ++i) {
  • Node node = nodeList.item(i);
  • if (node.getNodeType() == 1) {
  • children.add(new XNode(this.xpathParser, node, this.variables));
  • }
  • }
  • }
  • return children;
  • }

从代码可知,首先解析 properties 的子节点和属性信息,子节点解析结果赋值给 defaults 对象;然后解析 resource 或 url 属性信息对应的文件,并将解析结果放到 defaults 对象(**属性对应文件如果和子节点出现相同 name,那么属性中相同 name 的值会覆盖相应子节点的值,这会导致同名属性覆盖的问题 **);将解析结果 defaults 对象赋值给 parser 和 configuration。

解析<settings>节点

<settings> 相关配置是 MyBatis 中非常重要的配置,这些配置用于调整 MyBatis 运行时的行为。 settings 配置繁多,在对这些配置不熟悉的情况下,保持默认配置即可。关于 <settings> 相关配置, MyBatis 官网上进行了比较详细的描述,大家可以去了解一下。在本节中,暂时还用不到这些配置,所以即使不了解这些配置也没什么关系。下面先来看一个比较简单的配置,如下:

  • <settings>
  • <setting name="cacheEnabled" value="true"/>
  • <setting name="lazyLoadingEnabled" value="true"/>
  • <setting name="autoMappingBehavior" value="PARTIAL"/>
  • </settings>

接下来,对照上面的配置,来分析相关源码

  • // XMLConfigBuilder
  • private Properties settingsAsProperties(XNode context) {
  • if (context == null) {
  • return new Properties();
  • } else {
  • // 获取 settings 子节点中的内容
  • Properties props = context.getChildrenAsProperties();
  • // 创建 Configuration 类的“元信息”对象
  • MetaClass metaConfig = MetaClass.forClass(Configuration.class, this.localReflectorFactory);
  • Iterator var4 = props.keySet().iterator();
  • // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
  • Object key;
  • do {
  • if (!var4.hasNext()) {
  • return props;
  • }
  • key = var4.next();
  • } while(metaConfig.hasSetter(String.valueOf(key)));
  • throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
  • }
  • }

如上, settingsAsProperties 方法看起来并不复杂,不过这是一个假象。在上面的代码中出现了一个陌生的类 MetaClass,这个类是用来做什么的呢?答案是用来解析目标类的一些元信息,比如类的成员变量, getter/setter 方法等。关于这个类的逻辑,待会我会详细解析。接下来,简单总结一下上面代码的逻辑。

  1. 解析 settings 子节点的内容,并将解析结果转成 Properties 对象
  2. 为 Configuration 创建元信息对象
  3. 通过 MetaClass 检测 Configuration 中是否存在某个属性的 setter 方法,不存在则抛异常
  4. 若通过 MetaClass 的检测,则返回 Properties 对象,方法逻辑结束

下面,我们重点关注一下第 2 步和第 3 步的流程。这两步流程对应的代码较为复杂,需要一点耐心阅读

元信息对象创建过程

元信息类MetaClass的构造方法为私有类型,所以不能直接创建,必须使用其提供的forClass 方法进行创建。它的创建逻辑如下:

  • public class MetaClass {
  • private final ReflectorFactory reflectorFactory;
  • private final Reflector reflector;
  • private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
  • this.reflectorFactory = reflectorFactory;
  • this.reflector = reflectorFactory.findForClass(type);
  • }
  • public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
  • return new MetaClass(type, reflectorFactory);
  • }
  • // 省略其他方法
  • }
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐