2.8.解析 objectFactory 元素 配置 Mybatis 的对象创建工厂
在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
实例运行时需要的参数。
XmlConfigBuilder
的parseConfiguration
方法负责触发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
实例赋值给Configuration
的objectFactory
的属性。
/**
* 配置对象创建工厂
*
* @param objectFactory 对象创建工厂
*/
public void setObjectFactory(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
到此,objectFactory
元素的解析就完成了。