Beauty of Mybatis source code: 2.1. Build XmlConfigBuilder and prepare the basic environment for parsing XML files

Build XmlConfigBuilder to prepare the basic environment for parsing XML files

As mentioned earlier, the XmlConfigBuilder object is mainly used to parse the global Configuration file of mybatis and obtain the instance of the Configuration object.

XmlConfigBuilder exposes six construction methods, which can be divided into two categories according to the input stream type of mybatis configuration file:

They are respectively responsible for processing the configuration file in the form of byte stream and character stream.

// mybatis configuration for handling byte stream types
XMLConfigBuilder(InputStream inputStream);
XMLConfigBuilder(InputStream inputStream, String environment);
XMLConfigBuilder(InputStream inputStream, String environment, Properties props);

// mybatis configuration for handling character stream types
XMLConfigBuilder(Reader reader);
XMLConfigBuilder(Reader reader, String environment);
XMLConfigBuilder(Reader reader, String environment, Properties props);

In the specific code implementation, the calls of these two types of construction methods will eventually fall on the following two methods according to the difference of the file stream (because we passed in the byte stream earlier, the first method is triggered here):

/**
 * @param inputStream  Profile input stream
 * @param environment Current environment ID
 * @param props       User defined properties
 */
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(
                    inputStream,/*XML File input stream*/
                    true, /*Turn on verification*/
                    props, /*Property ginseng*/
                    new XMLMapperEntityResolver() /*XML Entity Resolver */
            ) /*Create a new instance of XML parser*/
            , environment/*Environment object*/
            , props /*Property parameter*/
    );
}
/**
 * @param reader      Profile input stream
 * @param environment Current environment ID
 * @param props       User defined properties
 */
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader,
                    true/*DTD check or not*/
                    , props /*parameter*/
                    , new XMLMapperEntityResolver() /*XML Finder of corresponding DTD file*/
            )/*Building an XPath parser*/
            , environment /*Current environment*/
            , props /*parameter*/
    );
}

In the above two methods, mybati will use the incoming configuration file input stream to create an XPathParser object, and then use the XPathParser object to call xmlconfigbuilder's private construction method XMLConfigBuilder(XPathParser parser, String environment, Properties props) to complete the construction process of xmlconfigbuilder object.

/**
 * @param parser      XPath Resolver
 * @param environment Environment ID
 * @param props       User defined properties
 */
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // Initializing Configuration objects and registering partial aliases and language drivers
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // set variable
    this.configuration.setVariables(props);
    // Initialize parsing label
    this.parsed = false;
    // Initialize environment container
    this.environment = environment;
    // Initialize Xml address resolver
    this.parser = parser;
}

XPathParser is a parsing tool class defined by mybatis to read data in XML documents based on XPath language. >XPath is the XML Path Language, which is used to determine the location of a part of an XML document.

When mybatis creates the XPathParser object, a new XMLMapperEntityResolver() object is passed in. We will talk about the function of the object later. Now we will ignore the specific implementation of the XPathParser object and continue to see the construction process of XmlConfigBuilder.

new XPathParser(reader,
                true/*DTD check or not*/
                , props /*parameter*/
                , new XMLMapperEntityResolver() /*XML Finder of corresponding DTD file*/
        )

In the private construction method of XmlConfigBuilder, mybatis will call the nonparametric construction method of Configuration to generate a Configuration object, and pass the Configuration object into the construction method of BaseBuilder, the parent class of XmlConfigBuilder, to complete the initialization of BaseBuilder.

// Initializing Configuration objects and registering partial aliases and language drivers
super(new Configuration());

The nonparametric construction method of Configuration includes the preparation of some basic data, including registering common type aliases, registering script language drivers and configuring the default script language drivers.

public Configuration() {
    // Register alias

    // Register JDBC alias
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    // Register transaction management alias
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    // Register JNDI alias
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    // Register pooled data source alias
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    // Register as a pooled data source
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    // Register permanent cache
    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    // Register first in first out cache
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    // Register recent minimum cache usage
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    // Register soft cache
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    // Register weak cache
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    // Register the provider that processes the database ID
    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    // Register XML based language driver
    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    //Register static language drivers (usually not required)
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    // Register Sl4j log
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    //Register Commons log
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    //Register log4j log
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    //Register log4j2 log
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    //Register jdk log log
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    //Register standard output log
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    //No log for registration
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    // Register CGLIB
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    // Register JAVASSIST
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    // XML language driven by default
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    // Support for native language drivers
    languageRegistry.register(RawLanguageDriver.class);
}

