Mybatis之Configuration初始化(配置文件.xml的解析)

源码解读第一步我觉着应该从Mybatis如何解析配置文件开始。

1.先不看跟Spring集成如何解析,先看从SqlSessionFactoryBuilder如果解析的。

1 String resouce = "conf.xml";
2 InputStream is = Resources.getResourceAsStream(resouce);
3 
4 // 构建sqlSession工厂
5 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

SqlSessionFactoryBuilder

 1   public SqlSessionFactory build(InputStream inputStream) {
 2     return build(inputStream, null, null);
 3   }
 4 
 5   public SqlSessionFactory build(InputStream inputStream, String environment) {
 6     return build(inputStream, environment, null);
 7   }
 8 
 9   public SqlSessionFactory build(InputStream inputStream, Properties properties) {
10     return build(inputStream, null, properties);
11   }
12   //上面那么多同名不同参的方法最后都会进入这个方法
   //支持传入Enviromment.properties实际上是支持动态传入覆盖全局配置xml的内容 13 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 14 try { 15 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //因为从下面的Build方法可以看出 parser.parse()后Configuration就初始化好了
16 return build(parser.parse()); 17 } catch (Exception e) { 18 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 19 } finally { 20 ErrorContext.instance().reset(); 21 try { 22 inputStream.close(); 23 } catch (IOException e) { 24 // Intentionally ignore. Prefer previous error. 25 } 26 } 27 }
   public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
   }

真正初始化Configuration的类是XMLConfigBuilder

 1   public Configuration parse() {
    //这一块可以看出来 全局Configuration只会初始化一次,实例化时候是false
2 if (parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } 5 parsed = true; 6 parseConfiguration(parser.evalNode("/configuration")); 7 return configuration; 8 } 9 //获取配置文件整个/configuration节点内容 10 private void parseConfiguration(XNode root) { 11 try { 12 Properties settings = settingsAsPropertiess(root.evalNode("settings")); //初始化配置文件中全局变量 13 //issue #117 read properties first 14 propertiesElement(root.evalNode("properties"));//初始化xml中的配置文件,同时讲其中的变量保存起来,因为可能其他地方引用 15 loadCustomVfs(settings); //加载自定义的VFS实现类? 这个什么用? 16 typeAliasesElement(root.evalNode("typeAliases")); //加载typeAliases别名初始化 17 pluginElement(root.evalNode("plugins")); //加载插件,实际上就是拦截器 18 objectFactoryElement(root.evalNode("objectFactory"));// //加载自定义的对象工厂 19 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //加载自定义的处理驼峰方式的key的处理器 20 reflectionFactoryElement(root.evalNode("reflectionFactory")); //加载自定义的反射器 没用过? 21 settingsElement(settings); //将setting的属性,设置到Configuration对象属性中 22 // read it after objectFactory and objectWrapperFactory issue #631 23 environmentsElement(root.evalNode("environments")); 24 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 25 typeHandlerElement(root.evalNode("typeHandlers")); //初始化类型处理器 26 mapperElement(root.evalNode("mappers")); //处理mappers节点内容,实际上就是初始化MaperStatement 27 } catch (Exception e) { 28 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 29 } 30 }

因为大部分方法都比较简单,我这里只介绍几个我认为比较重要的。

① typeAliasesElement(root.evalNode(“typeAliases”)); //加载typeAliases别名初始化

 1   private void typeAliasesElement(XNode parent) {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) { //配置的是包名 扫描包里所有的类。放入的key默认是注解的key
 5           String typeAliasPackage = child.getStringAttribute("name");
 6           configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
 7         } else {
 8           String alias = child.getStringAttribute("alias");
 9           String type = child.getStringAttribute("type");
10           try {
11             Class<?> clazz = Resources.classForName(type);
12             if (alias == null) {
13               typeAliasRegistry.registerAlias(clazz);
14             } else {
15               typeAliasRegistry.registerAlias(alias, clazz);
16             }
         //实际上就是存在了TypeAliasRegistry类的一个私有map里面。
17 } catch (ClassNotFoundException e) { 18 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); 19 } 20 } 21 } 22 } 23 }
1 public class TypeAliasRegistry {
2 
3   private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
  。。。。。。
4 }

 

② pluginElement(root.evalNode(“plugins”)); 加载插件,便于后期理解拦截器原理

 1   private void pluginElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         String interceptor = child.getStringAttribute("interceptor");
 5         Properties properties = child.getChildrenAsProperties();
       //获取interceptor 这里resolveClass实际上就是到TypeAliasResitstry里面找一下,找到了获取class,没有直接用class去反射回去对象
