1. Mybatis加载解析配置文件流程
2. 如何解析配置文件里面的parameterType
3. 提高看源码的能力
4. 查看源码编写方式,明白应该如何规范的写出解析XML文件的代码,提高编码能力
5. 学会使用框架上解决问题的思维和常用手段
----------------------------------------------------------------------------------------------------------------------------------------------------
如下一段代码:调用了Mybatis提供的加载及解析配置文件功能。
public class DBAccess { public SqlSession getSqlSession() throws IOException { //1、通过配置文件获取数据库连接相关信息 Readerreader=Resources.getResourceAsReader("hdu/terence/config/Configuration.xml"); //2、通过配置信息构建SqlSessionFactory SqlSessionFactorySSF=new SqlSessionFactoryBuilder().build(reader); //3、通过SqlSessionFactory打开数据库会话 SqlSessionsqlSession=SSF.openSession(); return sqlSession; } }
题外话
上述代码在Mybatis中的使用存在两个问题:
问题一:每次访问数据调用Sql语句的时候,都会临时的去调用加载配置文件解析,很耗性能。
问题二:另外,每次访问,都需要反复加载,耗费时间。
这个问题的暂且说一下解决办法:
针对第一个配置文件加载的时机问题,要自己写一个监听器,容器在启动的时候加载配置文件。
【加载时机】-->【监听器】
针对第二个问题,通过单利模式存放监听器加载的配置内容,防止其重复加载。
【重复加载】-->【单例模式】
在实际开发中则是通过Spring+Mybatis解决上述两个问题的。
先说一下上述加载解析配置文件的代码:根据注释可知,此部分分为三步。
1. 通过配置文件获取数据库连接相关信息
2. 通过配置信息构建SqlSessionFactory
3. 通过SqlSessionFactory打开数据库会话
其中,在第二步通过build()方法进入Mybatis当中。
上述build()方法是在SqlSessionFactoryBuilder.class这个类中实现的:
public SqlSessionFactorybuild(Reader reader, String environment, Properties properties) { try { 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 previouserror. } } }
看此句:
XMLConfigBuilderparser = new XMLConfigBuilder(reader, environment, properties)表示将转换后的reader、配置环境以及配置的各个属性包装在parser解析项中,然后通过return build(parser.parse())返回一个会话工厂,仍然需要进入另外的源码XMLConfigBilder.class中,找到parser()解析方法:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only beused once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
在构造函数当中,将parsed=flase,在configuration parse()方法中,先判别parsed是否为ture,如果是True,则表示已经加载解析过,抛出异常(防止重复加载,耗费性能耗费时间,防止出现类似于调用sql语句时候每次都调用DBAccesss一样出现的两个问题),否则将其赋值为true,然后继续解析加载文件,通过parser.evalNode("/configuration")进入XPathParser.Class中,通过里面的Document文件读取对象来解析文件(那么由此可以说明,Mybatis解析xml文件使用的是Dom对象和Java JDK中的类进行的)。
XPathParser.Class文件相关内容:
构造函数:
public XPathParser(String xml) { commonConstructor(false, null, null); this.document = createDocument(new InputSource(new StringReader(xml))); } public XPathParser(Reader reader) { commonConstructor(false, null, null); this.document = createDocument(new InputSource(reader)); }
看第二个构造函数可知此步表示reader对象的转化为输入流,然后转化为document对象。
上述的源码目的是为了进入配置文件Configuration.xml文件解析,下面先贴出来配置文件
贴总配置文件Configuration.xml:
子配置文件Dao.xml---- Message.xml
OK,退出来梳理总流程:
首先,建立一个总配置文件的解析流,使用Document对象代替reader对象,成为了新的配置文件解析代言人,然后Document对象进入Configuration.xml配置文件,解析出
最后,进入Dao.xml配置文件之后,使用JDK中的Dom对象逐个解析,肯定能找到
下面就解读一下如何查询总配置文件和在总配置文件进入到sql语句Dao.xml配置文件:
先说一个东西:XPath
Xpath是XML的路径语言,使用路径表达式来表示配置文档中的结点、结点集:
XMLConfigBuilder.class: public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only beused once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
这个方法就是利用Xpath的XML路径语言(也是一种路径表达式),获取的是总配置文件Configuration.xml文件的
进入到XPathParser.class方法:
public XNode evalNode(String expression) { return evalNode(document, expression); //document是解读对象,expresssion是路径表达式 } public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables); } public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables); } private Object evaluate(String expression, Object root, QName returnType) { try { return xpath.evaluate(expression, root, returnType); //在此步就给出了要取出的参数的类型。 } catch (Exception e) { throw new BuilderException("Error evaluating XPath. Cause: " + e, e); } }
主线里面有辅线,一层一层如同递归一样调用,将获取的对象保存下来,然后使用XMLConfigBuilder.class文件下的parseConfigation(XNode root)方法来解析内容。
private void parseConfiguration(XNode root) { try { 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); // read it after objectFactory andobjectWrapperFactory 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 MapperConfiguration. Cause: " + e, e); } }
此方法同样是利用路径表达式(上述代码中括号中引号里面的内容)遍历root根节点下层的结点,比如在总配置文件configuration.xml文件plugins用来配置插件或拦截器,environments用来配置数据库访问的各个属性,mappers用来配置sql语句文件dao.xml的映射路径。
同样的,evalNode()方法就是上述用到的查找根节点的方法。
看分支语句:mapperElement(root.evalNode("mappers"))通过调用mapperElement()入sql配置文件Dao.xml解析文件。
看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); XMLMapperBuildermapperParser = 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); XMLMapperBuildermapperParser = 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 aurl, resource or class, but not more than one."); } } } } }
该方法首先判断父节点是否为空,若不为空,则进入子节点
如果存在包文件”package”,则进入configuration.addMappers(mapperPackage)分支;
否则利用getStringAttribute()方法获取所有的属性名称和属性值;
若仅Resource不为空,则将该节点暂存于线程池: (ErrorContext.instance().resource(resource))
然后利用Resouces.getResourceAsStream(resource)字节流方法获取该节点的属性值: resource=”hdu/terence/config/sqlxml/Message.xml”
将参数保存到流中: XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,configuration, url,configuration.getSqlFragments());
然后调用mapperParser.parse()方法解析,此方法需要进入XMLMapperBuilder.java类中
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); }
过程和上述判断类似:首先判断是否对其解析过,如果解析过,抛出异常,否则继续解析文件。
在parser()方法中利用parser.evalNode("/mapper")找到根节点
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot beempty");
}
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. Cause:" + e, e);
}
}
由上述代码可知,必须要配置namespace属性值,否则会抛出异常:Mapper's namespace cannot beempty。
然后依次获取parameterMap/resultMap/sql等内容,然后调用buildStatementFromContext(context.evalNodes("select|insert|update|delete")),对这些不同类型的sql语句进行处理,追溯--------------到如下函数(中间曲折迂回太多了)。
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap =context.getStringAttribute("parameterMap"); String parameterType =context.getStringAttribute("parameterType"); Class> parameterTypeClass = resolveClass(parameterType); String resultMap =context.getStringAttribute("resultMap"); String resultType =context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformerincludeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes andremove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre:
and were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); }
源码主要通过语句:
Class> parameterTypeClass= resolveClass(parameterType);
根据参数类型获取参数类名。
获取参数类名又是一个艰辛的过程: 通过函数resolveClass(parameterType)来一层一层追溯目标到 TypeAliasRegistry下的resolveAlias(alias)方法
TypeAliaiRegistry类:中成员和方法
private final Map
> TYPE_ALIASES = new HashMap >(); public TypeAliasRegistry() { registerAlias("string", String.class); registerAlias("byte", Byte.class); registerAlias("long", Long.class); registerAlias("short", Short.class); registerAlias("int", Integer.class); registerAlias("integer", Integer.class); registerAlias("double", Double.class); registerAlias("float", Float.class); registerAlias("boolean", Boolean.class); registerAlias("byte[]", Byte[].class); registerAlias("long[]", Long[].class); registerAlias("short[]", Short[].class); registerAlias("int[]", Integer[].class); registerAlias("integer[]", Integer[].class); registerAlias("double[]", Double[].class); registerAlias("float[]", Float[].class); registerAlias("boolean[]", Boolean[].class); registerAlias("_byte", byte.class); registerAlias("_long", long.class); registerAlias("_short", short.class); registerAlias("_int", int.class); registerAlias("_integer", int.class); registerAlias("_double", double.class); registerAlias("_float", float.class); registerAlias("_boolean", boolean.class); registerAlias("_byte[]", byte[].class); registerAlias("_long[]", long[].class); registerAlias("_short[]", short[].class); registerAlias("_int[]", int[].class); registerAlias("_integer[]", int[].class); registerAlias("_double[]", double[].class); registerAlias("_float[]", float[].class); registerAlias("_boolean[]", boolean[].class); registerAlias("date", Date.class); registerAlias("decimal", BigDecimal.class); registerAlias("bigdecimal", BigDecimal.class); registerAlias("biginteger", BigInteger.class); registerAlias("object", Object.class); registerAlias("date[]", Date[].class); registerAlias("decimal[]", BigDecimal[].class); registerAlias("bigdecimal[]", BigDecimal[].class); registerAlias("biginteger[]", BigInteger[].class); registerAlias("object[]", Object[].class); registerAlias("map", Map.class); registerAlias("hashmap", HashMap.class); registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class); registerAlias("collection", Collection.class); registerAlias("iterator", Iterator.class); registerAlias("ResultSet", ResultSet.class); } public Class resolveAlias(String string) { try { if (string == null) { return null; } // issue #748 String key = string.toLowerCase(Locale.ENGLISH); Class value; //存放类名 if (TYPE_ALIASES.containsKey(key)) { value = (Class ) TYPE_ALIASES.get(key); } else { value = (Class ) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause:" + e, e); } }
如果参数类型string!=null,则先将参数类型字符串转换为小写key,然后判断Type_Aliases这个Map对象里面是否包含key,如果有,则获取对应的类名(此处获取的是java自定义好的类名,如String.class、Integer.class、Byte[].class、Collection.class等等),否则,直接利用反射获取类名(此处获取的类名是自定义的类名),然后将获得类名返回。
其余方法不再细说,可自行追溯。
上述这条解读源码的主线是解析参数parameterType对应的类名:
reader(总配置文件路径)—>变为configuration代言人—>解读Configuration.xml总配置文件—>
找到
找到
OK,完毕!