XMLMapperBuilder源码分析


XMLMapperBuilder这个类主要是用于解析mybatis中的<mapper>标签里边的内容,怎么一步一步的引用,看下边的分析:
其实 XMLMapperBuilderXMLConfigBuilder的功能比较相似, 只是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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值