1. 程式人生 > >Mybatis之Configuration初始化(配置檔案.xml的解析)

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物件初始話完事了...  全域性只有一個。