首先附上一张简单的MyBatis的配置文件xml图,便于后续查看
在上篇中定位到源代码XMLConfigBuilder类中的parse()方法
public Configuration parse() {
if(this.parsed) {
throw new BuilderException("Each MapperConfigParser can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
上篇了解到(this.parser.evalNode("/configuration")返回了MyBatis配置文件中的configuration节点下的所有内容,那么继续看下parseConfiguration()方法
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.settingsElement(root.evalNode("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);
}
}
在这个方法中接受传来的XNode的congfiguration节点内容,可以看到有关解析配置文件里的一些节点方法,有plugins,settings,environments的,还有mappers就是包含我们所有mapper.xml标签(单独的Mybatis,未整合spring),那进入到mapperElement方法中,依旧是XMLCongfigBuilder类中的方法:
private void mapperElement(XNode parent) throws Exception {
if(parent != null) {
Iterator i$ = parent.getChildren().iterator();
while(true) {
while(i$.hasNext()) {
XNode child = (XNode)i$.next();
String resource;
if("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if(resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if(resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if(resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
那这个方法就越来越清楚了,首先判断XNode不为空,就是mappers,然后获得XNode的getChildren().iterator();mappers下的子节点mapper.xml,getChildren()方法返回的是List<XNode>所以可以调用iterator()来遍历。
得到了 XNode child = (XNode)i$.next(); 子节点之后,根据child.getName(),返回当前节点名并且判断是否等于package,显然不等于(节点名是mapper),随后开始在执行else下的代码
resource = child.getStringAttribute("resource");
通过getStringAttribute("resource")方法拿到遍历的每个mapper节点的属性名为"resource"的值,那就是这篇文章的上图中
<mapper resource="/UserMapper.xml" />这里的xml路径,下面的两行代码
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
通过配置文件看到我们暂时没有用到,所以走下面if判断的第一个分支
if(resource != null && url == null && mapperClass == null)
第一行代码先略过-_- ErrorContext.instance().resource(resource);
下面是很熟悉的代码
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
这里呢依旧是之前那样的步骤,和解析MyBatis配置文件一样解析mapper.xml文件,不多说,很稳!
那还是进到parse()方法看看
public void parse() {
if(!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingChacheRefs();
this.parsePendingStatements();
}
首先还是判断文件是否被解析,如果解析了的话,先放一下这个点,先看未解析的步骤。
首先依旧是this.parser.evalNode("/mapper"),返回mapper节点下的所有内容,是XNode类型,在通过configurationElement()方法解析
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
this.sqlElement(context.evalNodes("/mapper/sql"));
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception var3) {
throw new RuntimeException("Error parsing Mapper XML. Cause: " + var3, var3);
}
}
看到方法里有获得属性getStringAttribute("")方法,有常见的namespace,parameterMap等等比较重要的,
可以看下setCurrentNamespace()方法里代码写了,规定了namespace不能为空
其他的解析方法都能看懂,这里直接看下面的context.evalNodes("select|insert|update|delete")这行代码,
这里就是返回所有select或ins,或up,或的节点内容,返回值依旧是List<XNode>,现在进入到XMLMapperBuilder类的
buildStatementFromContext方法
private void buildStatementFromContext(List<XNode> list) {
if(this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
this.buildStatementFromContext(list, (String)null);
}
this.configuration.getDatabaseId()为空,继续执行他的重载方法
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator i$ = list.iterator();
while(i$.hasNext()) {
XNode context = (XNode)i$.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
然后看到依然是,初始化,在调用初始化类的方法,发现整个程序的逻辑,以及代码的编写,基本都是这个样子的,在看下通过初始化的类调用的他的parseStatementNode()的这个方法。
到现在,第二篇,发现了从一开始的返回整个Configuration后,再次获得Mappers的内容,然后遍历Mappers中的mapper,
再次解析每一个mapper.xml。 在每个mapper标签中判断是否有namespace属性,继续解析xml方式得到select,insert,update,delete的标签节点,得到List<XNode>,最后遍历List<XNode>中的每一个的元素以及内容,这里就到了如何具体的解析具体的sql 标签了。下一篇继续在看吧。