MyBatis 3.5.4源码之旅三之mapper映射文件解析一
对照的流程图
XMLConfigBuilder的mapper映射文件解析一
我们的映射配置解析就是在这里做的,包含了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.");
}
}
}
}
}
package结点解析
以package
为例子:
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="com.ww.dao"/>
</mappers>
直接调用configuration.addMappers(mapperPackage);
最终会调用到MapperRegistry
的addMappers
:
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
这个代码很熟悉吧,跟前面别名的获取类似,只是最后是addMapper(mapperClass);
。我们来看看怎么做的,其实处理的都是接口:
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
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();//解析
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
我们可以看到MapperProxyFactory
里面其实就是存了接口Class
对象:
MapperAnnotationBuilder的parse解析接口类对应的xml文件
然后用MapperAnnotationBuilder
进行解析parser.parse();
:
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {//没加载的话
//加载映射文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//添加缓存
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();//遍历所有方法解析
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
其中有个loadXmlResource();
方法,告诉我们为什么要把xml
映射文件放在同包下且要同名了,其实就是把.
换成可/
最后加.xml,请看:
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//同包下且要同名,把.换成可/最后加.xml
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {//如果能找到就用XMLMapperBuilder 解析
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
原来他是通过替换分隔符然后加后缀名来表示xml
映射文件路径的,然后创建XMLMapperBuilder
去解析:
XMLMapperBuilder的parse解析映射xml
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
然后先看configurationElement
这个就是在解析映射文件了,具体都有对应的标签,一般的就不多说了:
其中比较重要的就是buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
,解析CRUD
语句的:
这个还是比较复杂的,下次说吧。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。