mybatis源码学习之执行过程分析(2)——config.xml配置文件和mapper.xml映射文件解析过程
2017-01-04 17:53
1371 查看
在上一篇中跟踪了SqlSessionFactory及SqlSession的创建过程。这一篇,主要跟踪Mapper接口和XML文件映射及获取。
调用XMLConfigBuilder的构造器:
在这里
再来看
至此,
接下来调用
可以看到
对应的配置文件为:
Configuration的实例化:详见Configuration的分析 mybatis源码学习——Configuration类及其初始化过程、TypeHandler、TypeAlias
调用栈信息如下图:
在来看
这回该看这3个方法了。
//TODO 这里先打标记,后面搞明白这里的用意再写。
至此,
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>
Configuration的实例化:详见Configuration的分析 mybatis源码学习——Configuration类及其初始化过程、TypeHandler、TypeAlias
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中会用到。相关文章推荐
- Mybatis3源码分析(三):解析mapper的xml配置文件
- Mybatis3源码分析(三):解析mapper的xml配置文件
- mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行
- mybatis源码学习之执行过程分析(0)——配置文件加载(io包)
- Mybatis工作机制源码分析—初始化—mapper配置文件解析
- MyBatis 源码分析 - 配置文件解析过程
- Mybatis学习总结(三)——SqlMapConfig.xml全局配置文件解析
- Mybatis 源码学习笔记(八)mapper映射文件配置之select、resultMap
- MyBatis的Mapper XML映射文件配置解析
- mybatis源码学习之执行过程分析(3)——mapper接口的获取
- Mybatis配置文件(mybatis-config.xml )源码分析
- MyBatis学习(二)-XML映射配置文件mybatis-config.xml
- MyBatis 源码分析 - 映射文件解析过程
- mybatis入门基础(三)----SqlMapConfig.xml全局配置文件解析
- 【mybatis源码分析】原理分析之三:初始化(配置文件读取和解析)
- Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 二
- Spring、MyBatis的整合数据映射器类(UserMapper->iocContext.xml)配置文件详解
- mybatis-Config.xml全局配置文件解析
- MyBatis 官方文档学习2---XML 映射配置文件
- MyBatis知识系列之三:MyBatis的配置文件:mybatis.cfg.xml和xxx.mapper.xml文件的解析