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<?>> 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<?>> 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<?>> 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 && !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 && !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.