mybatis原始碼學習之執行過程分析(2)——config.xml配置檔案和mapper.xml對映檔案解析過程
在上一篇中跟蹤了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>
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中會用到。