XMLMapperBuilder源码分析

来源:互联网 发布:jsp项目源码 编辑:程序博客网 时间:2024/05/18 13:08
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> voidaddMapper(Class<T> type) {
this.mapperRegistry.addMapper(type);
}
原创粉丝点击