!! It should be noted that here mybatis defines that the default scripting language driver is the XML language driver with the alias of XML.

As we said before, XMLConfigBuilder object is a subclass of BaseBuilder. In BaseBuilder, there are three final decorated properties:

/**
 * Mybatis configuration information
 */
protected final Configuration configuration;
/**
 * Type alias registry
 */
protected final TypeAliasRegistry typeAliasRegistry;
/**
 * Type conversion processor registry
 */
protected final TypeHandlerRegistry typeHandlerRegistry;

Their initial assignment occurs in the construction method of BaseBuilder:

public BaseBuilder(Configuration configuration) {
      // Keep reference
      this.configuration = configuration;
      // Synchronize type alias registry from Mybatis configuration
      this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
      // Synchronize the type processor registry from the Mybatis configuration
      this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

In the above construction method, BaseBuilder caches the reference to the Configuration object, and points its typeHandlerRegistry and TypeAliasRegistry properties to the typeHandlerRegistry and TypeAliasRegistry properties of the Configuration object.

public class Configuration {
  ...
/**
    * Type processor registry
    */
   protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
   /**
    * Type alias registry is mainly used for entering and exiting parameters of executing SQL statements and shorthand of some classes
    */
   protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
   ...
 }
  • Type processor: Click to learn about mybatis type processor >Whether MyBatis sets a parameter in the prepared statement or takes a value from the result set, it will use the type processor to convert the obtained value into Java type in an appropriate way.

After calling the construction method of BaseBuilder, XMLConfigBuilder will pass the user-defined variable collection to the Configuration object and save it for later Configuration parsing.

// Cache user-defined variables
this.configuration.setVariables(props);

After that, reset the parsing flag and change its status to not enter the parsing operation, indicating that the parse() method is allowed to be called:

// Initialize parsing label
this.parsed = false;

Then record the current environment information for later configuration resolution:

// Initialize environment container
this.environment = environment;

Then, save the Xml address resolver currently in use:

// Initialize Xml address resolver
 this.parser = parser;

This completes the construction process of XMLConfigBuilder object. In addition to several properties involved in the construction method, XMLConfigBuilder also has a constant property, localReflectorFactory, which is hard coded as DefaultReflectorFactory in the code. We will learn after its function, regardless of it:

/**
 * Used to create {@ link org.apache.ibatis.reflection.Reflector }Object's factory
 */
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

Now let's go back to the XPathParser object that XMLConfigBuilder relies on.

There are many ways to construct the XPathParser. In addition to the corresponding XML documents that need to be passed in by users, you can also receive some other optional parameters. >Here, the incoming XML Document can take many forms, such as XML address links, input streams, and even Document objects.

There are three optional parameters:

  • The first is the validation of boolean type, which indicates whether DTD verification is performed on XML documents. The default value is false.

  • The second parameter is variables of type Properties, which represents user-defined property objects that can be read and used as placeholders throughout the configuration file.

  • The third parameter is the entityresolver object of type entityresolver, which is used to find the DTD validation file for the caller.

    As mentioned earlier, the EntityResolver parameter is hard coded as an object of type XMLMapperEntityResolver in the XmlConfigBuilder construction method.

    new XPathParser(reader,
                    true/*DTD check or not*/
                    , props /*parameter*/
                    , new XMLMapperEntityResolver() /*XML Finder of corresponding DTD file*/
            )/*Building an XPath parser*/
    

    His code is relatively simple and defines six constants:

    /**
     * IBATIS systemId of the global profile (for IBATIS compatibility)
     */
    private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
    /**
     * IBATIS Mapper systemId of the profile (for IBATIS compatibility)
     */
    private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
    /**
     * MYBATIS systemId of the global profile
     */
    private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
    /**
     * MYBATIS Mapper systemId of the configuration file
     */
    private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
    /**
     * MYBATIS DTD file corresponding to global configuration file
     */
    private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
    /**
     * MYBATIS MAPPER DTD file corresponding to configuration file
     */
    private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
    
    

    At the same time, the resolveEntity method of EntityResolver is implemented. In this method, XMLMapperEntityResolver will match a corresponding DTD validation file for the incoming systemId:

    /**
      * Get DTD file from local
      *
      * @param publicId Public signs
      * @param systemId System identifier
      * @return DTD
      */
     @Override
     public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
         try {
             if (systemId != null) {
                 String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
                 if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
                     // mybatis global profile` config.xml `Corresponding DTD file
                     return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
                 } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
                     // mybatis data mapping profile`* Mapper.xml `Corresponding DTD file
                     return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
                 }
             }
             return null;
         } catch (Exception e) {
             throw new SAXException(e.toString());
         }
     }
    

    Click to learn about systemId and publicId

    • systemId: system flag, usually used to directly indicate the actual location of the corresponding DTD resource, which is often unique within the scope of the application.
    • publicId: public flag, a globally unique flag, is usually associated with a specific DTD, indirectly indicating the location of DTD files, which is usually used in scenarios across multiple applications.

    In fact, EntityResolver and its implementation class XMLMapperEntityResolver is a standard policy pattern implementation. >Policy pattern is a common behavioral design pattern. Its function is: we can define a series of algorithms and encapsulate them so that they can replace each other. Policy pattern allows algorithms to change independently of using their clients

