在上一節記錄了Mapper的初始化的整個過程,本節將記錄一下Mapper的初始化過程中一個子過程:從xml解析Mapper配置。
Mapper的使用是首先定義一個mapper接口,在接口裏面對mapper的入參和返回值進行定義,然後編寫同名的mapper的xml配置文件,並在配置文件中對每一個接口的具體的sql的執行進行配置(當然使用註解也是一個很好的方式,在上一節提到了,註解的方式會覆蓋掉xml的配置,但是在框架的初始化過程中首先是解析xml的,所以本節主要記錄xml的解析主要做了什麼工作)
在上一節提到了mapper的初始主要是在MapperAnnotationBuilder中的parse方法中完成的
public void parse() {
// type是mapper的class信息
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 1、解析mapper的xml配置文件信息
loadXmlResource();
configuration.addLoadedResource(resource);
// type.getName=xml文件中的mapper的namespace
assistant.setCurrentNamespace(type.getName());
// 2、解析mapper的cache註解
parseCache();
// 3、解析mapper中cache的引用
parseCacheRef();
// 4、解析mapper類型的所有的方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
// 解析mapper接口的方法的註解信息
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
本節主要介紹上述過程中的loadXmlResource();過程主要做了什麼工作
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
// 再次判斷Mapper資源是否已經被加載
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 把類的全限定名轉換爲對於的文件的路徑
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
// 讀取xml文件資源
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
// 解析mapper對於的xml配置文件
xmlParser.parse();
}
}
}
從上面的代碼可以看出,首先將類的權限定名轉換爲文件路徑(其實類的全限定名也就是包路徑加上文件名,如在com/mh目錄下有一個文件test,那麼該test文件的類的全限定名是com.mh.test,通過替換”.”爲”/”就完成了全限定名到文件路徑的轉換,最後加上後綴”.xml”);然後將xml文件讀取出來,最後完成xml的解析。所以如果需要了解mapper的初始化過程,需要深入到xml的解析中去。
XMLMapperBuilder.parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// mapper的xml節點解析
configurationElement(parser.evalNode("/mapper"));
// 設置該mapper爲已加載的資源
configuration.addLoadedResource(resource);
// 綁定Mapper的命名空間,後續的一些mapper信息的緩存需要使用該命令空間
// 來區分不同的mapper的解析的內容。(這是由於所有mapper的解析結果會全部
// 存放在Configuration中的同一個位置,所以需要命名空間來區分不同mapper的解析結果)
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
從上面的代碼中我們可以看到,configurationElement(parser.evalNode(“/mapper”));函數是主要負責mapper節點的解析的,所以深入到該源碼裏面去看看
XMLMapperBuilder.configurationElement(XNode context)
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");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
/*
* 解析<parameterMap></parameterMap>所有的節點
* parameterMap – 已廢棄!老式風格的參數映射。內聯參數是首選,這個元素可能在將來被移除,這裏不會記錄。
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析所有resultMap的節點
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析所有的sql節點
sqlElement(context.evalNodes("/mapper/sql"));
// 解析所有的select|insert|update|delete等節點信息
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
從上面我們可以看出,會依次解析mapper下的一些子節點,我們選擇其中一個子節點的解析過程看看解析的過程,我們通過分析resultMap的解析過程來分析具體的解析內容resultMapElements(context.evalNodes(“/mapper/resultMap”));
/**
* 解析resultMap節點信息
* @param list resultMap節點列表
* @throws Exception
*/
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
// resultMapNode是代表xml中的一個resultMap節點
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
// 解析resultMap節點並生成ResultMap類
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 將解析結果封裝成爲ResultMap並添加到Configuration裏面的resultMaps中
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
在上面的代碼中我們看到最終會解析生成,具體如何添加到Configuration的resultMaps中的,可以繼續查看resultMapResolver.resolve();方法即可看到。
通過上面的ResultMap的解析過程可以看出了mapper的xml的解析過程,xml的mapper的其他節點如:parameterMap、sql、select|insert|update|delete等節點的解析過程都是一樣的,且最終都會被添加到Configuration中對應的map集合中去(問題來了,既然所有的mapper的xml的解析都會被添加到Configuration中去,那麼如何區分不同的mapper的resultMap節點等信息呢,通過mapper的namespace,詳情可以查看MapperBuilderAssistant.addResultMap方法)
MapperBuilderAssistant.addResultMap方法
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
// 爲ID應用命名空間,用於區分不同Mapper的XML中相同的ID
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 = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
configuration.addResultMap(resultMap);
return resultMap;
}