6 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); 7 interceptorInstance.setProperties(properties);
       //获取对象后调用configuration方法。
8 configuration.addInterceptor(interceptorInstance); 9 } 10 } 11 }

Configuration

1  public void addInterceptor(Interceptor interceptor) {
2     interceptorChain.addInterceptor(interceptor);
3   }

InterceptorChain

1   public void addInterceptor(Interceptor interceptor) {
2     interceptors.add(interceptor);
3   }

这一块就是放Configuration的拦截器链里面添加拦截器。这一块现在知道是在这时候添加的就好了。后面介绍Mybatis的拦截器的时候深入了解。

③ mapperElement(root.evalNode(“mappers”)); //处理mappers节点内容,实际上就是初始化MaperStatement

 1   private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) { 
 5           String mapperPackage = child.getStringAttribute("name");
 6           configuration.addMappers(mapperPackage);
 7         } else {
 8           String resource = child.getStringAttribute("resource");
 9           String url = child.getStringAttribute("url");
10           String mapperClass = child.getStringAttribute("class");
        //xml文件中mapper节点,配置resource,url是执行的mapper.xml文件的位置,所以现在只分析这一块。
11 if (resource != null && url == null && mapperClass == null) { 12 ErrorContext.instance().resource(resource); 13 InputStream inputStream = Resources.getResourceAsStream(resource); 14 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 15 mapperParser.parse(); 16 } else if (resource == null && url != null && mapperClass == null) { 17 ErrorContext.instance().resource(url); 18 InputStream inputStream = Resources.getUrlAsStream(url); 19 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 20 mapperParser.parse(); 21 } else if (resource == null && url == null && mapperClass != null) { 22 Class<?> mapperInterface = Resources.classForName(mapperClass); 23 configuration.addMapper(mapperInterface); 24 } else { 25 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 26 } 27 } 28 } 29 } 30 }

可以看出来resource和url都是通过XMLMapperBuilder来解析的。下面来看下parse方法。

 1   private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
 2     super(configuration);
//前面还有个构造器,是根据传入的inputstream构造XPathParser,parser可以拿到resouce里面的内容
3 this.builderAssistant = new MapperBuilderAssistant(configuration, resource); 4 this.parser = parser; 5 this.sqlFragments = sqlFragments; 6 this.resource = resource; 7 } 8 9 public void parse() { 10 if (!configuration.isResourceLoaded(resource)) { 是否加载过改文件, 11 configurationElement(parser.evalNode("/mapper")); 来解析mapper文件内容 12 configuration.addLoadedResource(resource); 添加加载记录 13 bindMapperForNamespace();往configration添加命名空间的代理对象 14 } 15 16 parsePendingResultMaps(); 17 parsePendingChacheRefs(); 18 parsePendingStatements(); 19 }
 1   private void configurationElement(XNode context) { //解析mapper.xml子节点的内容
 2     try {
 3       String namespace = context.getStringAttribute("namespace");
 4       if (namespace == null || namespace.equals("")) {
 5         throw new BuilderException("Mapper's namespace cannot be empty");
 6       }
 7       builderAssistant.setCurrentNamespace(namespace);
 8       cacheRefElement(context.evalNode("cache-ref"));
 9       cacheElement(context.evalNode("cache"));
10       parameterMapElement(context.evalNodes("/mapper/parameterMap"));
11       resultMapElements(context.evalNodes("/mapper/resultMap")); //解析resultMap 很复杂,后面单独解读
12       sqlElement(context.evalNodes("/mapper/sql"));
13       buildStatementFromContext(context.evalNodes("select|insert|update|delete")); //初始化这几个类型节点的内容
14     } catch (Exception e) {
15       throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
16     }
17   }
 1   private void buildStatementFromContext(List<XNode> list) {
因为会有多个节点,所以是一个List<XNode>
2 if (configuration.getDatabaseId() != null) { 3 buildStatementFromContext(list, configuration.getDatabaseId()); 4 } 5 buildStatementFromContext(list, null); 6 } 7 8 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { 9 for (XNode context : list) { 10 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); 11 try { 12 statementParser.parseStatementNode(); 13 } catch (IncompleteElementException e) { 14 configuration.addIncompleteStatement(statementParser); 15 } 16 } 17 }

