在解析mybatis的全局配置文件的子节点时,是有4种情况的:
<mapper resource="com/blog4java/mybatis/example/mapper/UserMapper.xml"/>
<mapper url="file:///mybatis/com/blog4java/mybatis/example/mapper/UserMapper.xml"/>
<mapper class="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper.UserMapper"/>
<package name="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper"/>
MyBatis源码中同样也是对于这是4种情况进行了处理:
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) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} 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.");
}
}
}
}
}
1、解析package标签
-
首先是获取到了中配置的需要解析的包的路径:String mapperPackage =
child.getStringAttribute(“name”); -
然后是将包路径作为参数调用了Configuration中的addMappers方法
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
- 在Configuration中的addMappers则是将包名作为参数调用了MapperRegister中的addMappers方法
/**
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* @since 3.2.2
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
-
然后我们就可以看到MapperRegister中的addMappers方法调用了重载的addMappers(String
packageName, Class<?> superType) 方法- 对于addMappers中的具体实现是依赖于ResolverUtil这个工具类的
- resolverUtil.find(new ResolverUtil.IsA(superType),
packageName);这个方法的内部实现是获取packageName这个包下的所有是superType子类的类的Class对象,并将其保存在了Set<Class<?
extends T>> matches = new HashSet<Class<? extends T>>();中 - resolverUtil.getClasses();这个方法就是祛除了matches中的所有对象
- 最后遍历获取到的指定包下的所有Class对象,并调用MapperRegistry中的addMapper方法,将其真正的进行注册到
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new
HashMap<Class<?>,
MapperProxyFactory<?>>();中Key值为Mapper的Class类型,value值为对应的MapperProxyFactory实例(MapperRegistry中提供了getMapper()方法,能根据Mapper接口的Class对象获取对应的MapperProxyFactory对象,然后就可以使用MapperProxyFactory对象创建Mapper动态代理对象了)
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));
// 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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
2、解析类型的标签
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 专门用来解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 通过XMLMapperBuilder解析mapper映射文件
mapperParser.parse();
XMLMapperBuilder是专门用来解析mapper映射文件的,看下解析的流程:
其中的parse()方法如下:
public void parse() {
// mapper映射文件是否已经加载过(判断Configuration的loadedResources的Set集合中是否有了这个资源路径)
if (!configuration.isResourceLoaded(resource)) {
// 从映射文件中的<mapper>根标签开始解析,直到完整的解析完毕
//调用XPathParser的evalNode()方法获取根节点对应的XNode对象
configurationElement(parser.evalNode("/mapper"));
//将资源路径添加到Configuration对象中(添加到了Configuration的loadedResources的Set集合中)
// 标记已经解析
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
//继续解析之前解析出现异常的ResultSetMap
parsePendingResultMaps();
//继续解析之前解析出现异常的CacheRefs
parsePendingCacheRefs();
//继续解析之前解析出现异常的<select|update|delete|insert>标签配置
parsePendingStatements();
}
我们先来看下 bindMapperForNamespace();方法,其实这个方法就是对于Mapper的注册和上面对于Package标签的注入是差不多的,都是会调用Configuration中的addMapper方法,最终会将其放到knownMappers.put(type, new MapperProxyFactory(type));的knownMappers中,为后面的调用Mapper生成Mapper的动态代理对象做好准备
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)) {
// 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.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
重点来看下 configurationElement(parser.evalNode("/mapper"));这个方法,这个方法就是解析标签的内容的:
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");
}
// 设置当前的命名空间为namespace的值
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>子标签
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>子标签
cacheElement(context.evalNode("cache"));
// 解析<parameterMap>子标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析<resultMap>子标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析<sql>子标签,也就是SQL片段
sqlElement(context.evalNodes("/mapper/sql"));
// 解析<select>\<insert>\<update>\<delete>子标签
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);
}
我们看一下其中的两个方法:
- sqlElement(context.evalNodes("/mapper/sql")); // 解析子标签,也就是SQL片段
- buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));//
解析<insert><update><delete>子标签
首先来看对于子标签的解析:
private void sqlElement(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
对于每一个子标签进行循环处理,最终是将这些子标签放到了sqlFragments的Map中,其中Key值是当前Mapper.xml的NameSpace + “.” + 子标签中的属性id的值;然后Value是这个子标签的XNode的引用;
所以在sqlFragments中存放了所有子标签的内容
以下是对MapperedStatement的注册:
再来看下buildStatementFromContext方法,这也是最重要的一个方法,是对<insert><update><delete>四个子标签进行解析,并在最终调用了buildStatementFromContext方法,构建了XMLStatementBuilder来解析select等4个标签,创建MappedStatement对象
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// MappedStatement解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析select等4个标签,创建MappedStatement对象
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
来看一下parseStatementNode方法的具体实现:
public void parseStatementNode() {
// 获取statement的id属性(特别关键的值)
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
// 获取入参类型
String parameterType = context.getStringAttribute("parameterType");
// 别名处理,获取入参对应的Java类型
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取ResultMap
String resultMap = context.getStringAttribute("resultMap");
// 获取结果映射类型
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 别名处理,获取返回值对应的Java类型
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 设置默认StatementType为Prepared,该参数指定了后面的JDBC处理时,采用哪种Statement
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
// 解析SQL命令类型是什么?确定操作是CRUD中的哪一种
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
// <include>标签解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 解析<selectKey>标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 通过构建者助手,创建MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
最终会通过builderAssistant构建助手对象来创建MapperedStatemnt并存入到Configuration对象的MapperedSatements的Map中;
3、解析类型的标签
这个和解析类型标签的逻辑是一样的
4、解析类型的标签
对于这一标签类型的解析是比较简单的,直接调用Configuration的addMapper方法就可以了,传入指定的类的Class对象