深入Mybatis源码——配置解析

时间:2022-08-10 13:30:27

@

前言

上一篇分析了Mybatis的基础组件,Mybatis的运行调用就是建立在这些基础组件之上的,那它的执行原理又是怎样的呢?在往下之前不妨先思考下如果是你会怎么实现。

正文

熟悉Mybatis的都知道,在使用Mybatis时需要配置一个mybatis-config.xml文件,另外还需要定义Mapper接口和Mapper.xml文件,在config文件中引入或扫描对应的包才能被加载解析(现在由于大多是SpringBoot工程,基本上都不会配置config文件,而是通过注解进行扫描就行了,但本质上的实现和xml配置没有太大区别,所以本篇仍以xml配置方式进行分析。),所以Mybatis的第一个阶段必然是要去加载并解析配置文件,这个阶段在项目启动时就应该完成,后面直接调用即可。加载完成之后,自然就是等待调用,但是我们在项目中只会定义Mapper接口和Mapper.xml文件,那具体的实现类在哪呢?Mybatis是通过动态代理实现的,所以第二个阶段应该是生成Mapper接口的代理实现类。通过调用代理类,最终会生成对应的sql访问数据库并获取结果,所以最后一个阶段就是SQL解析(参数映射、SQL映射、结果映射)。本文主要分析配置解析阶段。

配置解析

Mybatis可以通过下面的方式解析配置文件:

    final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

所以入口就是build方法(从名字可以看出使用了建造者模式,它和工厂模式一样,也是解用于创建对象的一种模式,不过与工厂模式不一样的是,前者需要我们自己参与构建的细节,而后者则不需要):

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());//解析配置文件得到configuration对象,并返回SqlSessionFactory
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

这里先是创建了一个XMLConfigBuilder对象,这个对象就是用来加载解析config文件的,先看看它的构造方法中做了些什么事情:

  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
} private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}

需要注意的是这里创建了一个Configuration对象,他就是Mybatis的核心CPU,保存了所有的配置信息,在后面的执行阶段所需要的信息都是从这个类取的,因为这个类比较大,这里就不贴详细代码了,读者请务必阅读源码熟悉该类。因为这个类对象保存了所有的配置信息,那么必然这个类是全局单例的,事实上这个对象的创建也只有这里一个入口,保证了全局唯一。

在该类的构造方法中,首先就注册了核心组件的别名和对应的类映射关系:

  public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); ......省略 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}

而注册类在实例化时同样也注册了一些基础类型的别名映射:

  public TypeAliasRegistry() {
registerAlias("string", String.class); registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class); ......省略 registerAlias("ResultSet", ResultSet.class);
}

看到这相信你就知道parameterType和resultType属性的简写是怎么实现的了。回到主流程,进入到parser.parse方法中:

  public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
} private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析<properties>节点
propertiesElement(root.evalNode("properties"));
//解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
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"));
settingsElement(settings);//将settings填充到configuration
// 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);
}
}

这个方法就是去解析配置文件中的各个节点,并将其封装到Configuration对象中去,前面的节点解析没啥好说的,自己看一下就明白了,重点看一下最后一个对mapper节点的解析,这个就是加载我们的Mapper.xml文件:

  <mappers>
<mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/CachedAuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/PostMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/NestedBlogMapper.xml"/>
</mappers>
 private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {//处理mapper子节点
if ("package".equals(child.getName())) {//package子节点
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {//获取<mapper>节点的resource、url或mClass属性这三个属性互斥
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {//如果resource不为空
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);//加载mapper文件
//实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {//如果url不为空
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);//加载mapper文件
//实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {//如果class不为空
Class<?> mapperInterface = Resources.classForName(mapperClass);//加载class对象
configuration.addMapper(mapperInterface);//向代理中心注册mapper
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

从上面的代码中我们可以看到这里有两种配置方式,一种是配置package子节点,即扫描并批量加载指定的包中的文件;另一种则是使用mapper子节点引入单个文件,而mapper节点又可以配置三种属性:resource、url、class。而解析XML的核心类是XMLMapperBuilder,进入parse方法:

  public void parse() {
//判断是否已经加载该配置文件
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));//处理mapper节点
configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
bindMapperForNamespace();//注册mapper接口
}
//处理解析失败的ResultMap节点
parsePendingResultMaps();
//处理解析失败的CacheRef节点
parsePendingCacheRefs();
//处理解析失败的Sql语句节点
parsePendingStatements();
} private void configurationElement(XNode context) {
try {
//获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置builderAssistant的namespace属性
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
//重点分析 :解析cache节点----------------1-------------------
cacheElement(context.evalNode("cache"));
//解析parameterMap节点(已废弃)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//重点分析 :解析resultMap节点(基于数据结果去理解)----------------2-------------------
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
//重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

核心的处理逻辑又是通过configurationElement实现的,接下来挨个分析几个重要节点解析过程。

1. cacheRefElement/cacheElement

这两个节点都是解析二级缓存配置的,前者是引用其它namespace的二级缓存,后者则是直接开启当前namespace的二级缓存,所以重点看后者:

  private void cacheElement(XNode context) throws Exception {
if (context != null) {
//获取cache节点的type属性,默认为PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
//找到type对应的cache接口的实现
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//读取eviction属性,既缓存的淘汰策略,默认LRU
String eviction = context.getStringAttribute("eviction", "LRU");
//根据eviction属性,找到装饰器
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//读取flushInterval属性,既缓存的刷新周期
Long flushInterval = context.getLongAttribute("flushInterval");
//读取size属性,既缓存的容量大小
Integer size = context.getIntAttribute("size");
//读取readOnly属性,既缓存的是否只读
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
//读取blocking属性,既缓存的是否阻塞
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//通过builderAssistant创建缓存对象,并添加至configuration
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
} public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
//经典的建造起模式,创建一个cache对象
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
//将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
configuration.addCache(cache);
currentCache = cache;
return cache;
} public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}

从这里我们可以看到默认创建了PerpetualCache对象,这个是缓存的基本实现类,然后根据配置给缓存加上装饰器,默认会装饰LRU。配置解析完成后,才会通过MapperBuilderAssistant类真正创建缓存对象并添加到Configuration对象中。为什么这里要通过MapperBuilderAssistant对象创建缓存对象呢?从名字可以看出它是XMLMapperBuilder的协助者,因为XML的解析和配置对象的装填是非常繁琐的一个过程,如果全部由一个类来完成,会非常的臃肿难看,并且耦合性较高,所以这里又雇佣了一个“协助者”。

2. resultMapElements

  private void resultMapElements(List<XNode> list) throws Exception {
//遍历所有的resultmap节点
for (XNode resultMapNode : list) {
try {
//解析具体某一个resultMap节点
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
} private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//获取resultmap节点的id属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//获取resultmap节点的type属性
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//获取resultmap节点的extends属性,描述继承关系
String extend = resultMapNode.getStringAttribute("extends");
//获取resultmap节点的autoMapping属性,是否开启自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//从别名注册中心获取entity的class对象
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
//记录子节点中的映射结果集合
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
//从xml文件中获取当前resultmap中的所有子节点,并开始遍历
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {//处理<constructor>节点
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {//处理<discriminator>节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {//处理<id> <result> <association> <collection>节点
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);//如果是id节点,向flags中添加元素
}
//创建ResultMapping对象并加入resultMappings集合中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//实例化resultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//通过resultMap解析器实例化resultMap并将其注册到configuration对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}

这个方法同样先是解析节点的属性,然后通过buildResultMappingFromContext方法创建ResultMapping对象并封装到ResultMapResolver中去,最后还是通过MapperBuilderAssistant实例化ResultMap对象并添加到ConfigurationresultMaps属性中:

  public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
//完善id,id的完整格式是"namespace.id"
id = applyCurrentNamespace(id, false);
//获得父类resultMap的完整id
extend = applyCurrentNamespace(extend, true); //针对extend属性的处理
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
//添加需要被继承下来的resultMapping对象结合
resultMappings.addAll(extendedResultMappings);
}
//通过建造者模式实例化resultMap,并注册到configuration.resultMaps中
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
configuration.addResultMap(resultMap);
return resultMap;
}

3. sqlElement

解析SQL节点,只是将其缓存到XMLMapperBuildersqlFragments属性中。

4. buildStatementFromContext

  private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
} private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//创建XMLStatementBuilder 专门用于解析sql语句节点
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析sql语句节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

这个方法是重点,通过XMLStatementBuilder对象解析select、update、insert、delete节点:

  public void parseStatementNode() {
//获取sql节点的id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
/*获取sql节点的各种属性*/
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); //根据sql节点的名称获取SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing
//在解析sql语句之前先解析<include>节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them.
//在解析sql语句之前,处理<selectKey>子节点,并在xml节点中删除
processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//解析sql语句是解析mapper.xml的核心,实例化sqlSource,使用sqlSource封装sql语句
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");//获取resultSets属性
String keyProperty = context.getStringAttribute("keyProperty");//获取主键信息keyProperty
String keyColumn = context.getStringAttribute("keyColumn");///获取主键信息keyColumn //根据<selectKey>获取对应的SelectKeyGenerator的id
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); //获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} //通过builderAssistant实例化MappedStatement,并注册至configuration对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
} public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) { if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
} MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}

同样的,最后根据sql语句和属性实例化MappedStatement对象,并添加到Configuration对象的mappedStatements属性中。

回到XMLMapperBuilder.parse方法中,在解析完xml之后又调用了bindMapperForNamespace方法:

  private void bindMapperForNamespace() {
//获取命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//通过命名空间获取mapper接口的class对象
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {//是否已经注册过该mapper接口?
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
//将命名空间添加至configuration.loadedResource集合中
configuration.addLoadedResource("namespace:" + namespace);
//将mapper接口添加到mapper注册中心
configuration.addMapper(boundType);
}
}
}
}

这个方法中首先通过namespace拿到xml对应的Mapper接口类型,然后委托给Configuration类中的mapperRegistry注册动态的代理的工厂MapperProxyFactory

  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 {
//实例化Mapper接口的代理工程类,并将信息添加至knownMappers
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//解析接口上的注解信息,并添加至configuration对象
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

这个就是创建Mapper接口的动态代理对象的工厂类,所以Mapper的代理对象实际上并不是在启动的时候就创建好了,而是在方法调用时才会创建,为什么会这么设计呢?因为代理对象和SqlSession是一一对应的,而我们每一次调用Mapper的方法都是创建一个新的SqlSession,所以这里只是缓存了代理工厂对象。

代理工厂注册之后还通过MapperAnnotationBuilder类提供了对注解方式的支持,这里就不阐述了,结果就是将注解的值添加到Configuration中去。

总结

解析配置文件的流程虽然比较长,但逻辑一点都不复杂,主要就是获取xml配置的属性值,实例化不同的配置对象,并将这些配置都丢到Configuration对象中去,我们只需要重点关注哪些对象被注册到了Configuration中去,最后根据Configuration对象实例化DefaultSqlSessionFactory对象并返回,而DefaultSqlSessionFactory就是用来创建SqlSession对象的,这个对象就是上一篇架构图中的接口层,它提供了所有访问数据库的操作并屏蔽了底层复杂实现细节,具体的实现原理将在下一篇进行分析。