从上面可以看出来最终是由XMLStatementBuilder来解析我们的写的sql部分。

 1   public void parseStatementNode() {
 2     String id = context.getStringAttribute("id");
 3     String databaseId = context.getStringAttribute("databaseId");
 4 
 5     if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
 6       return;
 7     }
 8 
 9     Integer fetchSize = context.getIntAttribute("fetchSize");
10     Integer timeout = context.getIntAttribute("timeout");
11     String parameterMap = context.getStringAttribute("parameterMap");
12     String parameterType = context.getStringAttribute("parameterType");
13     Class<?> parameterTypeClass = resolveClass(parameterType);
14     String resultMap = context.getStringAttribute("resultMap");
15     String resultType = context.getStringAttribute("resultType");
16     String lang = context.getStringAttribute("lang");
17     LanguageDriver langDriver = getLanguageDriver(lang);
18 
19     Class<?> resultTypeClass = resolveClass(resultType);
20     String resultSetType = context.getStringAttribute("resultSetType");
21     StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
22     ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
23 
24     String nodeName = context.getNode().getNodeName();
25     SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
26     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
27     boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
28     boolean useCache = context.getBooleanAttribute("useCache", isSelect);
29     boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
30 
31     // Include Fragments before parsing
32     XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
33     includeParser.applyIncludes(context.getNode());
34 
35     // Parse selectKey after includes and remove them.
36     processSelectKeyNodes(id, parameterTypeClass, langDriver);
37     
38     // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
39     SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
40     String resultSets = context.getStringAttribute("resultSets");
41     String keyProperty = context.getStringAttribute("keyProperty");
42     String keyColumn = context.getStringAttribute("keyColumn");
43     KeyGenerator keyGenerator;
44     String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
45     keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
46     if (configuration.hasKeyGenerator(keyStatementId)) {
47       keyGenerator = configuration.getKeyGenerator(keyStatementId);
48     } else {
49       keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
50           configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
51           ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
52     }
53   获取节点所有属性内容,调用builderAssistant,实现是MapperBuilderAssistant 在XmlMapperBuilder构造器初始化时候就制定了
54     builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
55         fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
56         resultSetTypeEnum, flushCache, useCache, resultOrdered, 
57         keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
58   }
 1   public MappedStatement addMappedStatement(
 2       String id,
 3       SqlSource sqlSource,
 4       StatementType statementType,
 5       SqlCommandType sqlCommandType,
 6       Integer fetchSize,
 7       Integer timeout,
 8       String parameterMap,
 9       Class<?> parameterType,
10       String resultMap,
11       Class<?> resultType,
12       ResultSetType resultSetType,
13       boolean flushCache,
14       boolean useCache,
15       boolean resultOrdered,
16       KeyGenerator keyGenerator,
17       String keyProperty,
18       String keyColumn,
19       String databaseId,
20       LanguageDriver lang,
21       String resultSets) {
22 
23     if (unresolvedCacheRef) {
24       throw new IncompleteElementException("Cache-ref not yet resolved");
25     }
26 
27     id = applyCurrentNamespace(id, false); //把id加上namespace+"."
28     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
29 
30     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
31         .resource(resource)
32         .fetchSize(fetchSize)
33         .timeout(timeout)
34         .statementType(statementType)
35         .keyGenerator(keyGenerator)
36         .keyProperty(keyProperty)
37         .keyColumn(keyColumn)
38         .databaseId(databaseId)
39         .lang(lang)
40         .resultOrdered(resultOrdered)
41         .resulSets(resultSets)
42         .resultMaps(getStatementResultMaps(resultMap, resultType, id))
43         .resultSetType(resultSetType)
44         .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
45         .useCache(valueOrDefault(useCache, isSelect))
46         .cache(currentCache);
47 
48     ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
49     if (statementParameterMap != null) {
50       statementBuilder.parameterMap(statementParameterMap);
51     }
52 
53     MappedStatement statement = statementBuilder.build();
54     configuration.addMappedStatement(statement);
55     return statement;
56   }
MappedStatement.Builder 是MappedStatement的内部类。里面有MappedStatement的引用,所有方法都设置内部引用mappedStatement的属性,并返回自身,所以这一块就是类似于setparam()
最终通过build方法 返回对象。 然后调用Congifuration.addMappedStatement保存到Congifuration对象里面。
1   public void addMappedStatement(MappedStatement ms) {
2     mappedStatements.put(ms.getId(), ms);
3   }

到此Congifuration对象初始话完事了…  全局只有一个。