###描述一下mybatis是怎么解析xml的?
1、Xml配置构建器,和它的parse方法
SqlSessionFactoryBuilder类中有build的具体代码。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
我们可以看到XMLConfigBuilder 对象。XMLConfigBuilder 这个对象获取了build方法传入的流,构造了一个XML配置文件解析器。并且在下一行调用了解析器的parse方法。让我们来猜一猜,这个解析器会做什么事情呢?它肯定是1)将xml封装成了dom对象,然后获取了xmlDOM中的各个属性,将其封装到了Configuration 中,别问我为什么,因为build的参数是Configuration。
2、parse方法里做了什么?
那么现在让我们来证实一下。
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"));
我们在XMLConfigBuilder们类中发现上面的代码被调用了。查看h核心配置文件的dtd约束文件可以发现,evalNode方法中的参数字符串,都是核心配置文件中可以被定义的元素。你可以去印证一下。
3、Mappers-mapperElement方法
我们发现了mappers这个字眼。它和我们配置在核心配置中的,指示mapper.xml文件位置的标签名是一样的。那mapperElement方法里面到底是什么呢?
一下是点进方法mapperElement方法里获取到的代码。
一下是点进方法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.");
}
}
}
可以发现,for循环遍历了mappers的子节点。当然,mappers的子节点可能是下面这样的
<mapper resource="com/iktz/mybatis/beans/UserMapper.xml" />
当然也可能更丰满
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
这3行代码,说明了mapper标签可以有这3个属性。
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.");
}
而上面的代码则说明,这3个属性,同时只能存在一个。
我们来看看他们的区别吧!
4、XMLMapperBuilder 类和它的parse方法
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();
}
他们最让我好奇的是parse方法。我非常想知道他们里面是什么。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
Pend、、这个单词的意思是预备的意思。ChacheRef这个是什么鬼?
5、解析mapper.xml的关键部分
你肯定知道,我要看最后的3行代码。但是其实不是,真正关键的代码是这一行
configurationElement(parser.evalNode("/mapper"));
configurationElement方法中的代码才是对mapper文件的解析。
configurationElement(parser.evalNode("/mapper"));
configurationElement方法中的代码才是对mapper文件的解析。
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (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"));
我突然有一种感觉,这里的每一句话都很关键。
那就让我们一句一句来读吧
cacheRefElement(context.evalNode("cache-ref"));
这句应该是关于缓存的,你看方法名上都带有cache字眼。整个方法名仿佛在说,这个方法里要做的事情是缓存指向的元素。
算了,这里先不看了,给下次留点期待。而不能留给下次的,是这3个问题:parameterMapElement,resultMapElements,buildStatementFromContext。
6、解析Parameter,获取ParameterMapping
解析Parameter是由上面提到的parameterMapElement方法完成的。
以下是这个方法的代码。
以下是这个方法的代码。
private void parameterMapElement(List<XNode> list) throws Exception {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
Integer numericScale = parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
Class<?> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
7、parameterMapping——>configuration
生成了parameterMapping以后,放到了哪里呢,放到了configuration中
public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
id = applyCurrentNamespace(id, false);
ParameterMap.Builder parameterMapBuilder = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings);
ParameterMap parameterMap = parameterMapBuilder.build();
configuration.addParameterMap(parameterMap);
return parameterMap;
}
8、总结
解析mapper,主要干了什么事呢?其实也没什么,就是把配置文件解析后,封装成了MappedStatement、ParameterMap、ResultMap这3个东西放到了Configuration中。