Beauty of mybatis source code: 2.8. Parse the objectFactory element and configure the object creation factory of mybatis

Keywords: Programming Mybatis MySQL Attribute JDBC

Parse the objectFactory element and configure the object creation factory of mybatis

In Mybatis, there are many operations to instantiate objects through reflection, such as converting JDBC operation results to specific instance objects based on reflection.

For example, the following data are available:

MYSQL data:

Name Sex (sex) Age
panda male 18

JAVA object:

public class User{
  private String name;
  private String sex;
  private Integer age;
  // Omit getter/setter
}

After getting MYSQL data, convert the data to specific User object based on Reflection:

user={
  "name":"panda",
  "sex":"male",
  "age":18,
}

For this operation, Mybatis provides an interface definition - ObjectFactory, which is responsible for instantiating objects. Its default implementation class is DefaultObjectFactory, which is hard coded in the configuration object:

/**
 * Configure object creation factory
 */
protected ObjectFactory objectFactory = new DefaultObjectFactory();

ObjectFactory is defined as follows:

/**
 * mybatis Object creation factory for
 *
 * @author Clinton Begin
 */
public interface ObjectFactory {

    /**
     * Configure parameters to be used at run time
     *
     * @param properties configuration properties configuration parameter
     */
    void setProperties(Properties properties);

    /**
     * Create an instance of the specified type using the default construction method
     *
     * @param type Specify type
     */
    <t> T create(Class<t> type);

    /**
     * Create an instance of the specified type by constructing methods and parameters
     * .
     *
     * @param type                Specify object type
     * @param constructorArgTypes Construct parameter type set
     * @param constructorArgs     Construct parameter set
     */
    <t> T create(Class<t> type, List<class<?>&gt; constructorArgTypes, List<object> constructorArgs);

    /**
     * Returns true if the object can contain a set of other objects, the purpose of this method is to be compatible with non Collection collections.
     *
     * @param type object type
     * @return Is it a collection
     * @since 3.1.0
     */
    <t> boolean isCollection(Class<t> type);

}

There are four methods defined in ObjectFactory:

  • setProperties is used to configure parameters required at run time
  • create(Class) is responsible for the instantiation of an object using the default nonparametric construction method
  • The create (class, list < class <? > & gt;, list < Object >) method is responsible for the instantiation of an object by specifying the constructor of the constructor parameter
  • The isCollection method is used to determine whether the incoming type is a collection that can contain other objects.

The default implementation class of ObjectFactory DefaultObjectFactory is relatively simple as a whole. Its create(Class) method implementation is given to the create (class, list < class <? > & gt;, list < Object >) method to complete:

/**
 * Instantiate the specified object with the nonparametric construction method of the specified type
 *
 * @param type Specify type
 * @param <t>  type
 * @return Type instance
 */
@Override
public <t> T create(Class<t> type) {
    return create(type, null, null);
}

In the create (class, list < class <? > & gt;, list < Object >) method, call the resolveInterface method to complete the type processing operation, and then delegate the specific instantiation work to the instantiateClass to complete:

/**
 *  Create an instance of the specified type by constructing methods and parameters
 * @param type                Specify object type
 * @param constructorArgTypes Construct parameter type set
 * @param constructorArgs     Construct parameter set
 * @param <t> type
 * @return Type instance
 */
@SuppressWarnings("unchecked")
@Override
public <t> T create(Class<t> type, List<class<?>&gt; constructorArgTypes, List<object> constructorArgs) {
    // Get the type to create
    Class<!--?--> classToCreate = resolveInterface(type);
    // Instantiation type
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}

Because interfaces cannot be instantiated directly, the resolveInterface method is responsible for converting common interface types to common subclass types for subsequent instantiation operations:

/**
 * Converting an interface to a child implementation class
 *
 * @param type Types to process
 * @return Effective implementation type
 */
protected Class<!--?--> resolveInterface(Class<!--?--> type) {
    Class<!--?--> classToCreate;
    if (type == List.class || type == Collection.class || type == Iterable.class) {
        classToCreate = ArrayList.class;
    } else if (type == Map.class) {
        classToCreate = HashMap.class;
    } else if (type == SortedSet.class) { // issue #510 Collections Support
        classToCreate = TreeSet.class;
    } else if (type == Set.class) {
        classToCreate = HashSet.class;
    } else {
        classToCreate = type;
    }
    return classToCreate;
}

The instantialeclass method, which is responsible for instantiation operations, looks complex, but in fact, its logic is relatively simple:

/**
 * Create an instance of the specified type by constructing methods and parameters
 *
 * @param type                Specify object type
 * @param constructorArgTypes Construct parameter type collection
 * @param constructorArgs     Construct parameter set
 * @param <t>                 type
 * @return Type instance
 */
private <t> T instantiateClass(Class<t> type, List<class<?>&gt; constructorArgTypes, List<object> constructorArgs) {
    try {
        Constructor<t> constructor;
        // Handle nonparametric construction
        if (constructorArgTypes == null || constructorArgs == null) {
            constructor = type.getDeclaredConstructor();
            try {
                return constructor.newInstance();
            } catch (IllegalAccessException e) {
                if (Reflector.canControlMemberAccessible()) {
                    constructor.setAccessible(true);
                    return constructor.newInstance();
                } else {
                    throw e;
                }
            }
        }

        // Handling parametric constructs
        constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
        try {
            return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
        } catch (IllegalAccessException e) {
            if (Reflector.canControlMemberAccessible()) {
                constructor.setAccessible(true);
                return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
            } else {
                throw e;
            }
        }
    } catch (Exception e) {
        // Splicing structure parameter type data
        StringBuilder argTypes = new StringBuilder();
        if (constructorArgTypes != null &amp;&amp; !constructorArgTypes.isEmpty()) {
            for (Class<!--?--> argType : constructorArgTypes) {
                argTypes.append(argType.getSimpleName());
                argTypes.append(",");
            }
            argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
        }
        // Splicing construction parameter real parameter object
        StringBuilder argValues = new StringBuilder();
        if (constructorArgs != null &amp;&amp; !constructorArgs.isEmpty()) {
            for (Object argValue : constructorArgs) {
                argValues.append(String.valueOf(argValue));
                argValues.append(",");
            }
            argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
        }
        // Rethrow exception
        throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
}

The instantialeclass method will instantiate the target object through the nonparametric construction method and the parametric construction method respectively according to whether the caller has passed in the construction parameters. If the object cannot be instantiated, try refreshing the access rights of the construction method and try again. If it still cannot be instantiated, an exception will be thrown.

Because DefaultObjectFactory does not support property configuration, its setProperties method is actually an empty implementation:

@Override
public void setProperties(Properties properties) {
    // no props for default
}

The last method to determine whether the specified type is a Collection object is simply to determine whether the specified type is a subclass / implementation class of the Collection interface

@Override
public <t> boolean isCollection(Class<t> type) {
    return Collection.class.isAssignableFrom(type);
}

After a preliminary understanding of objectfactory and its implementation classes, we return to the analysis of objectfactory elements.

The DTO of objectFactory is defined as follows:

<!--ELEMENT configuration (..., objectFactory?, ...)-->

<!--ELEMENT objectFactory (property*)-->
<!--ATTLIST objectFactory
type CDATA #REQUIRED
-->

<!--ELEMENT property EMPTY-->
<!--ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
-->

In the global configuration file of mybatis, at most one objectfactory node can be configured. Objectfactory has a required type attribute, which is used to point to an instance of objectfactory. Here, you can use the instance alias. At the same time, under objectfactory, you can configure one or more property sub elements to set the parameters required by objectfactory instance when it runs.

The parseConfiguration method of XmlConfigBuilder is responsible for triggering the resolution of objectFactory nodes:

...
// Configure object creation factory
objectFactoryElement(root.evalNode("objectFactory"));
...

The objectFactoryElement method is responsible for the resolution and configuration of the objectFactory elements:

/**
 * Resolving objectFactory nodes
 *
 * @param context objectFactory node
 */
private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
        // Get the type attribute of objectFactory
        String type = context.getStringAttribute("type");
        // Get parameter configuration
        Properties properties = context.getChildrenAsProperties();
        // Resolve alias get instance
        ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
        // configuration parameter
        factory.setProperties(properties);
        // Configure object creation factory
        configuration.setObjectFactory(factory);
    }
}

The implementation of the objectFactoryElement method is also very simple. After the ObjectFactory instance is obtained through reflection, the Properties parsed from the property will be set to the instance,

Finally, setObjectFactory, the method given to the Configuration object, assigns the obtained objectfactory instance to the property of Configuration's objectfactory.

/**
 * Configure object creation factory
 *
 * @param objectFactory Object creation factory
 */
public void setObjectFactory(ObjectFactory objectFactory) {
    this.objectFactory = objectFactory;
}

At this point, the parsing of the objectFactory element is complete.

Pay attention to me and learn more together

Posted by gazoo on Fri, 26 Jun 2020 20:48:57 -0700