Mybatis Mapper动态代理的实现
一、Mapper创建示例
大家通常在Spring的环境下使用Mybatis,可能已经忘记了原生的Mybatis是如何使用了,这里举个简单的例子,帮助大家回忆一下:
java
// 1. 加载MyBatis核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 构建SqlSessionFactory(会话工厂)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 开启数据库会话(try-with-resources自动关闭资源)
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 4. 获取Mapper接口的代理实例
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 5. 执行数据库操作
User user = userMapper.selectById(1L);
System.out.println("查询结果:" + user);
}
二、Mapper创建过程解析
从例子中可以看到,原生的Mybatis是通过SQLSession的getMapper方法创建实例的,接下来我们通过时序图来解析下这个Mapper是如何创建的。
2.1 Mapper创建时序图
2.2 创建过程解析
-
用户调用
SqlSession.getMapper()
用户通过已创建的
SqlSession
对象,传入目标 Mapper 接口(如UserMapper.class
),请求获取接口实例。 -
SqlSession 委托给 MapperRegistry
SqlSession
内部并不直接创建 Mapper,而是将请求转发给Configuration
中的MapperRegistry
(Mapper注册中心),同时传递自身实例作为参数。 -
MapperRegistry 查找代理工厂
MapperRegistry
从内部缓存knownMappers
中,根据接口类型查找对应的MapperProxyFactory
(每个Mapper接口对应一个专属工厂)。这个映射关系,是在配置解析过程中,通过
MapperRegistry
的addMapper
方法注册的:
java
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 只处理接口
if (!hasMapper(type)) {
// 创建该接口对应的MapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<>(type));
// 解析接口中的注解或XML映射(如方法上的@Select等)
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
}
}
}
- 代理工厂创建代理实例
MapperProxyFactory
接收SqlSession
参数,创建MapperProxy
(实现InvocationHandler
的代理处理器),并通过JDK动态代理生成UserMapper
接口的代理对象。
java
// Mapper中每个方法代理实现的缓存
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public T newInstance(SqlSession sqlSession) {
// 创建MapperProxy
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 通过JDK的动态代理创建Mapper的代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
2.3 类之间关系
三、动态代理的实现:MapperProxy
MapperProxy实现了InvocationHandler接口,通过JDK实现了对Mapper的动态代理,下面我们解析下MapperProxy。
3.1 核心属性
java
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 数据库会话,用于执行SQL(如selectOne、insert等)
private final SqlSession sqlSession;
// 被代理的Mapper接口类型(如UserMapper.class)
private final Class<T> mapperInterface;
// 缓存Mapper接口方法与MapperMethod的映射,避免重复解析
private final Map<Method, MapperMethod> methodCache;
// ... 构造方法等
}
3.2 核心方法invoke
MapperProxy
实现了InvocationHandler
的invoke
方法,这是动态代理的核心拦截方法。当调用 Mapper 接口的任何方法时,都会被该方法拦截并处理:
kotlin
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 处理Object类的方法(如toString、hashCode等),直接调用原生实现
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 2. 处理接口默认方法(Java 8+)
else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
// 3. 处理Mapper接口的业务方法(核心逻辑)
else {
// 从缓存获取或创建MapperMethod,执行SQL操作
return cachedMapperMethod(method).execute(sqlSession, args);
}
}
逻辑解析
- 对于
Object
类的方法(如toString()
、hashCode()
),直接通过反射调用原生实现,不涉及数据库操作。 - 对于 Java8+ 的接口默认方法(带
default
关键字),通过invokeDefaultMethod()
处理,也就是直接调Mapper中的default方法。 cachedMapperMethod(method)
:从缓存中获取当前方法对应的MapperMethod
,缓存中没有则创建并缓存。
java
private MapperMethod cachedMapperMethod(Method method) {
// 从缓存获取,若不存在则创建并缓存
return methodCache.computeIfAbsent(method, k ->
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
);
}
Q:还记得methodCache
是从哪里来的么?
A: MapperProxyFactory
在创建的时候提供的,methodCache
是MapperProxyFactory
的一个属性,因此一个MapperProxyFactory
无论创建多少Mapper,methodCache
都是共享的。
- 通过
MapperMethod.execute(sqlSession, args);
执行SQL
3.3 MapperMethod
核心属性 MapperMethod有两个核心属性,在构造是初始化:
SqlCommand
:封装 SQL 命令的元信息(如SQL类型、对应的MappedStatement
ID)。MethodSignature
:封装方法的签名信息(如参数解析、返回值类型、是否返回集合等)。
java
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
// 构造函数:初始化SqlCommand和MethodSignature
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
// ...
}
MapperMethod通过方法类型(增删改查)以及返回参数,执行不同的分支:
java
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据SQL命令类型(如SELECT/INSERT/UPDATE/DELETE)执行对应操作
switch (command.getType()) {
case INSERT:
Object param = methodSignature.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
case UPDATE:
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
case DELETE:
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
case SELECT:
if (methodSignature.returnsVoid() && methodSignature.hasResultHandler()) {
// 处理带ResultHandler的查询
executeWithResultHandler(sqlSession, args);
result = null;
} else if (methodSignature.returnsMany()) {
// 处理返回集合的查询(selectList)
result = executeForMany(sqlSession, args);
} else if (methodSignature.returnsMap()) {
// 处理返回Map的查询
result = executeForMap(sqlSession, args);
} else {
// 处理返回单个对象的查询(selectOne)
Object param = methodSignature.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
3.4 关系图
小结
MapperProxy
作为 MyBatis 动态代理的核心处理器,通过JDK动态代理,拦截Mapper接口方法调用,把每个方法的执行,委托给对应MapperMethod
。而MapperMethod
通过SqlSession
,执行真正的SQL。为了避免方法的重复解析,MapperProxy
为每个MapperMethod
做了缓存,以避免重复的解析。