上一篇讲了mybatis初始化过程中核心文件的解析和映射文件的解析,这一篇重点讲解下mybatis中Mapper接口的绑定。
一、XMLMapperBuilder(解析映射配置文件--续)
1、绑定Mapper接口
- 每一个映射文件的命名空间可以绑定一个Mapper接口,并注册到MapperRegistry中。
private void bindMapperForNamespace() {
//获取映射文件命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//解析命名空间类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
//MapperRegistry.addMapper方法进行注册boundType接口
configuration.addMapper(boundType);
}
}
}
}
MapperRegistry的addMapper方法
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
//解析mapper中的注解
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
//检验是否已经加载
if (!configuration.isResourceLoaded(resource)) {
//没有加载的话,创建XMLmapperBuilder对象解析mapper对应的映射文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//解析CacheNameSpace注解
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
//解析@selectKey、@resultMap等注解创建MappedStatement对象
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//进行重新绑定
parsePendingMethods();
}
2、处理incomplete*集合
- 上面解析都是按xml顺序进行解析的,如果定义时候不安顺序前面需要引用后面定义的元素就会抛出异常那么mybaits会把ResultMapResolver对象暂存入incompleteResultMaps集合中,当完成一次xml解析后,还会进行incomplete集合的处理,进行重新解析,因此在完成上述解析后,mybatis还提供了三个处理incomplete集合的方法。
//处理configurationElement中解析失败的<resultMap>节点
parsePendingResultMaps();
//处理configurationElement中解析失败的<cache-ref>节点
parsePendingCacheRefs();
//处理configurationElement中解析失败的sql语句节点
parsePendingStatements();
以parsePendingResultMaps为例说明:
private void parsePendingResultMaps() {
Collection<ResultMapResolver> incompleteResultMaps =
configuration.getIncompleteResultMaps();
synchronized (incompleteResultMaps) {
Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
while (iter.hasNext()) {
try {
iter.next().resolve();//重新解析sql语句节点
iter.remove();//一处xmlstatemenBuilder对象
} catch (IncompleteElementException e) {
// ResultMap is still missing a resource...
}
}
}
}
二、解析后返回Configuration对象并封装于DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
三、总结
- mybatis的初始化过程主要分为两个方面,一个是解析核心配置文件,一个是解析映射配置文件,解析完成后的数据封装在Configuration对象中,然后封装与DefaultSqlSession对象返回。
解析核心配置文件过程
SqlSessionFactoryBuilder() .build(inputStream)-->
new XMLConfigBuilder(reader, environment, properties)-->
XMLConfigBuilder.parse()--> XMLConfigBuilder.parseConfiguration(parser.evalNode("/configuration"))
-->完成核心配置文件各个节点的解析,并把结果封装与configuration对象
解析映射配置文件的过程
XMLMapperBuilder.parse()-->
configurationElement(parser.evalNode(“/mapper”));
-->完成cache、cache-ref、resutlMap等节点的解析
-->XMLStatementBuilder用于select/insert/update/delete语句各自的解析
-->XMLStatementBuilder.parseStatementNode方法中解析了各种语句的属性和参数以及动态SQL的处理
-->调用builderAssistant.addMappedStatement方法,所有的参数和内容被构建成MappedStatement,添加到了configuration中
-->Mapper接口的绑定
-->处理incomplete*集合
-
整个初始化详细过程如图: