您的位置:首页 > 移动开发

mybatis源码学习之执行过程分析(2)——config.xml配置文件和mapper.xml映射文件解析过程

2017-01-04 17:53 1371 查看
在上一篇中跟踪了SqlSessionFactory及SqlSession的创建过程。这一篇,主要跟踪Mapper接口和XML文件映射及获取。

1.xml文件的解析

1.1Mybatis-config.xml的解析

在SqlSessionFactoryBuilder中执行build()方法时,其实做了配置文件的加载和解析,以及Configuration的初始化。

SqlSessionFactoryBuilder.java

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//在这里实例化XMLConfigBuilder 时进行配置文件的加载。
//parser.parse()实现配置文件的解析,以及Configuration的初始化。
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.
}
}
}


调用XMLConfigBuilder的构造器:

XMLConfigBuilder.java

//构造方法,用来实例化XMLConfigBuilder
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
//调用了构造方法
this(new XPathParser(reader, true, props,
new XMLMapperEntityResolver()), environment, props);
}

//当XPathParser实例创建后实际调用了该构造方法实例化XMLConfigBuilder
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());    //重点:在这里创建了Configuration的实例
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
// 设置parsed状态为false,会在public Configuration parse()中做判断,保证配置文件只会被解析一次
this.parsed = false;
this.environment = environment;
this.parser = parser;
}


在这里
new XMLMapperEntityResolver()
主要用来离线检查mybatis配置文件DTDs,用来约束mybatis中的xml文件的正确性的。

再来看
XPathParser


XPathParser.java

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));    //解析并生成文档。
//mybatis-config.xml中配置的各项参数被保存在DeferredDocumentImpl中的 protected transient Object fNodeName[][]; 和 protected transient Object fNodeValue[][];中。如图

}

private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();    //通过XPathFactory工程创建XPath实例。XPath用来解析XML文件。
this.xpath = factory.newXPath();
}

private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);

factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);

/**
* DocumentBuilder 可以通过XML创建Document
*/
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}

@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}

@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);    //使用DocumentBuilder将XML文件解析成Document。
} catch (Exception e) {
throw new BuilderException("Error creating document instance.  Cause: " + e, e);
}
}




至此,
XMLConfigBuilder
的实例化操作已经完成。

接下来调用
parse()
方法,来初始化Configuration。

XMLConfigBuilder.java

public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析mybatis-config.xml文件中的<configuration>下面的配置,并设置Configuration中的属性。
parseConfiguration(parser.evalNode("/configuration"));   //从根节点<configuration>开始解析配置
return configuration;
}

//重点:在这里,通过配置文件中的配置,进一步对configuration实例进行各项配置。
//这里的解析顺序就是xml DTDs中约定的顺序。
private void parseConfiguration(XNode root) {
try {
//XMLMapper的解析在settingsAsPropertiess()方法中调用了
//具体分析见下面的Mapper.xml解析过程的分析
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);    //对Configuration做设置
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));      // configuration.addMappers()注册Mapper映射文件
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}


可以看到
XPathParser#evalNode("/configuration")
拿到了config.xml文件的根节点,然后调用
private void parseConfiguration(XNode root)
开始解析XML文件,

对应的配置文件为:

<configuration>
<typeAliases>
<typeAlias alias="User" type="com.cumt.mybatisstudy.entity.User"/>
</typeAliases>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_study"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>


Configuration的实例化:详见Configuration的分析 mybatis源码学习——Configuration类及其初始化过程、TypeHandler、TypeAlias

2.Mapper.xml内容的解析

XMLConfigBuilder.java

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);

//这里将UserMapper.xml读取为InputStream
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());
//调用XMLMapperBuilder的parse()方法,由于在一行的XMLMapperBuilder实例化中已经将Configuration传递给XMLMapperBuilder,所以parse()解析的所有Mapper配置都会记录到Configuration中。
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.");
}
}
}
}

=============   Mapper.xml映射文件的解析就在这里啦=======================
XMLMapperBuilder.java

public void parse() {
//首先会判断mapper映射文件是否已经加载过
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}

private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置NameSpace。这里用到了MapperBuilderAssistant类的帮助
builderAssistant.setCurrentNamespace(namespace);

//这两个cache标签没用过,暂时不分析
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));

//这里开始解析映射文件中的parameterMap、resultMap、以及sql语句。
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);
}
}
/**
* 在这里没有只分析最常用的resultMap的注册,其他的类似。
* sql 标签也比较常用,解析过程与resultMap类似。在这里不做追踪。
* buildStatementFromContext()值得研究,这里只认识CRUD基本操作的标签,而且解析后会注册给Configuration,在后面的Executor会拿这个信息,根据[select|insert|update|delete]类型的不同调用不同的方法去执行sql和包装ResultSet。
*/

XMLConfigBuilder.java

private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode); //调用
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}

XMLMapperBuilder.java

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}

//在这里亲切的看到了返回类型是ResultMap
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
id = applyCurrentNamespace(id, false);
extend = applyCurrentNamespace(extend, true);

if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
resultMappings.addAll(extendedResultMappings);
}

//就是这里创建了ResultMap
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();

//将ResultMap 注册给万能的configuration 2333
configuration.addResultMap(resultMap);
return resultMap;
}


调用栈信息如下图:



ResultMap.java

public ResultMap build() {
if (resultMap.id == null) {
throw new IllegalArgumentException("ResultMaps must have an id");
}
resultMap.mappedColumns = new HashSet<String>();
resultMap.idResultMappings = new ArrayList<ResultMapping>();
resultMap.constructorResultMappings = new ArrayList<ResultMapping>();
resultMap.propertyResultMappings = new ArrayList<ResultMapping>();
for (ResultMapping resultMapping : resultMap.resultMappings) {
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
final String column = resultMapping.getColumn();
if (column != null) {
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
} else if (resultMapping.isCompositeResult()) {
for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
final String compositeColumn = compositeResultMapping.getColumn();
if (compositeColumn != null) {
resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
}
}
}
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
resultMap.constructorResultMappings.add(resultMapping);
} else {
resultMap.propertyResultMappings.add(resultMapping);
}
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
resultMap.idResultMappings.add(resultMapping);
}
}
if (resultMap.idResultMappings.isEmpty()) {
resultMap.idResultMappings.addAll(resultMap.resultMappings);
}
// lock down collections 设置这些Mapping为只读的。java集合中的内容
resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
return resultMap;
}
}


在来看
XMLMapperBuilder#parse()
方法中的
bindMapperForNamespace();
方法:

XMLMapperBuilder.xml

private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
//记录已经加载过的映射文件,防止重复加载
configuration.addLoadedResource("namespace:" + namespace);
//这里是调用MapperRegistry添加Mapper,其实就是向map里添加数据
configuration.addMapper(boundType);
}
}
}
}


这回该看这3个方法了。

//TODO 这里先打标记,后面搞明白这里的用意再写。

parsePendingResultMaps();


parsePendingChacheRefs();


parsePendingStatements();


至此,
XMLConfigBuilder#mapperElement(XNode parent)
方法调用返回,
XMLConfigBuilder#parseConfiguration(XNode root)
的调用也结束并返回。调用栈信息如下图:



2.总结

config.xml和mapper.xml的加载和解析主要油XMLConfigBuilder和XMLMapperBuilder两个类中的对应方法完成。最终解析的所有内容都会注册到我们King类——Configuration中,这些信息在Executor中以及ResultSet结果集wrapper中会用到。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  xml mybatis 源码