# Mybatis源码解析之配置加载
Posted 叶长风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了# Mybatis源码解析之配置加载相关的知识,希望对你有一定的参考价值。
Mybatis源码解析之配置加载(二)
这一篇是承接上一篇文章Mybatis源码解析之配置加载(一),上一篇原本是想把整个配置加载都分析完全,然后发现内容还是比较多,所以决定分成两篇来说好了,现在就开始剩下的配置分析。
配置加载
继续回到parseConfiguration方法中,**parseConfiguration()**方法如下:
private void parseConfiguration(XNode root)
try
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
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);
properties加载
我们首先看properties的加载:
private void propertiesElement(XNode context) throws Exception
if (context != null)
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null)
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
if (resource != null)
defaults.putAll(Resources.getResourceAsProperties(resource));
else if (url != null)
defaults.putAll(Resources.getUrlAsProperties(url));
Properties vars = configuration.getVariables();
if (vars != null)
defaults.putAll(vars);
parser.setVariables(defaults);
configuration.setVariables(defaults);
这里properties其实到没有多少好说的,其实就是我们一般在配置db连接的时候往往都是使用单独的一个db.properties这种,不是直接配置到xml中,当然我们上一篇文章就简便起见都配置都conf.xml文件中了,一般配置都是如下:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db
jdbc.username=root
jdbc.password=root
<!-- 加载数据库属性文件 -->
<properties resource="db.properties">
然后上述解析properties文件就是解析resource节点,得到数据库配置信息。
解析typeAliases
接下来我们看typeAliases,settings的解析与加载与properties差不多,解析typeAliases源码如下:
private void typeAliasesElement(XNode parent)
if (parent != null)
for (XNode child : parent.getChildren())
if ("package".equals(child.getName()))
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
else
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try
Class<?> clazz = Resources.classForName(type);
if (alias == null)
typeAliasRegistry.registerAlias(clazz);
else
typeAliasRegistry.registerAlias(alias, clazz);
catch (ClassNotFoundException e)
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
这里对alias进行了注册,转到registerAlias,
public void registerAlias(String alias, Class<?> value)
if (alias == null)
throw new TypeException("The parameter alias cannot be null");
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value))
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
TYPE_ALIASES.put(key, value);
实质上就是将对应的alias名与class保存进map中,最后在使用的时候再从map中取出来,这个在后面使用的时候再来讲这段。
解析插件
解析plugin的过程:
private void pluginElement(XNode parent) throws Exception
if (parent != null)
for (XNode child : parent.getChildren())
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
这里的plugin都是作为单独的一个个功能增强来使用,所以拦截器是最好的表现形式,这里的**configuration.addInterceptor()**里面就是一个InterceptorChain,这个InterceptorChain在初始化的时候会将每一个plugin加入到chain中,然后在查询的时候一个个过滤,看是否需要使用插件,这个在后面也会说到。
解析mapper
前面几种解析原理都差不多,就不再详述了,最重要的还是mapper的加载,我们转到
mapperElement(root.evalNode("mappers"))
mapperElement方法如下:
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);
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());
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的具体过程,但在我们上一篇文章中的对应配置为:
<mappers>
<mapper resource="cn/com/mapper/userMapper.xml"/>
</mappers>
所以对应的解析条件为else中的第二个if条件,如下:
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
在加载xml文件后,对xml进行了解析,进入到parse()方法中。
public void parse()
if (!configuration.isResourceLoaded(resource))
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
我们转到**configurationElement()**方法中
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"));
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. The XML location is '" + resource + "'. Cause: " + e, e);
重点都是在这个方法了,首先确定当前mapper属于哪一个namespace。
String namespace = context.getStringAttribute("namespace");
builderAssistant.setCurrentNamespace(namespace);
接下来这两行:
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
是对缓存的处理,这个就留到讲缓存的时候再说吧,暂且跳过。
最重要的是对resultMap的加载:
resultMapElements(context.evalNodes("/mapper/resultMap"));
private void resultMapElements(List<XNode> list) throws Exception
for (XNode resultMapNode : list)
try
resultMapElement(resultMapNode);
catch (IncompleteElementException e)
// ignore, it will be retried
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
return resultMapResolver.resolve();
catch (IncompleteElementException e)
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
这些里面其实最重要的就是**return resultMapResolver.resolve();**这行了,这一行对resultMap进行了最终的解析,这里面就不再讲述了,有兴趣的可以看下。
接下来看Mapper中的sql的解析:
sqlElement(context.evalNodes("/mapper/sql"));
private void sqlElement(List<XNode> list) throws Exception
if (configuration.getDatabaseId() != null)
sqlElement(list, configuration.getDatabaseId());
sqlElement(list, null);
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception
for (XNode context : list)
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrentMybatis之Mapper调用源码分析
mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)