mybatis原始碼解析之Configuration載入(一)
概要
上一篇,我們主要搭建了一個簡單的環境,這邊我們主要來分析下mybatis是如何來載入它的配置檔案Configuration.xml的。
分析
1 public class App { 2 public static void main(String[] args) { 3 try { 4 InputStream inputStream = Resources.getResourceAsStream("configuration/Configuration.xml"); 5 SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder().build(inputStream); 6 SqlSession session = sqlSessionFactory.openSession(); 7 //寫法一 8 //NewsVO newsVo = session.selectOne("com.mybatis.read.dao.NewsMapper.getNews", 40); 9 //寫法二 10 NewsMapper newsMapper = session.getMapper(NewsMapper.class); 11 NewsVO newsVo = newsMapper.getNews(40); 12 System.out.println(newsVo.toString()); 13 } catch (IOException e) { 14 e.printStackTrace(); 15 } 16 } 17 }
我們先看下第4行程式碼,看起來很簡單,利用Resource載入指定路徑下的檔案,獲取輸入流,具體的程式碼實現為:
1 InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {2 for (ClassLoader cl : classLoader) { 3 if (null != cl) { 4 5 // try to find the resource as passed 6 InputStream returnValue = cl.getResourceAsStream(resource); 7 8 // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource 9 if (null == returnValue) { 10 returnValue = cl.getResourceAsStream("/" + resource); 11 } 12 13 if (null != returnValue) { 14 return returnValue; 15 } 16 } 17 } 18 return null; 19 }
可以看出來,其內部載入resource的方式還是通過classloader.getResouceAsStream。
獲取到輸入流之後呢,我們看到是通過SqlSessionFactoryBuilder的build方法去載入的,我們可以繼續跟進去,可以發現SqlSessionFactoryBuilder中使用了多型,主要有這麼兩個方法,具體如下:
1.通過reader的字元流載入配置檔案,返回SqlSessionFactory
1 public SqlSessionFactory build(Reader reader, String environment, Properties properties) { 2 try { 3 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); 4 return build(parser.parse()); 5 } catch (Exception e) { 6 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 7 } finally { 8 ErrorContext.instance().reset(); 9 try { 10 reader.close(); 11 } catch (IOException e) { 12 // Intentionally ignore. Prefer previous error. 13 } 14 } 15 }
2.通過inputStream的位元組流載入配置檔案,獲取SqlSessionFactory
1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 2 try { 3 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 4 return build(parser.parse()); 5 } catch (Exception e) { 6 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 7 } finally { 8 ErrorContext.instance().reset(); 9 try { 10 inputStream.close(); 11 } catch (IOException e) { 12 // Intentionally ignore. Prefer previous error. 13 } 14 } 15 }
我們的main方法中使用的是inputStream的方式,我們就來看下方式二的程式碼。解析Configuration.xml的程式碼就在第三行XMLConfigBuilder的構造方法中,我們繼續跟進去,
1 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { 2 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); 3 }
很明顯,這裡關鍵是第二行的XPathParser,我繼續跟進去看一下它的在構造中幹了什麼?
1 public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { 2 commonConstructor(validation, variables, entityResolver); 3 this.document = createDocument(new InputSource(inputStream)); 4 }
第二行程式碼commonConstructor,
1 private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) { 2 this.validation = validation; 3 this.entityResolver = entityResolver; 4 this.variables = variables; 5 XPathFactory factory = XPathFactory.newInstance(); 6 this.xpath = factory.newXPath(); 7 }
初始化XPathParser的類變數validation,variables,entityResolver,並獲取一個xpath的例項。
再看第三行程式碼createDocument方法:
1 private Document createDocument(InputSource inputSource) { 2 // important: this must only be called AFTER common constructor 3 try { 4 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 5 factory.setValidating(validation); 6 7 factory.setNamespaceAware(false); 8 factory.setIgnoringComments(true); 9 factory.setIgnoringElementContentWhitespace(false); 10 factory.setCoalescing(false); 11 factory.setExpandEntityReferences(true); 12 13 DocumentBuilder builder = factory.newDocumentBuilder(); 14 builder.setEntityResolver(entityResolver); 15 builder.setErrorHandler(new ErrorHandler() { 16 @Override 17 public void error(SAXParseException exception) throws SAXException { 18 throw exception; 19 } 20 21 @Override 22 public void fatalError(SAXParseException exception) throws SAXException { 23 throw exception; 24 } 25 26 @Override 27 public void warning(SAXParseException exception) throws SAXException { 28 } 29 }); 30 return builder.parse(inputSource); 31 } catch (Exception e) { 32 throw new BuilderException("Error creating document instance. Cause: " + e, e); 33 } 34 }
第5到11行程式碼,主要設定DocumentBuilderFactory中一些引數的值,具體含義如下:
- setValidating表示是否驗證xml檔案,這個驗證是DTD驗證
- setNamespaceAware表示是否支援xml名稱空間
- setIgnoringComments表示是否忽略註釋
- setIgnoringElementContentWhitespace表示是否忽略元素中的空白
- setCoalescing表示是否將CDATA節點轉換為Text節點,並將其附加到相鄰(如果有)的Text節點
- setExpandEntityReferences表示是否擴充套件實體引用節點
第13行程式碼從DocumentBuilderFactory中獲取DocumentBuilder例項,第14行程式碼設定一個實體解析器,第15到29行程式碼設定一個錯誤處理器。
再看下第30行的parse方法,
1 public Document parse(InputSource is) throws SAXException, IOException { 2 if (is == null) { 3 throw new IllegalArgumentException( 4 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, 5 "jaxp-null-input-source", null)); 6 } 7 if (fSchemaValidator != null) { 8 if (fSchemaValidationManager != null) { 9 fSchemaValidationManager.reset(); 10 fUnparsedEntityHandler.reset(); 11 } 12 resetSchemaValidator(); 13 } 14 domParser.parse(is); 15 Document doc = domParser.getDocument(); 16 domParser.dropDocumentReferences(); 17 return doc; 18 }
這邊就是用DocumentBuilder將inputStream載入成org.w3c.dom.Document物件返回,並且儲存在XPathParser中。
我們前面看了這麼多,其實就是分析了這麼一句程式碼 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 得到了org.w3c.dom.Document物件,那麼又是怎麼得到Java物件的呢?
我們回到SqlSessionFactoryBuilder的build方法:
1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 2 try { 3 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 4 return build(parser.parse()); 5 } catch (Exception e) { 6 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 7 } finally { 8 ErrorContext.instance().reset(); 9 try { 10 inputStream.close(); 11 } catch (IOException e) { 12 // Intentionally ignore. Prefer previous error. 13 } 14 } 15 }
之前我們分析了第3行程式碼,接下來我們看一下第4行程式碼,
1 public Configuration parse() { 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 }
這個方法裡面首先判斷有沒有解析過,如果解析過了,直接丟擲異常,重點看下第6行程式碼,這裡的parser就是Xpathparse,看下它的evalNode方法:
1 public XNode evalNode(String expression) { 2 return evalNode(document, expression); 3 } 4 5 public XNode evalNode(Object root, String expression) { 6 Node node = (Node) evaluate(expression, root, XPathConstants.NODE); 7 if (node == null) { 8 return null; 9 } 10 return new XNode(this, node, variables); 11 } 12 13 private Object evaluate(String expression, Object root, QName returnType) { 14 try { 15 return xpath.evaluate(expression, root, returnType); 16 } catch (Exception e) { 17 throw new BuilderException("Error evaluating XPath. Cause: " + e, e); 18 } 19 }
主要就是解析configuration.xml中configuration開始的節點,並將結果放到XNode中返回,接下來的分別的去獲取XNode中的內容。看下第6行parseConfiguration方法,
1 try { 2 //issue #117 read properties first 3 propertiesElement(root.evalNode("properties")); 4 Properties settings = settingsAsProperties(root.evalNode("settings")); 5 loadCustomVfs(settings); 6 typeAliasesElement(root.evalNode("typeAliases")); 7 pluginElement(root.evalNode("plugins")); 8 objectFactoryElement(root.evalNode("objectFactory")); 9 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 10 reflectorFactoryElement(root.evalNode("reflectorFactory")); 11 settingsElement(settings); 12 // read it after objectFactory and objectWrapperFactory issue #631 13 environmentsElement(root.evalNode("environments")); 14 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 15 typeHandlerElement(root.evalNode("typeHandlers")); 16 mapperElement(root.evalNode("mappers")); 17 } catch (Exception e) { 18 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 19 } 20 }
這個就是逐個解析configuration.xml中configuration的子節點,並設定到對應的屬性中。我們並不一個一個的去看,因為有些標籤並不常用。
這裡還要在看個東西,就是XMLConfigBuilder這個類父類,BaseBuilder,它有幾個類成員,
1 protected final Configuration configuration; 2 protected final TypeAliasRegistry typeAliasRegistry; 3 protected final TypeHandlerRegistry typeHandlerRegistry;
Configuration物件,就是最終將配置檔案轉化成java的那個物件,包含所有的配置資訊,TypeAliasRegistry,這個用來註冊配置了別名的類,供後面使用,後續會講到。
properties解析
首先,我們來看下,我們在configuration.xml中的properties標籤是什麼樣子的?
1 <properties resource="properties/db.properties" />
具體的解析程式碼如下:
1 private void propertiesElement(XNode context) throws Exception { 2 if (context != null) { 3 Properties defaults = context.getChildrenAsProperties(); 4 String resource = context.getStringAttribute("resource"); 5 String url = context.getStringAttribute("url"); 6 if (resource != null && url != null) { 7 throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); 8 } 9 if (resource != null) { 10 defaults.putAll(Resources.getResourceAsProperties(resource)); 11 } else if (url != null) { 12 defaults.putAll(Resources.getUrlAsProperties(url)); 13 } 14 Properties vars = configuration.getVariables(); 15 if (vars != null) { 16 defaults.putAll(vars); 17 } 18 parser.setVariables(defaults); 19 configuration.setVariables(defaults); 20 } 21 }
很明顯,從第4行和第5行程式碼看出,<properties>標籤下不能同時指定"resource"屬性和"url"屬性。接下來的9到19行程式碼,載入我們制定的.properties檔案,並將結果儲存到XPathParser和Configuration的Variable中去
settings解析
解析程式碼如下:
1 private Properties settingsAsProperties(XNode context) { 2 if (context == null) { 3 return new Properties(); 4 } 5 Properties props = context.getChildrenAsProperties(); 6 // Check that all settings are known to the configuration class 7 MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); 8 for (Object key : props.keySet()) { 9 if (!metaConfig.hasSetter(String.valueOf(key))) { 10 throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); 11 } 12 } 13 return props; 14 }
首先如果沒有<settings>標籤,則返回一個新的Properties物件。不為空的話,第5行程式碼,獲取<settings>標籤的所有子標籤,第7到12行程式碼,這個一次看的時候比較難懂啊,其實很簡單,就是驗證<settings>的子標籤當中的name是不是胡亂寫的,具體是怎麼驗證的呢?簡單來說就是,<setting>的name屬性對應的值,必須在Configuration類有相應的Setter,比如設定了一個屬性useGenerateKeys方法,那麼必須在Configuration類中有setUseGenerateKeys方法才行。
這邊這部分只是解析了<settings>,具體用處後面繼續講。
類別名解析
1 private void typeAliasesElement(XNode parent) { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 if ("package".equals(child.getName())) { 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 } 17 } catch (ClassNotFoundException e) { 18 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); 19 } 20 } 21 } 22 } 23 }
這個方法是解析<typeAliases>標籤,首先我們看第4行和第7行的if......else......,可以看出,<typeAliases>標籤下不能同時出現<package>和<typeAlias>,我們首先來看<package>標籤的情況,看下第6行程式碼,獲取到name後,直接就呼叫registerAliases方法進行註冊,程式碼如下:
1 public void registerAliases(String packageName){ 2 registerAliases(packageName, Object.class); 3 } 4 5 public void registerAliases(String packageName, Class<?> superType){ 6 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); 7 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); 8 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); 9 for(Class<?> type : typeSet){ 10 // Ignore inner classes and interfaces (including package-info.java) 11 // Skip also inner classes. See issue #6 12 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { 13 registerAlias(type); 14 } 15 } 16 }
6到8行程式碼,在package目錄下尋找.class檔案,並獲取對應的class檔案,第9到14行程式碼,根據獲取的class,排除匿名類,介面,成員類,然後呼叫registerAlias進行註冊:
1 public void registerAlias(Class<?> type) { 2 String alias = type.getSimpleName(); 3 Alias aliasAnnotation = type.getAnnotation(Alias.class); 4 if (aliasAnnotation != null) { 5 alias = aliasAnnotation.value(); 6 } 7 registerAlias(alias, type); 8 }
這段程式碼主要作用是獲取class的simpleName,也就是不包含類路徑的那個名字,3到6行程式碼,獲取類上面的Alias註解的value值,不為空的話就用這個當做類的別名。第7行程式碼進行註冊:
1 public void registerAlias(String alias, Class<?> value) { 2 if (alias == null) { 3 throw new TypeException("The parameter alias cannot be null"); 4 } 5 // issue #748 6 String key = alias.toLowerCase(Locale.ENGLISH); 7 if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { 8 throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); 9 } 10 TYPE_ALIASES.put(key, value); 11 }
第2到4行程式碼,判斷別名不能為空,第6行程式碼,將別名全部小寫,7到9行程式碼,判斷 TYPE_ALIASES這個hashMap中是否已經存在這樣的別名或者這樣的類,第10行程式碼,將類加到hashMap中,供後續使用。
如果使用的是<typeAlias>標籤的話:
1 String alias = child.getStringAttribute("alias"); 2 String type = child.getStringAttribute("type"); 3 try { 4 Class<?> clazz = Resources.classForName(type); 5 if (alias == null) { 6 typeAliasRegistry.registerAlias(clazz); 7 } else { 8 typeAliasRegistry.registerAlias(alias, clazz); 9 } 10 } catch (ClassNotFoundException e) { 11 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); 12 }
第4行程式碼,如果一直跟進去的話,你就會發現,最後實際上是呼叫classloder按型別載入初始化類。第5到9行程式碼,根據標籤中的<alias> 是否設定,去分別註冊類,這兩個方法上面都看過了,不在贅述。
剛才跟程式碼的時候,其實可以發現這裡面已經有很多預設的typeAlias,Configuration類中有如下部分:
1 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); 2 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); 3 4 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); 5 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); 6 typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); 7 8 typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); 9 typeAliasRegistry.registerAlias("FIFO", FifoCache.class); 10 typeAliasRegistry.registerAlias("LRU", LruCache.class); 11 typeAliasRegistry.registerAlias("SOFT", SoftCache.class); 12 typeAliasRegistry.registerAlias("WEAK", WeakCache.class); 13 14 typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); 15 16 typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); 17 typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); 18 19 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); 20 typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); 21 typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); 22 typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); 23 typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); 24 typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); 25 typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); 26 27 typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); 28 typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); 29 30 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); 31 languageRegistry.register(RawLanguageDriver.class);
TypeAliasRegistry類中有如下部分:
1 registerAlias("string", String.class); 2 3 registerAlias("byte", Byte.class); 4 registerAlias("long", Long.class); 5 registerAlias("short", Short.class); 6 registerAlias("int", Integer.class); 7 registerAlias("integer", Integer.class); 8 registerAlias("double", Double.class); 9 registerAlias("float", Float.class); 10 registerAlias("boolean", Boolean.class); 11 12 registerAlias("byte[]", Byte[].class); 13 registerAlias("long[]", Long[].class); 14 registerAlias("short[]", Short[].class); 15 registerAlias("int[]", Integer[].class); 16 registerAlias("integer[]", Integer[].class); 17 registerAlias("double[]", Double[].class); 18 registerAlias("float[]", Float[].class); 19 registerAlias("boolean[]", Boolean[].class); 20 21 registerAlias("_byte", byte.class); 22 registerAlias("_long", long.class); 23 registerAlias("_short", short.class); 24 registerAlias("_int", int.class); 25 registerAlias("_integer", int.class); 26 registerAlias("_double", double.class); 27 registerAlias("_float", float.class); 28 registerAlias("_boolean", boolean.class); 29 30 registerAlias("_byte[]", byte[].class); 31 registerAlias("_long[]", long[].class); 32 registerAlias("_short[]", short[].class); 33 registerAlias("_int[]", int[].class); 34 registerAlias("_integer[]", int[].class); 35 registerAlias("_double[]", double[].class); 36 registerAlias("_float[]", float[].class); 37 registerAlias("_boolean[]", boolean[].class); 38 39 registerAlias("date", Date.class); 40 registerAlias("decimal", BigDecimal.class); 41 registerAlias("bigdecimal", BigDecimal.class); 42 registerAlias("biginteger", BigInteger.class); 43 registerAlias("object", Object.class); 44 45 registerAlias("date[]", Date[].class); 46 registerAlias("decimal[]", BigDecimal[].class); 47 registerAlias("bigdecimal[]", BigDecimal[].class); 48 registerAlias("biginteger[]", BigInteger[].class); 49 registerAlias("object[]", Object[].class); 50 51 registerAlias("map", Map.class); 52 registerAlias("hashmap", HashMap.class); 53 registerAlias("list", List.class); 54 registerAlias("arraylist", ArrayList.class); 55 registerAlias("collection", Collection.class); 56 registerAlias("iterator", Iterator.class); 57 58 registerAlias("ResultSet", ResultSet.class);
對於這些資料,我們可以直接使用registerAlias方法的第一個引數對應的字串而不需要定義這些typeAlias。
上面我記得在解析<settings>標籤的時候,最後返回的propertis並沒有用,我們來看下parseConfiguration的settingsElement方法:
1 private void settingsElement(Properties props) throws Exception { 2 configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); 3 configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); 4 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); 5 configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); 6 configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); 7 configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false)); 8 configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); 9 configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); 10 configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); 11 configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); 12 configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); 13 configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null)); 14 configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); 15 configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); 16 configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); 17 configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); 18 configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); 19 configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); 20 configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); 21 configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); 22 configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); 23 configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); 24 configuration.setLogPrefix(props.getProperty("logPrefix")); 25 @SuppressWarnings("unchecked") 26 Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl")); 27 configuration.setLogImpl(logImpl); 28 configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); 29 }
這個方法就是講<settings>標籤中設定的屬性設定到configuration中,有些欄位如果沒有設定的話,設定預設值。
configuation.xml的解析我們先講到這裡,下一篇我們介紹剩下的兩個比較重要的標籤<environments>和<mapper>的解析。