在SqlSessionFactoryBuilder中存在这样一个方法,所有的的build都会调用下面的这个方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//构建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse() 将输入流转换为Configuration对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 此处的去解析Configuration配置的代码
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
由上面的代码我们可以知道是由XMLConfigBuilder去解析XML文件,并生成最终的Configuration对象的。
我们再进入上方的函数,会进入到对于xml配置的根(configuration)的解析。也就是对于一https://blog.csdn.net/liu20111590/article/details/81293649 中的mybatis.xml文件中的所有内容的解析。此时我们可以看到最后一句话,是对于mapper的解析。
private void parseConfiguration(XNode root) {
try {
//对于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"));
// 设置setting的值
settingsElement(settings);
// 对于environments的解析
environmentsElement(root.evalNode("environments"));
// 对于databaseIdProvider的解析
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 对于typeHandler的解析
typeHandlerElement(root.evalNode("typeHandlers"));
// 对于mapper的解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
<mappers>
<mapper url=""/>
<mapper resource="mapper/supplier-label-mapper.xml"/>
mappers>
我们可以看到对于mapper的用法有四种,
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.");
}
}
}
}
}
由上方的四种解析方式中,分为了两种,一种是调用Configuration,addMapper访问,一种是构建XMLMapperBuiler,然后来进行mapper的解析。接下来对于这两种进行一下解析。
进入到Configuration类中,我们可以看到三个方法
/**
* 将指定包下面的指定接口进行加载
*/
public void addMappers(String packageName, Class> superType) {
mapperRegistry.addMappers(packageName, superType);
}
/**
* 加载指定包下面的所有接口
*/
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
/**
* 加载指定的接口
*/
public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
由这三种我们可以看到对于mapper的加载是委托给mapperRegistry来进行的,我们可以来看一下默认的mapperRegistry
public class MapperRegistry {
private final Configuration config;
private final Map, MapperProxyFactory>> knownMappers = new HashMap, MapperProxyFactory>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public boolean hasMapper(Class type) {
return knownMappers.containsKey(type);
}
public void addMapper(Class 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(type));
// 创建一个MapperAnnotationBuilder来对xml或者类进行解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/**
* @since 3.2.2
*/
public Collection> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
/**
* @since 3.2.2
*/
public void addMappers(String packageName, Class> superType) {
// 构建解析过滤器
ResolverUtil> resolverUtil = new ResolverUtil>();
// 过滤查找packageName下面所有的superType类的子类或者实现类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set>> mapperSet = resolverUtil.getClasses();
// 将查找到的所有类进行添加
for (Class> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* @since 3.2.2
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
}
我们深入代码,会发现,对于包下面的所有接口、包下面指定的接口、指定的接口解析都委托给了MapperAnnotationBuilder类去进行解析
MapperAnnotationBuilder,根据名字我们可以猜测,是用来解析和mapper相关的注解的。对于注解的解析,我们这期不关注。我们只关注从xml文件中载入和mapper相关的内容。
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 载入xml文件的内容
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()方法,我们继续进入到这个方法。
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())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
我们可以看到,对于Mapper的解析委托给了XmlMapperBuilder类。而在之前根据资源加载mapper的时候,也是委托给了XmlMapperBuilder来进行解析。由此我们可以确定对于mybatis中的mapper.xml都是交由XmlMapperBuilder来进行解析。