- 入口使用
Resources.getResourceAsStream()方法獲取字節(jié)輸入流
public class MybatisTest {
/**
* 傳統(tǒng)方式
* @throws IOException
*/
@Test
public void test1() throws IOException {
// 1. 讀取配置文件,讀成字節(jié)輸入流,注意:現(xiàn)在還沒解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
}
}
- 點進去看
getResourceAsStream()其實傳了一個 null 的類加載器和核心配置文件的路勁下去
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
- 繼續(xù)往下點,又調(diào)用了
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 類中找到了類加載器和真正讀成字節(jié)流的方法
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 數(shù)組
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入口,使用構(gòu)建者模式創(chuàng)建一個 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
- 點進去之后發(fā)現(xiàn),其實調(diào)用了一個重載的方法,傳遞三個參數(shù),除了配置文件的字節(jié)流之外,其余都傳了 null 值
// 我們最初調(diào)用的build
public SqlSessionFactory build(InputStream inputStream) {
//調(diào)用了重載方法
return build(inputStream, null, null);
}
- 點進去查看這個重載方法
// 調(diào)用的重載方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 創(chuàng)建 XMLConfigBuilder, XMLConfigBuilder是專門解析mybatis的配置文件的類
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 執(zhí)行 XML 解析
// 創(chuàng)建 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解析器對象,讀取節(jié)點內(nèi)數(shù)據(jù),<configuration>是MyBatis配置文件中的頂層標簽
// 解析 XML configuration 節(jié)點
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
- 先獲取一個頂層的 configuration 節(jié)點,然后調(diào)用
parseConfiguration()這個方法,可以看到,這里就是對各種標簽進行解析
/**
* 解析 XML
*
* 具體 MyBatis 有哪些 XML 標簽,參見 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
*
* @param root 根節(jié)點
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析 <properties /> 標簽
propertiesElement(root.evalNode("properties"));
// 解析 <settings /> 標簽
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加載自定義的 VFS 實現(xiàn)類
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 對象到上面的結(jié)果。
* 3. 設(shè)置結(jié)果到 parser 和 configuration 中
*
* @param context 節(jié)點
* @throws Exception 解析發(fā)生異常
*/
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);
}
// 設(shè)置 defaults 到 parser 和 configuration 中。
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
- 解析完成之后,返回一個 configuration 對象,該對象中包含了一個 mappedStatements,其數(shù)據(jù)結(jié)構(gòu)就是一個 map,根據(jù) namespace.id 存放一個 MappedStatement 對象,之前的自定義持久層框架也是借鑒了這個思路
/**
* MappedStatement 映射
*
* KEY:`${namespace}.${id}`
*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");