config.xml解析为org.w3c.dom.Document
本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分:
1 2 3 4 5 6 7 8 9 | static { try { reader = Resources.getResourceAsReader( "mybatis/config.xml" ); ssf = new SqlSessionFactoryBuilder().build(reader); } catch (IOException e) { e.printStackTrace(); } } |
第3行的代码实现为:
1 2 3 4 5 6 7 8 9 | public static Reader getResourceAsReader(String resource) throws IOException { Reader reader; if (charset == null ) { reader = new InputStreamReader(getResourceAsStream(resource)); } else { reader = new InputStreamReader(getResourceAsStream(resource), charset); } return reader; } |
相当于就是将输入的路径转换为一个字符输入流并返回。
接着继续看静态块第4行的代码,new SqlSessionFactoryBuilder().build(reader),把代码定位到SqlSessionFactoryBuilder类的builder方法,这里使用了多态,直接跟到build方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public SqlSessionFactory build(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 previous error. } } } |
解析config.xml的代码在第3行XMLConfigBuilder类的构造方法中,看一下XMLConfigBuilder类的构造方法做了什么:
1 2 3 | public XMLConfigBuilder(Reader reader, String environment, Properties props) { this ( new XPathParser(reader, true , props, new XMLMapperEntityResolver()), environment, props); } |
这里的关键是第二行代码的第一个参数XPathParser,看一下实例化XPathParser类的代码:
1 2 3 4 | public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); this .document = createDocument( new InputSource(reader)); } |
第2行的代码commonConstructor方法没什么好看的,将validation、variables、entityResolver设置到XPathParser类的参数中而已,顺便再实例化一个javax.xml.xpath.XPath出来,XPath用于在XML文档中通过元素和属性进行导航,并对元素和属性进行遍历。
接着看第3行的createDocument方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 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); } catch (Exception e) { throw new BuilderException( "Error creating document instance. Cause: " + e, e); } } |
看一下第5行~第11行的代码设置DocumentBuilderFactory中参数的含义:
- setValidating表示是否验证xml文件,这个验证是DTD验证
- setNamespaceAware表示是否支持xml命名空间
- setIgnoringComments表示是否忽略注释
- setIgnoringElementContentWhitespace表示是否忽略元素中的空白
- setCoalescing表示是否将CDATA节点转换为Text节点,并将其附加到相邻(如果有)的Text节点
- setExpandEntityReferences表示是否扩展实体引用节点
第13行的代码由设置的参数从DocumentBuilderFactory中获取一个DocumentBuilder实例DocumentBuilderImpl,并由第14行的代码设置一个实体解析器,由第15行~第29行的代码设置一个错误处理器。
最后看一下第30行的代码parse方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public Document parse(InputSource is) throws SAXException, IOException { if (is == null ) { throw new IllegalArgumentException( DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "jaxp-null-input-source" , null )); } if (fSchemaValidator != null ) { if (fSchemaValidationManager != null ) { fSchemaValidationManager.reset(); fUnparsedEntityHandler.reset(); } resetSchemaValidator(); } domParser.parse(is); Document doc = domParser.getDocument(); domParser.dropDocumentReferences(); return doc; } |
看过Spring配置文件解析源码的朋友应该对这一段代码比较熟悉,一样的,使用DocumentBuilder将解析InputSource成org.w3c.dom.Document并将Document存储到XPathParser中。
Document转换为Configuration
前面的代码将config.xml转换为了org.w3c.dom.Document,下一步就是将org.w3c.dom.Document中的内容转换为Java对象了,其中最主要的一个对象就是org.apache.ibatis.session.Configuration,还是回到之前的SqlSessionFactoryBuilder的build方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public SqlSessionFactory build(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 previous error. } } } |
先看一下第4行的parse方法,parse方法是XMLConfigBuilder中的,之前重点分析了它的属性XPathParser,看一下XMLConfigBuilder的parse方法是如何实现的:
1 2 3 4 5 6 7 8 | public Configuration parse() { if (parsed) { throw new BuilderException( "Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode( "/configuration" )); return configuration; } |
这里看一下第6行,可以使用XPathParser的evalNode方法解析标签,后面解析标签会大量用到此方法,此方法将标签解析为XNode,像config.xml(可见上一篇文章的示例)解析完之后的XNode,toString()方法输出的内容是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <configuration> <properties resource= "properties/db.properties" /> <settings> <setting name= "cacheEnabled" value= "true" /> <setting name= "lazyLoadingEnabled" value= "true" /> <setting name= "useGeneratedKeys" value= "true" /> </settings> <typeAliases> <typeAlias alias= "Mail" type= "org.xrq.mybatis.pojo.Mail" /> </typeAliases> <environments default = "development" > <environment id= "development" > <transactionManager type= "JDBC" /> <dataSource type= "POOLED" > <property name= "driver" value= "${driveClass}" /> <property name= "url" value= "${url}" /> <property name= "username" value= "${userName}" /> <property name= "password" value= "${password}" /> </dataSource> </environment> </environments> <mappers> <mapper resource= "mybatis/mail.xml" /> </mappers> </configuration> |
可见xml文件中<configuration>中所有内容都已经被成功解析并放在XNode中了,剩下的只要调用XNode的方法获取自己想要的内容即可。
最后扫一眼parseConfiguration方法,之所以说扫一眼,因为之后要分析里面的一些常用的和重点的内容,这里只是列一下代码而已:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 and objectWrapperFactory 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 Mapper Configuration. Cause: " + e, e); } } |
这里就是逐个解析<configuration>标签下的子标签,并将数据设置到对应的属性中,这里要一个一个看一下。
settings解析
首先看settingsAsPropertiess(root.evalNode(“settings”))这句代码,显而易见这句话获取了<configuration>下的<settings>节点。跟一下代码的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private Properties settingsAsPropertiess(XNode context) { if (context == null ) { return new Properties(); } Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration. class , localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException( "The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)." ); } } return props; } |
第5行将节点解析成键值对的形式(Properties是Hashtable的子类),看一下props的toString方法打印的内容:
1 | {useGeneratedKeys= true , lazyLoadingEnabled= true , cacheEnabled= true } |
可见settings里面的数据已经被解析成了Properties了。之后还有一步,<settings>标签下的每个<setting>中的name属性不是随便填写的,都是MyBatis支持的配置,因此需要对Properties里面的Key做一个校验,校验的代码就是第7行至第12行的代码,其中有一个name不是MyBatis支持的就会抛出异常,MyBatis初始化整体失败。
至于具体校验的是哪些Key,这就要跟一下第7行的代码了,首先是MetaClass.forClass(Configuration.class, localReflectorFactory),第二个实参是XMLConfigBuilder里面直接new出来的,它的实际类型为DefaultReflectorFactory,看一下forClass方法实现:
1 2 3 | public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) { return new MetaClass(type, reflectorFactory); } |
看一下new MetaClass做了什么事:
1 2 3 4 | private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) { this .reflectorFactory = reflectorFactory; this .reflector = reflectorFactory.findForClass(type); } |
显而易见,继续跟一下第3行的代码DefaultRelectorFactory的findForClass方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public Reflector findForClass(Class<?> type) { if (classCacheEnabled) { // synchronized (type) removed see issue #461 Reflector cached = reflectorMap.get(type); if (cached == null ) { cached = new Reflector(type); reflectorMap.put(type, cached); } return cached; } else { return new Reflector(type); } } |
不管怎么样都会执行new Reflector(type)这一句代码,看一下此时做了什么事,注意传入的参数是Configuration的class对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public Reflector(Class<?> clazz) { type = clazz; addDefaultConstructor(clazz); addGetMethods(clazz); addSetMethods(clazz); addFields(clazz); readablePropertyNames = getMethods.keySet().toArray( new String[getMethods.keySet().size()]); writeablePropertyNames = setMethods.keySet().toArray( new String[setMethods.keySet().size()]); for (String propName : readablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } for (String propName : writeablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } } |
这么多方法至于具体要看哪个,要注意的是之前XMLConfigBuilder里面对于Key的判断是”!metaConfig.hasSetter(String.valueOf(key))”,代码的意思是判断的是否Key有set方法,那么显而易见这里要继续跟第5行的addSetMethods方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private void addSetMethods(Class<?> cls) { Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>(); Method[] methods = getClassMethods(cls); for (Method method : methods) { String name = method.getName(); if (name.startsWith( "set" ) && name.length() > 3 ) { if (method.getParameterTypes().length == 1 ) { name = PropertyNamer.methodToProperty(name); addMethodConflict(conflictingSetters, name, method); } } } resolveSetterConflicts(conflictingSetters); } |
到这里应该很明显了,结论就是:<setting>的name属性对应的值,必须在Configuration类有相应的Setter,比如设置了一个属性useGenerateKeys方法,那么必须在Configuration类中有setUseGenerateKeys方法才行。
顺便说一下第13行有一个resolveSetterConflicts方法,其作用是:Setter有可能在类中被重载导致有多个,此时取Setter中方法参数只有一个且参数类型与Getter一致的Setter。
properties解析
接着看一下propertiesElement(root.evalNode(“properties”))方法,这句读取的是<configuration>下的<properties>节点,代码实现为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | private void propertiesElement(XNode context) throws Exception { if (context != null ) { Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute( "resource" ); String url = context.getStringAttribute( "url" ); if (resource != null && url != null ) { throw new BuilderException( "The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other." ); } if (resource != null ) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null ) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null ) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); } } |
看到第4行~第7行的代码指定了MyBatis的<properties>标签下不能同时指定”resource”属性和”url”属性。
接着第9行~第13行的代码将.properties资源解析为Properties类,最后将Properties类设置到XPathParser和Configuration的variables属性中,variables是一个Propreties变量。
类型别名解析
跳过loadCustomVfs(settings)直接看typeAliasesElement(root.evalNode(“typeAliases”))这行,因为前者我也没看懂干什么用的,后者是用于定义类型的别名的,解析的是<configuration>下的<typeAliases>标签,用过MyBatis的应该很熟悉。看一下源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | private void typeAliasesElement(XNode parent) { if (parent != null ) { for (XNode child : parent.getChildren()) { if ( "package" .equals(child.getName())) { String typeAliasPackage = child.getStringAttribute( "name" ); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { String alias = child.getStringAttribute( "alias" ); String type = child.getStringAttribute( "type" ); try { Class<?> clazz = Resources.classForName(type); if (alias == null ) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException( "Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } } |
从源码实现中我们可以知道两点,<typeAliases>标签下可以定义<package>和<typeAlias>两种标签,但是看第4行和第7行的判断,这是一段if…else…,因此可以知道<package>标签和<typeAlias>标签只能定义其中的一种。首先看一下解析<package>标签的代码,第6行的registerAliases方法:
1 2 3 4 5 6 7 8 9 10 11 12 | public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find( new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for (Class<?> type : typeSet){ // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } } |
第3行根据路径packageName寻找它下面的”.class”文件拿到所有的”.class”文件对应的类的Class,然后遍历所有的Class,做了三层判断
此时此Class对应的类符合条件,会进行注册,通过registerAlias方法进行注册,看一下方法实现:
1 2 3 4 5 6 7 8 | public void registerAlias(Class<?> type) { String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias. class ); if (aliasAnnotation != null ) { alias = aliasAnnotation.value(); } registerAlias(alias, type); } |
第2行获取Class的simpleName,simpleName指的是移除了包名的名称,比如aa.bb.cc.Mail,getSimpleName()获取的就是Mail。
第3行获取类上面的注解Alias,如果Alias注解中有定义value属性且指定了值,那么第4行~第6行的判断优先取这个值作为Class的别名。
第7行注册别名:
1 2 3 4 5 6 7 8 9 10 11 | public void registerAlias(String alias, Class<?> value) { if (alias == null ) { throw new TypeException( "The parameter alias cannot be null" ); } // issue #748 String key = alias.toLowerCase(Locale.ENGLISH); if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { throw new TypeException( "The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'." ); } TYPE_ALIASES.put(key, value); } |
其实就做了两步操作:
- 将alias全部小写
- 将alias以及Class对象放到TYPE_ALIASES中,TYPE_ALIASES是一个HashMap
这样一个流程,就将<package>标签name属性路径下的Class(如果符合要求),全部放到了HashMap中以供使用。
接着看一下<typeAlias>标签的解析,也就是前面说的else部分:
1 2 3 4 5 6 7 8 9 10 11 12 | String alias = child.getStringAttribute( "alias" ); String type = child.getStringAttribute( "type" ); try { Class<?> clazz = Resources.classForName(type); if (alias == null ) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException( "Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } |
这里先解析<typeAlias>中的alias属性,再解析<typeAlias>中的type属性,当然alias也可以不定义,不定义走的就是第6行的registerAlias方法,定义走的就是第8行的registerAlias方法,这两个重载的registerAlias方法前面也都说过了,就不说了。
默认typeAlias
上面说的是自定义typeAlias,MyBatis本身也默认提供给开发者了一些typeAlias定义,在两处地方。第一处地方在Configuration的构造方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public Configuration() { typeAliasRegistry.registerAlias( "JDBC" , JdbcTransactionFactory. class ); typeAliasRegistry.registerAlias( "MANAGED" , ManagedTransactionFactory. class ); typeAliasRegistry.registerAlias( "JNDI" , JndiDataSourceFactory. class ); typeAliasRegistry.registerAlias( "POOLED" , PooledDataSourceFactory. class ); typeAliasRegistry.registerAlias( "UNPOOLED" , UnpooledDataSourceFactory. class ); typeAliasRegistry.registerAlias( "PERPETUAL" , PerpetualCache. class ); typeAliasRegistry.registerAlias( "FIFO" , FifoCache. class ); typeAliasRegistry.registerAlias( "LRU" , LruCache. class ); typeAliasRegistry.registerAlias( "SOFT" , SoftCache. class ); typeAliasRegistry.registerAlias( "WEAK" , WeakCache. class ); typeAliasRegistry.registerAlias( "DB_VENDOR" , VendorDatabaseIdProvider. class ); typeAliasRegistry.registerAlias( "XML" , XMLLanguageDriver. class ); typeAliasRegistry.registerAlias( "RAW" , RawLanguageDriver. class ); typeAliasRegistry.registerAlias( "SLF4J" , Slf4jImpl. class ); typeAliasRegistry.registerAlias( "COMMONS_LOGGING" , JakartaCommonsLoggingImpl. class ); typeAliasRegistry.registerAlias( "LOG4J" , Log4jImpl. class ); typeAliasRegistry.registerAlias( "LOG4J2" , Log4j2Impl. class ); typeAliasRegistry.registerAlias( "JDK_LOGGING" , Jdk14LoggingImpl. class ); typeAliasRegistry.registerAlias( "STDOUT_LOGGING" , StdOutImpl. class ); typeAliasRegistry.registerAlias( "NO_LOGGING" , NoLoggingImpl. class ); typeAliasRegistry.registerAlias( "CGLIB" , CglibProxyFactory. class ); typeAliasRegistry.registerAlias( "JAVASSIST" , JavassistProxyFactory. class ); ... } |
第二处地方是在TypeAliasRegistry的构造方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | 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 ); } |
对于这些数据,我们可以直接使用registerAlias方法的第一个参数对应的字符串而不需要定义这些typeAlias。
元素设置
继续MyBatis的Configuration加载源码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 and objectWrapperFactory 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 Mapper Configuration. Cause: " + e, e); } } |
上回看到了第7行的<typeAlias>标签的解析,后面先暂时跳过<plugins>、<objectFactory>、<objectWrapperFactory>、<reflectorFactory>、<typeHandlers>、<databaseIdProvider>这几部分,这几部分要么属于MyBatis中不太常用的,要么属于MyBatis中比较进阶的应用,之后再说。
现在先看一下元素设置的代码,即第12行的settingsElement方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | private void settingsElement(Properties props) throws Exception { configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty( "autoMappingBehavior" , "PARTIAL" ))); configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty( "autoMappingUnknownColumnBehavior" , "NONE" ))); configuration.setCacheEnabled(booleanValueOf(props.getProperty( "cacheEnabled" ), true )); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty( "proxyFactory" ))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty( "lazyLoadingEnabled" ), false )); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty( "aggressiveLazyLoading" ), true )); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty( "multipleResultSetsEnabled" ), true )); configuration.setUseColumnLabel(booleanValueOf(props.getProperty( "useColumnLabel" ), true )); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty( "useGeneratedKeys" ), false )); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty( "defaultExecutorType" , "SIMPLE" ))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty( "defaultStatementTimeout" ), null )); configuration.setDefaultFetchSize(integerValueOf(props.getProperty( "defaultFetchSize" ), null )); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty( "mapUnderscoreToCamelCase" ), false )); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty( "safeRowBoundsEnabled" ), false )); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty( "localCacheScope" , "SESSION" ))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty( "jdbcTypeForNull" , "OTHER" ))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty( "lazyLoadTriggerMethods" ), "equals,clone,hashCode,toString" )); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty( "safeResultHandlerEnabled" ), true )); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty( "defaultScriptingLanguage" ))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty( "callSettersOnNulls" ), false )); configuration.setUseActualParamName(booleanValueOf(props.getProperty( "useActualParamName" ), false )); configuration.setLogPrefix(props.getProperty( "logPrefix" )); @SuppressWarnings ( "unchecked" ) Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty( "logImpl" )); configuration.setLogImpl(logImpl); configuration.setConfigurationFactory(resolveClass(props.getProperty( "configurationFactory" ))); } |
看到这个方法的实现主要就是将之前解析出来的<settings>中的内容设置到Configuration中。好像一直忘了说一个事,Configuration是XMLConfigBuilder的父类BaseBuilder中的一个属性,BaseBuilder中存储了三个重要属性,画一张图来表示一下:
environments加载
接着就是<environments>的加载了,一个比较重要的属性,用于配置JDBC信息,对应的是environmentsElement(root.evalNode(“environments”))这句代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private void environmentsElement(XNode context) throws Exception { if (context != null ) { if (environment == null ) { environment = context.getStringAttribute( "default" ); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute( "id" ); if (isSpecifiedEnvironment(id)) { TransactionFactory txFactory = transactionManagerElement(child.evalNode( "transactionManager" )); DataSourceFactory dsFactory = dataSourceElement(child.evalNode( "dataSource" )); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } } |
第3行~第5行的代码,得到默认的JDBC环境名称。
第6行的代码开始遍历<environments>标签下的每一个<environment>标签,先第7行的代码获取<environment>下的id属性,接着第8行的代码判断当前的<environment>是不是默认的JDBC环境,也就是第3行~第5行代码获取到的default属性对应的值。从这段代码可以看出两个问题:
- 源码并没有对不满足第8行判断即不是默认<environment>的场景做判断,因此可以得到一个结论:<environments>标签下的default属性是一个必填属性。
- 即使配置再多的<environment>标签,MyBatis只会加载其中的一个<environment>
第9行的代码根据<transactionManager>标签获取事物管理器,本系列文章配置的是”JDBC”,那么实例化出来的是JdbcTransactionFactory(JDBC–>JdbcTransactionFactory的对应关系在Configuration构造函数配置的alias映射中),其他的还有ManagedTransactionFactory和SpringManagedTransactionFactory,其中前者是MyBatis原生支持的,后者是Spring框架支持的。
第10行的代码和第9行的代码差不多,根据<dataSource>标签获取数据源工厂DataSourceFactory,本系列文章配置的是”POOLED”,那么实例化出来的是PooledDataSourceFactory(POOLED–>PooledDataSourceFactory的对应关系在Configuration构造函数配置的alias映射中),其他的还有UnpooledDataSourceFactory和JndiDataSourceFactory。
第11行的代码根据DataSourceFactory获取DataSource,在MyBatis中根据配置分三种场景:
- PooledDataSourceFactory对应的DataSource是PooledDataSource
- UnpooledDataSourceFactory对应的DataSource是UnpooledDataSource
- JndiDataSourceFactory对应的DataSource要去JNDI服务上去找
第12行~第15行的代码比较简单,根据TransactionFactory和DataSource创建一个Environment并设置到Configuration。
mapper加载
config.xml中两个最重要的标签,一个是<environment>(JDBC环境信息),另一个就是mapper(sql文件映射)了。mapper的加载是”mapperElement(root.evalNode(“mappers”))”这句代码,看一下实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); 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()); 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." ); } } } } } |
看到<mappers>下可以定义<mapper>和<package>两种子标签,它们同样是二选一的关系,即只能定义其中一种,这里先看package分支的内容即根据类路径加载Mapper就不看了,基本不用的,就看else分支里面的内容,即根据<mapper>标签解析sql映射。
接着第8行~第10行分别获取每一个<mapper>中的resource、url、mapperClass,接着下面的判断很有意思:
- resource != null && url == null && mapperClass == null
- resource == null && url != null && mapperClass == null
- resource == null && url == null && mapperClass != null
这告诉我们了resource、url、mapperClass三个属性只能定义其中的一个,else分支中抛出的异常同样也印证了这一说法。本系列文章的例子定义的是resource且定义resource的方式最常用,因此进入第一个if判断。
第12行的代码上下文设置一下resource,不是很重要。
第13行的代码根据mapper文件路径获取InputStream,InputStream在之后将会被转为InputSource用来解析mapper文件。
第14行的代码获取一个XMLMapperBuilder,它的流程和上文分析的XMLConfigBuilder是一样的,里面也使用的是XPathParser将mapper文件解析为Document。
第15行的代码跟进去看一下实现,因为XMLMapperBuilder的parse方法和XMLConfigBuilder的parse方法有区别,毕竟解析的是两种MyBatis配置文件:
1 2 3 4 5 6 7 8 9 10 11 | public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode( "/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } |
第2行的代码判断了当前资源是否被加载过,如果没有被加载过则会执行第3行~第5行的代码。
首先是第3行的代码configurationElement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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" ); } 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); } } |
第3行的代码获取当前mapper文件的namespace,namespace是一个很重要的属性,所有的<sql>、<resultMap>、<insert>、<delete>、<update>、<select>标签,它们的id都是和namespace绑定的,从而确保全局的唯一性,当namespace未定义或者为空字符串的时候,第5行就会抛出异常,因此每个mapper文件的namespace都是一个必填内容。
第7行的代码在MapperBuilderAssistant中设置了一下namespace,这样后文可以通过MapperBuilderAssistant拿namespace而不需要每次传一个String类型的参数。
第8行~第13行的代码分别用于解析<cache-ref>、<cache>、<parameterMap>、<resultMap>、<sql>、<select>、<insert>、<update>、<delete>这几个标签,逐个看一下:
cacheRefElement方法用于解析<cache-ref>标签,总结如下:
- 解析完的CacheRef放在cacheRefMap中
- cacheRefMap是一个HashMap
- 位于Configuration对象中
- Key为mapper文件的namespace,Value为<cache-ref>中配置的namespace
cacheElement方法用于解析<cache>标签,总结如下:
- 会根据<cache>中配置的属性new出一个org.apache.ibatis.cache.Cache
- 使用此Cache作为MyBatis缓存
parameterMapElement方法用于解析<parameterMap>标签,总结如下:
- 解析完的ParameterMap放在parameterMaps中
- parameterMaps是一个StrictMap
- 位于Configuration对象中,StrictMap是HashMap的子类
- Key为当前mapper的namespace+”.”+<parameterMap>标签中的id属性,Value为ParameterMap对象
resultMapElements方法用于解析<resultMap>标签在,总结如下:
- 解析完的ResultMap放在resultMaps中
- resultMaps是一个StrictMap,
- 位于Configuration对象中
- Key为当前mapper的namespace+”.”+<resultMap>标签中的id属性,Value为ResultMap对象
sqlElement方法用于解析<sql>标签,总结如下:
- 解析完的内容放在sqlFragments中
- sqlFragments是一个StrictMap
- 位于XMLMapperBuilder对象中
- Key为当前mapper的namespace+”.”+<sql>标签中的id属性,Value为sql这个XNode本身
buildStatementFromContext用于解析<select>、<insert>、<update>、<delete>这四个标签,总结如下:
- 解析完的内容放在mappedStatements中
- mappedStatements是一个StrictMap
- 位于Configuration对象中
- Key为当前mapper的namespace+”.”+<select>|<insert>|<update>|<delete>标签中的id属性,Value为MappedStatement对象
构建SqlSessionFactory
最后一步,构建SqlSessionFactory,回看前面SqlSessionFactoryBuilder的build方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException( "Error building SqlSession." , e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } |
第4行方法的parser.parse()这句之前一直在分析,将配置文件转换为了MyBatis中定义的各种对象且绝大部分配置存储在Configuration中,少部分配置存储在XMLConfigBuilder的父类BaseBuilder中。
接着就是外层的build方法了,看下实现:
1 2 3 | public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } |
最终构建出来的SqlSessionFactory是DefaultSqlSessionFactory,以Configuration对象为形参。
参考文章: