MyBatis 源码分析 - 配置文件解析过程,java报表系统技术方案

propertiesElement(root.evalNode(“properties”));

// 解析 settings 配置,并将其转换为 Properties 对象

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

// 解析 environments 配置

environmentsElement(root.evalNode(“environments”));

// 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象

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

}

}

到此,一个 MyBatis 的解析过程就出来了,每个配置的解析逻辑都封装在了相应的方法中。在下面分析过程中,我不打算按照方法调用的顺序进行分析,我会适当进行一定的调整。同时,MyBatis 中配置较多,对于一些不常用的配置,这里会略过。那下面我们开始进行分析吧。

2.2 解析 properties 配置

解析properties节点是由propertiesElement这个方法完成的,该方法的逻辑比较简单。在分析方法源码前,先来看一下 properties 节点的配置内容。如下:

在上面的配置中,我为 properties 节点配置了一个 resource 属性,以及两个子节点。下面我们参照上面的配置,来分析一下 propertiesElement 的逻辑。相关分析如下。

// -☆- XMLConfigBuilder

private void propertiesElement(XNode context) throws Exception {

if (context != null) {

// 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties

Properties defaults = context.getChildrenAsProperties();

// 获取 propertis 节点中的 resource 和 url 属性值

String resource = context.getStringAttribute(“resource”);

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.”);

}

if (resource != null) {

// 从文件系统中加载并解析属性文件

defaults.putAll(Resources.getResourceAsProperties(resource));

} else if (url != null) {

// 通过 url 加载并解析属性文件

defaults.putAll(Resources.getUrlAsProperties(url));

}

Properties vars = configuration.getVariables();

if (vars != null) {

defaults.putAll(vars);

}

parser.setVariables(defaults);

// 将属性值设置到 configuration 中

configuration.setVariables(defaults);

}

}

public Properties getChildrenAsProperties() {

Properties properties = new Properties();

// 获取并遍历子节点

for (XNode child : getChildren()) {

// 获取 property 节点的 name 和 value 属性

String name = child.getStringAttribute(“name”);

String value = child.getStringAttribute(“value”);

if (name != null && value != null) {

// 设置属性到属性对象中

properties.setProperty(name, value);

}

}

return properties;

}

// -☆- XNode

public List getChildren() {

List children = new ArrayList();

// 获取子节点列表

NodeList nodeList = node.getChildNodes();

if (nodeList != null) {

for (int i = 0, n = nodeList.getLength(); i < n; i++) {

Node node = nodeList.item(i);

if (node.getNodeType() == Node.ELEMENT_NODE) {

// 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中

children.add(new XNode(xpathParser, node, variables));

}

}

}

return children;

}

上面是 properties 节点解析的主要过程,不是很复杂。主要包含三个步骤,一是解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。二是从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。第二步对应的代码比较简单,这里就不分析了。有兴趣的话,大家可以自己去看看。最后一步则是将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。

需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。比如上面配置中的jdbc.properties内容如下:

jdbc.driver=com.mysql.cj.jdbc.Driver

jdbc.url=jdbc:mysql://localhost:3306/myblog?..

jdbc.username=root

jdbc.password=1234

与 properties 子节点内容合并后,结果如下:

img

如上,原jdbc.username值为coolblog,现在被覆盖为了root。同名属性覆盖的问题需要大家注意一下,其他的就没什么了,继续往下分析。

2.3 解析 settings 配置

2.3.1 settings 节点的解析过程

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

接下来,对照上面的配置,来分析源码。如下:

// -☆- XMLConfigBuilder

private Properties settingsAsProperties(XNode context) {

if (context == null) {

return new Properties();

}

// 获取 settings 子节点中的内容,getChildrenAsProperties 方法前面已分析过,这里不再赘述

Properties props = context.getChildrenAsProperties();

// 创建 Configuration 类的“元信息”对象

MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);

for (Object key : props.keySet()) {

// 检测 Configuration 中是否存在相关属性,不存在则抛出异常

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;

}

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

  1. 解析 settings 子节点的内容,并将解析结果转成 Properties 对象

  2. 为 Configuration 创建元信息对象

  3. 通过 MetaClass 检测 Configuration 中是否存在某个属性的 setter 方法,不存在则抛异常

  4. 若通过 MetaClass 的检测,则返回 Properties 对象,方法逻辑结束

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

2.3.2 元信息对象创建过程

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

public class MetaClass {

private final ReflectorFactory reflectorFactory;

private final Reflector reflector;

private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {

this.reflectorFactory = reflectorFactory;

// 根据类型创建 Reflector

this.reflector = reflectorFactory.findForClass(type);

}

public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {

// 调用构造方法

return new MetaClass(type, reflectorFactory);

}

// 省略其他方法

}

上面的代码看起来很简单,不过这只是冰山一角。上面代码出现了两个新的类ReflectorFactoryReflector,MetaClass 通过引入这些新类帮助它完成功能。下面我们看一下hasSetter方法的源码就知道是怎么回事了。

// -☆- MetaClass

public boolean hasSetter(String name) {

// 属性分词器,用于解析属性名

Prope

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值