Because when we create the sqlSessionFactory object, the byte stream of the configuration file is passed in, the actual construction method of calling the XPathParser here is XPathParser (InputStream InputStream, Boolean validation, properties variables, entityresolver entityresolver) (the rest construction methods are similar to the code of this method):

/**
 * XPathParser
 *
 * @param inputStream    XML File input stream
 * @param validation     DTD verification or not
 * @param variables      User defined property objects that can be read and used as placeholders throughout the configuration file
 * @param entityResolver Custom resolver for finding DTD validation files
 */
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    // Call common constructor
    commonConstructor(validation, variables, entityResolver);
    // Convert the user's incoming XML file stream to a Document object
    this.document = createDocument(new InputSource(inputStream));
}

The code in this construction method can be divided into two parts. One is a general method for initializing attributes: commonConstructor. The other is a method for parsing the Document object corresponding to the XML file: createDocument

The content of commonConstructor method is relatively simple. The only thing worth mentioning is the object instance with XPath type assigned to the hard coding of XPath attribute. The code is as follows:

  /**
     * Common construction parameters, mainly used to configure DocumentBuilderFactory
     *
     * @param validation     DTD check or not
     * @param variables      Property configuration
     * @param entityResolver Entity Resolver 
     */
    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
        // DTD check or not
        this.validation = validation;
        // Configure XML entity parser
        this.entityResolver = entityResolver;
        //  Property configuration
        this.variables = variables;

        // Initialize XPath
        XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();
    }

The createDocument method mainly parses the XML file according to the current configuration to obtain the corresponding Document object:

    /**
     * Create a text object based on the input source
     *
     * @param inputSource Input source
     * @return Text object
     */
    private Document createDocument(InputSource inputSource) {
        // important: this must only be called AFTER common constructor
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            // Whether to verify the parsed document
            factory.setValidating(validation);
            // Whether to provide support for XML namespace
            factory.setNamespaceAware(false);
            //Ignore Comments 
            factory.setIgnoringComments(true);
            //Ignore whitespace
            factory.setIgnoringElementContentWhitespace(false);
            //Whether to resolve CDATA node to TEXT node
            factory.setCoalescing(false);
            //Expand entity reference node or not
            factory.setExpandEntityReferences(true);

            DocumentBuilder builder = factory.newDocumentBuilder();
            // Configure the XML document parser, now it is mainly XMLMapperEntityResolver
            builder.setEntityResolver(entityResolver);
            // Configure error processor
            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 {
                }
            });
            // Parse out DOM tree
            return builder.parse(inputSource);
        } catch (Exception e) {
            throw new BuilderException("Error creating document instance.  Cause: " + e, e);
        }
    }

After executing these two methods, the construction process of XPathParser is finished. At this time, XPathParser not only holds the Document object but also an Xpath accessor. Next, many operations of XPathParser will use Xpath to read the contents of the Document object.

Here is the property definition of the XPathParser object:

public class XPathParser {
    /**
     * XML object
     */
    private final Document document;
    /**
     * DTD verification or not
     */
    private boolean validation;
    /**
     * DTD Entity Resolver 
     */
    private EntityResolver entityResolver;
    /**
     * User defined property objects that can be read and used as placeholders throughout the configuration file.
     */
    private Properties variables;
    /**
     * XML Address accessor
     */
    private XPath xpath;
  }

At this point, the construction process of the XMLConfigBuilder object and its dependent objects is basically completed, and the next step is to perform the work of parsing the mybatis configuration file.

Pay attention to me and learn more together

Tags: Programming xml Mybatis Apache SQL

Posted on Fri, 05 Jun 2020 02:04:30 -0400 by _rhod