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:

  • structureXMLConfigBuilder
  • callXMLConfigBuilderparse()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 object
  • boolean validation: Whether documents are validated against DTD or XSD
  • EntityResolver entityResolver: XML entity parser
  • Properties variables:mybatis-config.xmlpropertiesCollection of key-value pairs obtained under the tag (including imported configuration files)
  • XPath xpath:XPathThe 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 SettingsDocumentBuilderFactoryobject
  • fromDocumentBuilderFactoryCreated in theDocumentBuilderObject and sets the properties
  • callXPathParsing 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:

  • useXPathParserAccess to XMLconfigurationNode content
  • parsingconfigurationAll 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.

parseConfigurationparsingmybatis-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 tag in mybatis-config. XML, including the key-value pairs in the imported configuration file.

Methods and steps are as follows:

  • To obtainpropertiesChild nodespropertyDown all key value pairs
  • To obtainpropertiesIn the tagurlorresourceProperty specifies the key-value pairs in the resource (only one of two properties can exist)
  • Puts the collection of retrieved key-value pairs intoXMLConfigBuilderOf the classconfigurationparserVariable.
settingsAsProperties

SettingsAsProperties resolves the values of configurations under the Settings TAB, such as common configurations such as mapUnderscoreToCamelCase and useGeneratedKeys.

Methods and steps:

  • To obtainsettingsAll key-value pairs under the tag
  • To obtainConfigurationKind of the correspondingMetaClass(Reflection utility class packaged by Mybatis, including all kinds of metadata information of this class)
  • throughmetaConfigDetermine whether Mybatis supports itsettingThe key configured in the tag does not directly throw an exception
  • returnsettingsThe 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 obtaintypeAliasesA child of the tag
  • Parsing child tags
    • parsingpackageThe label
    • parsingtypeAliasThe label
  • 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 MybatisioUnder the bagResolverUtilOf the classfindWith the aid ofVFSFinds 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 methodregisterAlias

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:

  • traversepluginspluginnode
  • To obtainpluginnodeinterceptorProperty 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, useResourcesThe class loads the corresponding class file.
  • forinterceptorLoads the set properties and willinterceptorjoinconfigurationIn 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
  • traverseenvironment, only when the ID is equal to the default ID, the subsequent flow is started
  • parsingtransactionManagerThe tag gets the factory for the specified transaction type, parseddataSourceThe tag gets the factory for the specified data source type
  • Build according to the above factoryEnvironmentIn theconfiguration
databaseIdProviderElement

DatabaseIdProviderElement method is used to resolve multiple data source configuration.

The steps of this method are as follows:

  • To obtaindatabaseIdProviderThe labeltypeProperty value corresponding toDatabaseIdProvider
  • To obtainconfigurationDatabase connection information in
  • Connect to the database, get the product name of the database, anddatabaseIdProviderOf the sublabel under the labelnameAttribute matching
  • Will matchnameThe correspondingvalueSet todatabaseIdAnd 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:

  • traversemappersEach node below
  • Parse by sublabel type
    • If the sublabel ispackage, scans all interfaces under the packet
    • If the sublabel ismapperTo obtainresource,urlandclassIs not empty attribute value, and then according to the specific attribute for the corresponding parsing
  • useMapperBuilderparseMethod 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 hereXNodeType node instead of returning directlyNodeType of node because in constructionXNodeThe dynamic values are replaced during the node process. As you can see, in the callXNodeWhen a method is constructed, it stores the key-value pairs of variables in the resource filevariablesPassed as a parameter toXNode.

In the previous article, when an element node is obtained, such as , this constructor is called. In this constructor, the attributes of the node and the content body of the node are parsed. A placeholder occupies an attribute of a node.

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 parsed
  • builder: Records the parsed string
  • express: 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:

  1. First get the text where the placeholder start symbol ${appears start.

  2. 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.

  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

  4. 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.

  5. 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.

  6. 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 ().

  7. Call start = text.indexof (openToken, offset) to find the starting position of the placeholder.

  8. Concatenate the string after the last placeholder closing tag}.

  9. 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.