objectFactory
导学
本节学习目标:
- 理解
objectFactory在 MyBatis 结果映射中的定位:负责创建结果对象实例 - 掌握默认
DefaultObjectFactory的行为与能力边界 - 了解自定义对象工厂的适用场景与实现方式
- 通过源码流程图深入理解 MyBatis 对象实例化的完整链路
定义
当 MyBatis 执行查询并将结果集映射为 Java 对象时,面临一个基础问题:如何创建目标类的实例?
- 对于无参构造方法的普通类,
newInstance()即可 - 但对于某些特殊场景,可能需要:
- 从对象池中获取实例而非直接
new - 通过动态代理创建实例以实现 AOP
- 对实例进行创建后的统一初始化(如注入依赖、设置默认值)
- 从对象池中获取实例而非直接
objectFactory 元素的作用就是指定创建结果对象实例的工厂类。MyBatis 默认使用 DefaultObjectFactory,它通过反射调用无参或有参构造方法创建对象。在绝大多数项目中,无需修改此配置;但理解其原理,是深入掌握 MyBatis 结果映射机制的关键一环。
适用位置与核心属性
objectFactory 位于 mybatis-config.xml 中 typeHandlers 之后、plugins 之前。
<objectFactory type="com.feixiang.factory.EmployeeObjectFactory">
<property name="defaultDepartment" value="DEV001"/>
</objectFactory>
| 属性 | 是否必填 | 说明 |
|---|---|---|
type | 必填 | 对象工厂类的全限定名,必须继承 DefaultObjectFactory 或实现 ObjectFactory 接口 |
子标签 property 用于向对象工厂实例注入属性值,MyBatis 会通过 setter 方法或构造方法传入。
核心原理
对象创建流程图
源码层面:DefaultResultSetHandler 在创建结果对象时,会调用 ObjectFactory.create(Class<T> type) 或 create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)。默认实现使用 Class.newInstance() 或构造方法反射创建实例。
ObjectFactory 接口定义:
public interface ObjectFactory {
// 设置配置属性
void setProperties(Properties properties);
// 无参创建
<T> T create(Class<T> type);
// 带参创建(用于有参构造方法的结果映射)
<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
// 判断是否为集合类型
<T> boolean isCollection(Class<T> type);
}
完整示例
场景说明
乐途公司员工管理系统中,Employee 实体有一个业务规则:所有新创建的员工对象,默认所属部门应为 "DEV001"(研发部),除非数据库查询结果明确指定了其他部门。通过自定义 ObjectFactory,可以在对象创建阶段统一注入默认值,避免在每个 Mapper 或 Service 中重复编写初始化逻辑。
操作前的状态
Employee 实体类:
public class Employee {
private Integer employeeId;
private String employeeName;
private String departmentCode; // 期望默认值 DEV001
private Date createTime;
// getter / setter 省略
}
未配置自定义工厂时,从数据库查询 department_code 为 NULL 的记录,departmentCode 属性也为 null。
完整配置代码
步骤一:实现自定义 ObjectFactory
package com.feixiang.factory;
import com.feixiang.entity.Employee;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import java.util.List;
import java.util.Properties;
public class EmployeeObjectFactory extends DefaultObjectFactory {
private String defaultDepartment;
@Override
public void setProperties(Properties properties) {
// 读取 XML 中配置的 property
this.defaultDepartment = properties.getProperty("defaultDepartment", "DEV001");
System.out.println("【ObjectFactory】初始化,默认部门设置为: " + defaultDepartment);
}
@Override
public <T> T create(Class<T> type) {
T instance = super.create(type);
return applyDefaults(type, instance);
}
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
T instance = super.create(type, constructorArgTypes, constructorArgs);
return applyDefaults(type, instance);
}
@SuppressWarnings("unchecked")
private <T> T applyDefaults(Class<T> type, T instance) {
// 仅对 Employee 类型注入默认值
if (Employee.class.isAssignableFrom(type)) {
Employee emp = (Employee) instance;
if (emp.getDepartmentCode() == null) {
emp.setDepartmentCode(defaultDepartment);
System.out.println("【ObjectFactory】为 Employee 注入默认部门: " + defaultDepartment);
}
}
return instance;
}
}
步骤二:在 mybatis-config.xml 中配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.feixiang.entity"/>
</typeAliases>
<!-- 对象工厂配置 -->
<objectFactory type="com.feixiang.factory.EmployeeObjectFactory">
<property name="defaultDepartment" value="DEV001"/>
</objectFactory>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
</mappers>
</configuration>
步骤三:准备测试数据
-- 插入一条 department_code 为 NULL 的记录
INSERT INTO employee (employee_name, department_code, create_time)
VALUES ('王五', NULL, NOW());
步骤四:Java 测试代码
public class ObjectFactoryDemo {
public static void main(String[] args) throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
try (SqlSession session = factory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
// 查询 department_code 为 NULL 的员工
Employee emp = mapper.selectById(1);
System.out.println("员工姓名: " + emp.getEmployeeName());
System.out.println("所属部门: " + emp.getDepartmentCode());
}
}
}
实际效果/结果
控制台输出:
【ObjectFactory】初始化,默认部门设置为: DEV001
【ObjectFactory】为 Employee 注入默认部门: DEV001
员工姓名: 王五
所属部门: DEV001
效果验证:数据库中 department_code 为 NULL,但查询结果中 departmentCode 被自动填充为 DEV001。
分析
- 自定义
ObjectFactory的切入点在"对象创建后、属性填充前",因此可以通过super.create()先获得实例,再进行自定义初始化 setProperties()方法在ObjectFactory实例化后立即调用,适合读取 XML 中配置的参数- 由于
ObjectFactory全局唯一,所有结果对象的创建都会经过它,因此判断类型(Employee.class.isAssignableFrom(type))是必要的,避免对其他类型产生副作用
易错场景/常见误区
| 误区 | 错误表现 | 正解 |
|---|---|---|
在 create() 中直接 new 对象而不调用 super.create() | 丢失了 MyBatis 对构造方法参数的解析能力,有参构造映射失效 | 始终先调用 super.create(...) 获得基础实例,再在此基础上扩展 |
| 自定义工厂中未判断类型,对所有类统一处理 | 非 Employee 类型的查询结果也被注入 departmentCode,导致 ClassCastException | 使用 type.isAssignableFrom(Employee.class) 或 Employee.class.isAssignableFrom(type) 进行类型过滤 |
试图用 ObjectFactory 替代 Spring 的依赖注入 | ObjectFactory 创建的对象不会被 Spring 管理,后续无法使用 @Autowired | ObjectFactory 仅用于结果对象创建时的轻量级初始化,复杂依赖注入应集成 Spring |
认为 ObjectFactory 影响参数对象(如插入时的实体) | 发现插入时对象的默认值未被注入 | ObjectFactory 只负责结果对象的创建,不影响用户传入的参数对象 |
配置 property 但 setProperties 未被调用 | 属性值为 null | 确保自定义工厂类确实重写了 setProperties 方法,且方法参数为 Properties |
面试考点
Q1:MyBatis 默认使用什么对象工厂?什么情况下需要自定义?
A:默认使用
DefaultObjectFactory,它通过反射调用无参或有参构造方法创建实例。需要自定义的场景极少,通常包括:① 需要从对象池获取实例而非直接new;② 创建后需要统一初始化(如设置默认值、注入轻量级依赖);③ 需要通过动态代理创建实例以实现某些横切逻辑。
Q2:ObjectFactory 和 ObjectWrapperFactory 有什么区别?
A:
ObjectFactory负责创建对象实例(new的阶段);ObjectWrapperFactory负责包装已创建的对象实例,提供属性读写的统一接口(MetaObject的底层)。两者在结果映射链路中先后执行:ObjectFactory创建实例 →ObjectWrapperFactory包装实例 → 通过MetaObject设置属性值。
Q3:自定义 ObjectFactory 时,create(Class<T> type) 和 create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 分别在什么场景被调用?
A:无参
create(Class<T> type)是最常用的场景,适用于大多数无参构造方法的实体类。带参版本在有参构造方法的结果映射时被调用,例如<constructor>标签中指定了构造参数,MyBatis 会通过带参create传入解析后的参数类型和值。
小结
objectFactory 是 MyBatis 结果映射链路的"起点",它决定了查询结果将以何种方式实例化。虽然 99% 的项目无需离开默认的 DefaultObjectFactory,但理解其接口定义、调用时机和扩展方式,有助于在需要时(对象池、统一初始化、代理创建)精准切入。切记:ObjectFactory 只负责创建,不负责依赖管理,不要试图用它替代 Spring IoC。
下一章引子
对象创建之后,MyBatis 还提供了在 SQL 执行全链路中"横切干预"的能力——这就是 plugins(插件)机制。通过拦截 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四大核心对象,插件可以实现 SQL 打印、性能监控、分页逻辑、数据权限等强大功能。接下来,我们将学习如何编写和注册一个 MyBatis 插件。