This paper analyzes the parsing process of Mybatis configuration file Mybatis -config. XML in detail. It includes the loading of various attributes, placeholder replacement and other important functions. Follow this article to analyze and understand the process.
The test program
Creating a test class
public class BaseFlowTest {
@Test
public void baseTest(a) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(1); }}}Copy the code
The configuration file is as follows:
<?xml version="1.0" encoding="UTF-8" ? >
<configuration>
<properties resource="classpath:jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
</configuration>
Copy the code
Analytical steps
Because this module mainly studied the role of the Parsing module, which was responsible for parsing configuration files, the resource file flow was ignored and the construction of the SqlSessionFactory was focused on
To build (Inputstream) method, calls for the build (Inputstream, String, the Properties) method.
The function of this method is mainly divided into two steps:
- structure
XMLConfigBuilder
- call
XMLConfigBuilder
的parse()
Method to parse the configuration file
structureXMLConfigBuilder
Construct XPathParser and pass an entityResolver as XMLMapperEntityResolver.
XPathParser
XPathParser is Mybatis extension to the XPath parser for parsing XML configuration files such as Mybatis -config. XML and * mapper.xml.
The class contains five attributes:
Document document
: XML document DOM objectboolean validation
: Whether documents are validated against DTD or XSDEntityResolver entityResolver
: XML entity parserProperties variables
:mybatis-config.xml
中properties
Collection of key-value pairs obtained under the tag (including imported configuration files)XPath xpath
:XPath
The parser
The XPathParser constructor in the figure is the constructor called by XMLConfigBuilder, which calls one of the class’s generic assignment methods and then calls the createDocument method to create a DOM object from the input stream of the XML file.
Simple assignment operations.
The createDocument method is also simple, consisting of three steps:
- Create from Settings
DocumentBuilderFactory
object - from
DocumentBuilderFactory
Created in theDocumentBuilder
Object and sets the properties - call
XPath
Parsing XML files
Initialize theconfiguration
After obtaining the XML DOM object through XPathParser, the generic constructor for the class is called
This method calls the constructor of the parent class, but mainly initializes the Configuration, setting the default TypeAlias and TypeHandler in Mybatis, and initializing the various containers used to store the different configurations.
parse
After the initial configuration, call the parse method.
The method first checks to see if the configuration file has been parsed. If not, perform the following two steps:
- use
XPathParser
Access to XMLconfiguration
Node content - parsing
configuration
All configurations under
evalNode
The evalNode method retrieves Node contents that satisfy the expression based on XPath expressions. Finally, the contents of Node types are wrapped into objects of type XNode for easy replacement of dynamic values.
This method only retrieves the content of a particular node, as opposed to the evalNodes method, which retrieves the content of all nodes that satisfy the expression.
parseConfiguration
parsingmybatis-config.xml
This method is the core method for parsing mybatis-config.xml, which summarizes the various methods for parsing custom values in XML.
Some of the key methods of this method call are examined below
propertiesElement
The propertiesElement method is used to get the key-value pairs configured in the
Methods and steps are as follows:
- To obtain
properties
Child nodesproperty
Down all key value pairs - To obtain
properties
In the tagurl
orresource
Property specifies the key-value pairs in the resource (only one of two properties can exist) - Puts the collection of retrieved key-value pairs into
XMLConfigBuilder
Of the classconfiguration
和parser
Variable.
settingsAsProperties
SettingsAsProperties resolves the values of configurations under the Settings TAB, such as common configurations such as mapUnderscoreToCamelCase and useGeneratedKeys.
Methods and steps:
- To obtain
settings
All key-value pairs under the tag - To obtain
Configuration
Kind of the correspondingMetaClass
(Reflection utility class packaged by Mybatis, including all kinds of metadata information of this class) - through
metaConfig
Determine whether Mybatis supports itsetting
The key configured in the tag does not directly throw an exception - return
settings
The collection of key-value pairs under
typeAliasesElement
The typeAliasesElement method is used to obtain the typeAlias of the typeAliases configuration in mybatis-config. XML.
The method steps are as follows:
- To obtain
typeAliases
A child of the tag - Parsing child tags
- parsing
package
The label - parsing
typeAlias
The label
- parsing
- Register an alias for the class
There are two subtags under the typeAliases tag: typeAlias and Package.
In fact, the resolution of the Package tag includes the resolution of the properties in The typeAlias, so I’ll just analyze the typeAlias fetch of the Package tag.
RegisterAliases method steps are as follows:
- Calling Mybatis
io
Under the bagResolverUtil
Of the classfind
With the aid ofVFS
Finds the class file under the specified package - Iterate over each class that you get,Filter inner classes, interfaces, and anonymous classesTo invoke the alias registration method
registerAlias
This method sets the custom alias to lowercase and determines whether the alias has a value. If not, the registration is successful.
pluginElement
The pluginElement method loads a custom plug-in.
The steps of this method are as follows:
- traverse
plugins
下plugin
node - To obtain
plugin
nodeinterceptor
Property value, and load corresponding classes according to the property value. The alias has already been loaded, so the method first checks if the property value is an alias. If not, useResources
The class loads the corresponding class file. - for
interceptor
Loads the set properties and willinterceptor
joinconfiguration
In the.
objectFactoryElement
This configuration is not really used much, but is used to override the object instantiation behavior of the default object factory, so that you can create objects that meet your needs.
The procedure for the objectFactoryElement method is exactly the same as the pluginElement procedure. It just gets the property type.
objectWrapperFactoryElement
ObjectWrapperFactoryElement method agree with objectFactoryElement, simply create objects into ObjectWrapper, the class of object attribute provides good packing method to operate.
reflectorFactoryElement
Like objectWrapperFactoryElement reflectorFactoryElement method will create a class for information about objects Reflector, the class is a class encapsulates the information of some reflection method.
settingsElement
SettingsElement Loads the configuration under Settings into the Configuration.
Because there are many SQL-related configuration items, you need to load the SQL connection information into Configuration before loading it.
environmentsElement
The environmentsElement method parses the configuration of the database connection in mybatis-config.xml.
Environments can have multiple environment tags, but Mybatis will only load environments whose IDS are equal to the value of the Environments default attribute.
The main steps are as follows:
- Get the default environment ID
- traverse
environment
, only when the ID is equal to the default ID, the subsequent flow is started - parsing
transactionManager
The tag gets the factory for the specified transaction type, parseddataSource
The tag gets the factory for the specified data source type - Build according to the above factory
Environment
In theconfiguration
databaseIdProviderElement
DatabaseIdProviderElement method is used to resolve multiple data source configuration.
The steps of this method are as follows:
- To obtain
databaseIdProvider
The labeltype
Property value corresponding toDatabaseIdProvider
- To obtain
configuration
Database connection information in - Connect to the database, get the product name of the database, and
databaseIdProvider
Of the sublabel under the labelname
Attribute matching - Will match
name
The correspondingvalue
Set todatabaseId
And in theconfiguration
中
typeHandlerElement
The typeHandlerElement method is used to register custom typeHandler.
The entire method process is similar to that of typeAliases, except that the container in which the configuration is stored is different, which is not explained here.
mapperElement
The mapperElement method is used to parse the * mapper.xml configuration file or the *Mapper interface.
The main steps of this method are:
- traverse
mappers
Each node below - Parse by sublabel type
- If the sublabel is
package
, scans all interfaces under the packet - If the sublabel is
mapper
To obtainresource
,url
andclass
Is not empty attribute value, and then according to the specific attribute for the corresponding parsing
- If the sublabel is
- use
MapperBuilder
的parse
Method to parse the corresponding XML or interface class
The Mapper mapping configuration will be explained later.
At this point, all the configurations in mybatis-config. XML have been loaded
supplement
Placeholder loading
In the module that parses Environments, the configuration in mybatis-config.xml looks like this. Placeholders are used to dynamically replace connection information from the data source.
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
Copy the code
Before the placeholders are parsed, the file that holds the key-value pairs is loaded, and the key-value pairs are stored in the Variables property of XMLConfigBuilder and XPathParser.
Next, analyze when and how these placeholders are replaced.
Since environmentsElement
As you can see, the method parses the dataSource node when it gets the dataSource factory, and then calls the dataSourceElement method to process the node.
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
Copy the code
Enter the DataSourceElement method.
The getChildrenAsProperties method is called to resolve the child tags under the datasource tag
Properties props = context.getChildrenAsProperties();
Copy the code
Here we get all the children of the datasource using the getChildren method (here the placeholders have been replaced; Just like JavaScript DOM, text nodes are retrieved. Trace into the getChildren method.
The getChildren method calls getChildNodes to get all the child elements. The child elements of the node are then iterated, and if the child element node type is ELEMENT_NODE, a node of type XNode is constructed and added to the collection returned to the getChildrenAsProperties method.
Deliberately constructed hereXNode
Type node instead of returning directlyNode
Type of node because in constructionXNode
The dynamic values are replaced during the node process. As you can see, in the callXNode
When a method is constructed, it stores the key-value pairs of variables in the resource filevariables
Passed as a parameter toXNode
.
In the previous article, when an element node is obtained, such as
So let’s go to the parseAttributes method.
Within this method, all properties of the node are iterated over, replacing placeholders with the Parse method of PropertyParser.
Finally, we get to the core method of replacing placeholders.
In this method, we construct the variable symbol handler VariableTokenHandler and pass it to the GenericTokenParser. We set the start symbol of the variable directly to ${and the end symbol to}, in line with our placeholder ${driver}.
The parse method is then called to replace the placeholders.
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// find the placeholder to start the marker
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
// Record parsed strings
final StringBuilder builder = new StringBuilder();
// Record the literal value of the placeholder. If the dynamic value is ${val}, expression = val
StringBuilder expression = null;
while (start > -1) {
// If openToken is preceded by an escape character
if (start > 0 && src[start - 1] = ='\ \') {
// Instead of parsing the placeholder literal, get the value with the escape removed
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
}
// If openToken is not preceded by an escape character
else {
if (expression == null) {
expression = new StringBuilder();
} // If you found a placeholder literal before, empty it this time
else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
// Find the placeholder end tag
// If the end tag is preceded by an escape character, the result is spelled with the string after the escape character is removed, and the placeholder end tag is searched again
if (end > offset && src[end - 1] = ='\ \') {
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
}
// If no escape character precedes the placeholder end tag found, the literal between openToken and closeTokenlse is assigned to Express for subsequent parsing
expression.append(src, offset, end - offset);
break;
}
// If no closeToken corresponding to openToken is found, the entire string is returned as the result string
if (end == -1) {
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// Use a specific TokenHandler to get the value of the placeholder literalbuilder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); }}// If there is no openToken after offset, the while loop is broken
start = text.indexOf(openToken, offset);
}
// Concatenate the part after closeToken
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
Copy the code
It’s a long method, but the algorithm is easy to understand. And Mybatis under the source test package provides a lot of test class, including org. Apache. The ibatis. Parsing. GenericTokenParserTest. It’s easy to run the unit tests to understand this section of the method.
Before explaining the method flow, let me elaborate on a few variables in the method:
offset
: marks the position in the text that has been parsedbuilder
: Records the parsed stringexpress
: Records variables in placeholders
In addition, a placeholder is not recognized as a placeholder if it is preceded by an escape character.
Explain the process in words:
-
First get the text where the placeholder start symbol ${appears start.
-
If so, determine if the symbol is preceded by an escape character \
2.1 If so, concatenate all strings from offset to ${into the processed string Builder and move the offset to start + OpenToken.length (). Go to step 7.
2.2 If not, escape indicates the start of the placeholder. Go to step 3.
-
Find the position where the end symbol} appears end
3.1 If found, proceed to Step 4
3.2 If no, go to Step 5
-
Check if the symbol precedes the escape character \
4.1 If so, put all values from offset to end into Express, mark them as variables in placeholders and move the offset to end + closetoken.length (). Go to step 3 and continue looking for the terminator.
4.2 If not, pass the variable between start and end into Express as a placeholder. Go to step 6.
-
If no end character} is found, add all the unparsed parts to builder, set offset to src.length, that is, all the text of the tag is parsed, go to step 9.
-
The handleToken method of VariableTokenHandler is called to retrieve the corresponding value of the placeholder variable, and returns the original value of the placeholder if it is not retrieved. Concatenate this value into the Builder and move the offset to end + closetok.length ().
-
Call start = text.indexof (openToken, offset) to find the starting position of the placeholder.
-
Concatenate the string after the last placeholder closing tag}.
-
Returns the parsed string Builder.tostring ().
The handleToken method invoked in the process is simple. Similar to finding a value for a key in a HashMap. There will be cases where variables have default values in handleToken (in real development, variable defaults are rarely turned on).
At this point, the placeholder substitution is complete.
conclusion
This article analyzes the loading process of Mybatis -config. XML. There are many methods involved, but none of them are complicated.
During loading, only the methods under the package were analyzed in detail, and the role of the other modules would be analyzed later.