XMLMapperBuilder这个类主要是用于解析mybatis中的<mapper>标签里边的内容,怎么一步一步的引用,看下边的分析:
其实
XMLMapperBuilder和
XMLConfigBuilder的功能比较相似,
只是XMLConfigBuilder的作用范围,或者说包括的范围比较大,其中<mapper>标
签就是其中解析的一部分,看过源码的其实应该就知道。我接下来单独抽出来相关的部分代码做解释。
//通过XMLConfigBuilder解析mybatis配置,然后创建SqlSessionFactory对象
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//下面看看这个方法的源码
return build(
parser.parse());
} 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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
接下来看XMLConfigBuilder这个类,看怎么一步一步的到XMLMapperBuilder这个类的。
/**
* mybatis 配置文件解析
*/
public class
XMLConfigBuilder extends BaseBuilder {
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, 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;
}
//外部调用此方法对mybatis配置文件进行解析
public Configuration
parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//从根节点configuration
parseConfiguration(parser.evalNode("/
configuration"));
return configuration;
}
//此方法就是解析configuration节点下的子节点
//由此也可看出,我们在configuration下面能配置的节点为以下10个节点
private void
parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("
properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("
typeAliases"));
pluginElement(root.evalNode("
plugins"));
objectFactoryElement(root.evalNode("
objectFactory"));
objectWrapperFactoryElement(root.evalNode("
objectWrapperFactory"));
settingsElement(root.evalNode("
settings"));
environmentsElement(root.evalNode("
environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("
databaseIdProvider"));
typeHandlerElement(root.evalNode("
typeHandlers"));
mapperElement(root.evalNode("
mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
注意:在上述代码中,有一个非常重要的地方,
就是解析XML配置文件子节点<mappers>的方法mapperElements(root.evalNode("mappers")),
它将解析我们配置的Mapper.xml配置文件
,Mapper配置文件可以说是MyBatis的核心,MyBatis的特性和理念都体现在此Mapper的配置和设计上。
然后将这些值解析出来设置到Configuration对象中。
看下
mapperElement
方法的源码
:
/**
* mappers 节点解析
* 这是mybatis的核心之一,这儿先简单介绍,在接下来的文章会对它进行分析
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//如果mappers节点的子节点是package, 那么就扫描package下的文件, 注入进configuration
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//resource, url, class 三选一
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//mapper映射文件都是通过XMLMapperBuilder解析
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.");
}
}
}
}
}
到这里,XMLMappaerBuilder就引出来了,然后就开始看XMLMapperBuilder这个类是怎么处理我们平常配置的核心标签<mapper>中的xml语句的。其实它跟XMLConfigBuilder这个类的套路是一样的,看我上边标黄的部分,先得到类,然后调用parse()方法,当然继续看下边它的具体的实现方式(我只拿重点代码出来)
:
private
XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments)
{
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
public void
parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
configurationElement(this.parser.evalNode("/
mapper"));
this.configuration.addLoadedResource(this.resource);
bindMapperForNamespace();
}
//下边三个方法都是存储信息到configuration中。
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
方法的分别实现:
private void
configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("
namespace");
if (namespace.equals("")) {
throw new BuilderException("
Mapper's namespace cannot be empty");
}
this.builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("
cache-ref"));
cacheElement(context.evalNode("
cache"));
parameterMapElement(context.evalNodes("/
mapper/parameterMap"));
resultMapElements(context.evalNodes("
/mapper/resultMap"));
sqlElement(context.evalNodes("
/mapper/sql"));
buildStatementFromContext(context.evalNodes("
select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("
Error parsing Mapper XML. Cause: " + e, e);
}
}
我们知道每个mapper配置文件的namespace属性对应于某个接口,应用程序通过接口访问mybatis时,mybatis会为这个接口生成一个
代理对象
,这个对象就叫
mapper对象
,在生成代理对象前mybatis会校验接口是否已注册,未注册的接口会产生一个异常。为了避免这种异常,就需要注册mapper类型。这个步骤是在XMLMapperBuilder的
bindMapperForNamespace
方法中完成的。它通过调用Configuration对象的addMapper方法完成,而Configuration对象的addMapper方法是通过MapperRegistry的addMapper方法完成的,它只是简单的将namespace属性对应的接口类型存入本地缓存中。
另外,我们使用Mapper接口的某一个方法时,MyBatis会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select("statementId",parameterObject);
private void
bindMapperForNamespace() {
String namespace = this.builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
boundType = Resources.classForName(namespace);//得到mapper接口实现类,实现映射
}
catch (ClassNotFoundException e) {
}
if ((boundType != null) &&
(!this.configuration.hasMapper(boundType)))
{
this.configuration.addLoadedResource("namespace:" + namespace);
this.configuration.
addMapper
(boundType);
}
}
}
Configuration对象提供了一个
重载
的addMappers
(StringpackageName)方法,该方法以包路径名为参数,它的功能是
自动扫描包路径下的接口并注册到MapperRegistry的缓存中
,
同时扫描包路径下的mapper配置文件并解析之
。解析配置文件是在MapperAnnotationBuilder类的parse方法里完成的,该方法先解析配置文件,然后再解析接口里的注解配置,且注解里的配置会覆盖配置文件里的配置,也就是说注解的优先级高于配置文件,这点需要注意(
补充:注解指的是集成spring时候,通过注解的形式进行扫描文件,配置文件则是单独的指向某一个配置文件,这点了解一下
)。
重载方法
如下:
public void
addMappers(String packageName, Class<?> superType) {
this.mapperRegistry.addMappers(packageName, superType);
}
public void
addMappers(String packageName) {
this.mapperRegistry.addMappers(packageName);
}
public <T> void
addMapper(Class<T> type) {
this.mapperRegistry.addMapper(type);
}