2.8.解析 objectFactory 元素 配置 Mybatis 的对象创建工厂

优质
小牛编辑
133浏览
2023-12-01

在Mybatis中有很多通过反射来实例化对象的操作,比如基于反射将JDBC操作结果转换为具体的实例对象。

比如现有如下数据:

MYSQL数据:

姓名(name)性别(sex)年龄(age)
熊猫18

JAVA对象:

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

获取MYSQL数据之后,基于反射将数据转换为具体的User对象:

user={
  "name":"熊猫",
  "sex":"男",
  "age":18,
}

针对这种操作,Mybatis提供了一个负责实例化对象的接口定义——ObjectFactory,其默认实现类是DefaultObjectFactory,被硬编码在configuration对象中:

/**
 * 配置对象创建工厂
 */
protected ObjectFactory objectFactory = new DefaultObjectFactory();

ObjectFactory定义如下:

/**
 * mybatis的对象创建工厂
 *
 * @author Clinton Begin
 */
public interface ObjectFactory {

    /**
     * 配置运行时需要使用的参数
     *
     * @param properties configuration properties 配置参数
     */
    void setProperties(Properties properties);

    /**
     * 使用默认的构造方法创建一个指定类型的实例
     *
     * @param type 指定类型
     */
    <T> T create(Class<T> type);

    /**
     * 通过构造方法和构造参数创建一个指定类型的实例
     * .
     *
     * @param type                指定对象类型
     * @param constructorArgTypes 构造参数类型集合
     * @param constructorArgs     构造参数集合
     */
    <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

    /**
     * 如果此对象可以包含一组其他对象则返回true,该方法的目的是为了兼容非Collection的集合。
     *
     * @param type 对象类型
     * @return 是否是一个集合
     * @since 3.1.0
     */
    <T> boolean isCollection(Class<T> type);

}

ObjectFactory中定义了四个方法:

  • setProperties用于配置运行时需要的参数
  • create(Class)负责使用默认的无参构造方法完成一个对象的实例化操作
  • create(Class,List<Class<?>>,List<Object>)方法则负责通过指定构造参数的构造方法来完成一个对象的实例化操作
  • isCollection方法用于判断传入的类型是否是一个可以包含其他对象的集合。

ObjectFactory的默认实现类DefaultObjectFactory整体比较简单,他的create(Class)方法实现交给了create(Class,List<Class<?>>,List<Object>)方法来完成:

/**
 * 利用指定类型的无参构造方法实例化指定对象
 *
 * @param type 指定类型
 * @param <T>  类型
 * @return 类型实例
 */
@Override
public <T> T create(Class<T> type) {
    return create(type, null, null);
}

create(Class,List<Class<?>>,List<Object>)方法中,调用resolveInterface方法完成类型的处理操作,就将具体实例化的工作委托给instantiateClass来完成了:

/**
 *  通过构造方法和构造参数创建一个指定类型的实例
 * @param type                指定对象类型
 * @param constructorArgTypes 构造参数类型集合
 * @param constructorArgs     构造参数集合
 * @param <T> 类型
 * @return 类型实例
 */
@SuppressWarnings("unchecked")
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    // 获取需要创建的类型
    Class<?> classToCreate = resolveInterface(type);
    // 实例化类型
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}

因为接口是不可以直接实例化的,所以resolveInterface方法负责将常见的接口类型转换为常用的子类类型,便于后续的实例化操作:

/**
 * 将接口转换为子实现类
 *
 * @param type 需要处理的类型
 * @return 有效实现类型
 */
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;
}

负责实例化操作的instantiateClass方法,看起来比较复杂,但是实际上逻辑却比较简单:

/**
 * 通过构造方法和构造参数创建一个指定类型的实例
 *
 * @param type                指定对象类型
 * @param constructorArgTypes 构造参数类型集合
 * @param constructorArgs     构造参数集合
 * @param <T>                 类型
 * @return 类型实例
 */
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
        Constructor<T> constructor;
        // 处理无参构造
        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;
                }
            }
        }

        // 处理有参构造
        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) {
        // 拼接构造参数类型数据
        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 ,
        }
        // 拼接构造参数实参对象
        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 ,
        }
        // 重新抛出异常
        throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
}

instantiateClass方法会根据调用方是否传入了构造参数,分别通过无参构造方法和有参构造方法来实例化目标对象,如果不能实例化对象,那就尝试刷新构造方法的访问权限后重试,如果依然无法实例化,就会抛出异常。

因为DefaultObjectFactory不支持属性配置,所以他的setProperties方法实际上是一个空实现:

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

最后一个负责判断指定类型是否为集合对象的方法,则只是简单的判断指定类型是否为Collection接口的子类/实现类而已:

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

在初步了解了ObjectFactory及其实现类之后,我们回到objectFactory元素的解析工作上来。

objectFactory的DTO定义如下:

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

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

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

在mybatis的全局配置文件内,最多只能配置一个objectFactory节点,objectFactory有一个必填的type属性,该属性用于指向一个ObjectFactory的实例,这里可以使用实例别名,同时在objectFactory下可以配置一个或多个property子元素,用来设置ObjectFactory实例运行时需要的参数。

XmlConfigBuilderparseConfiguration方法负责触发objectFactory节点的解析工作:

...
// 配置对象创建工厂
objectFactoryElement(root.evalNode("objectFactory"));
...

objectFactoryElement方法则负责完成objectFactory元素的解析配置工作:

/**
 * 解析objectFactory节点
 *
 * @param context objectFactory节点
 */
private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
        // 获取objectFactory的type属性
        String type = context.getStringAttribute("type");
        // 获取参数配置
        Properties properties = context.getChildrenAsProperties();
        // 解析别名获取实例
        ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
        // 配置参数
        factory.setProperties(properties);
        // 配置对象创建工厂
        configuration.setObjectFactory(factory);
    }
}

objectFactoryElement方法的实现也很简单,通过反射获取ObjectFactory的实例之后,将由property解析而成的Properties设值到实例中,

最后交给Configuration对象的方法setObjectFactory将获取到的ObjectFactory实例赋值给ConfigurationobjectFactory的属性。

/**
 * 配置对象创建工厂
 *
 * @param objectFactory 对象创建工厂
 */
public void setObjectFactory(ObjectFactory objectFactory) {
    this.objectFactory = objectFactory;
}

到此,objectFactory元素的解析就完成了。