上篇我們準備好了debug環境,這篇我們具體深入分析下SqlSessionFactory 的建立
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1、SqlSessionFactoryBuilder建立SqlSessionFactory對象
1.1)調用重載方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
1.2)建立XMLConfigBuilder對象,調用parse方法解析XML配置檔案
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
1.3)解析成Configuration對象,然後再調用重載方法,建立SqlSessionFactory對象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
2、分析XMLConfigBuilder的parse方法,解析配置檔案
public Configuration parse() {
if (parsed) { // 第一次進入解析時,會在下面将parsed設定true,保證隻能解析一次
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
2.1)parseConfiguration(parser.evalNode("/configuration"));
解析配置檔案configuration标簽,我們主要分析下第一篇中配置的properties,environments,mappers是三個标簽
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
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"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.2)properties标簽解析
解析resource和url方式引入的配置屬性,而且如果resource和url同時不為null就會抛出BuilderException,證明隻能使用其中的一種。最後将這些屬性儲存到configuration對象中,例子中配置的這些屬性是environments中的資料源屬性
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
// 解析properties标簽是否有resource屬性
String resource = context.getStringAttribute("resource");
// 解析properties标簽是否有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.");
}
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);
// 将properties屬性儲存到configuration對象
configuration.setVariables(defaults);
}
}
2.3)environments标簽解析
2.3.1)解析transactionManager子标簽type類型為JDBC,建立事務工廠,用于建立事務
2.3.2)解析dataSource子标簽屬性type為POOLED,建立資料源工廠,用于建立資料源
2.3.3)封裝事務工廠和資料源到Environment對象中,set到Configuration對象中
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 建立事務工廠
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 建立資料源工廠
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 擷取資料源
DataSource dataSource = dsFactory.getDataSource();
// 通過Builder建立Environment對象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 設定Environment對象到configuration對象中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
3)mappers标簽解析
通過package,resource,url,class四種方式加載mapper檔案,由于我們配置的是resource方式,通過XMLMapperBuilder加載的配置檔案,是以直接分析mapperParser.parse();
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// *********************resource解析mapper檔案邏輯*************************
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
// *********************resource解析mapper檔案邏輯*************************
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
4)mapperParser.parse(); 解析mapper标簽
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper标簽,解析sql封裝MappedStatement對象儲存到configuration中
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 建立Mapper接口的代理工廠類,掃描Mapper接口方法的注解,封裝sql
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
4.1)configurationElement(parser.evalNode("/mapper"));
4.1.1)解析擷取mapper檔案的namespace
4.1.2)解析sql(SqlSource 封裝)調用棧如下
buildStatementFromContext(list, null);
statementParser.parseStatementNode();
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
builder.parseScriptNode();
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
4.1.3)将SqlSource和一系列參數封裝到MappedStatement,通過 configuration.addMappedStatement(statement);儲存到configuration對象中,後續在調用mapper接口方法時,會從這裡取出sql執行
4.2)bindMapperForNamespace(); // 綁定mapper接口和對應的mapper代理類
4.2.1)調用棧如下,後續可以通過sqlSession對象根據mapper類型來擷取對應的mapper代理對象
configuration.addMapper(boundType);
mapperRegistry.addMapper(type);
knownMappers.put(type, new MapperProxyFactory<>(type));
4.2.2)下面還有一個很重要的處理邏輯,就是讀取mapper接口方法的注解,建立SqlSource對象,最後再封裝成MappedStatement對象儲存到configuratioin中mappedStatements屬性中。調用棧如下
knownMappers.put(type, new MapperProxyFactory<>(type));
// 在put代理工廠後
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
parseStatement(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
buildSqlSourceFromStrings(strings, parameterType, languageDriver);
assistant.addMappedStatement(...)
5)至此XMLConfigBuilder的parse方法解析完畢,傳回configuration對象,再調用build方法建立DefaultSqlSessionFactory對象,SqlSessionFactory初始化完成
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}