- 入口使用
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;
}
- 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");