Mapper接口动态代理实现原理
要想理解本节内容,需要具备 JDK 动态代理基础。
通过上面的学习,大家可能会有一个疑问,为什么 Mapper 接口没有实现类却能被正常调用呢?
这是因为 MyBaits 在 Mapper 接口上使用了动态代理的一种非常规的用法,熟悉这种动态代理的用法不仅有利于理解 MyBatis 接口和 XML 的关系,还能开阔思路。接下来提取出这种动态代理的主要思路,用代码来为大家说明。
假设有一个如下的 Mapper 接口。
public interface UserMapper {
List<SysUser> selectAll();
}
这里使用 Java 动态代理方式创建一个代理类,代码如下。
public class MyMapperProxy<T> implements InvocationHandler {
private Class<T> mapperInterface;
private SqlSession sqlSession;
public MyMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
this.mapperInterface = mapperInterface;
this.sqlSession = sqlSession;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 针对不同的 sql 类型,需要调用 sqlSession 不同的方法
// 接口方法中的参数也有很多情况,这里只考虑没有参数的情况
List<T> list = sqlSession.selectList(mapperInterface.getCanonicalName() + "." + method.getName());
// 返回值也有很多情况,这里不做处理直接返回
return list;
}
}
测试代码如下。
import java.lang.reflect.Proxy;// 获取 sqlSession
SqlSession sqlSession = getSqlSession();
// 获取 UserMapper 接口
MyMapperProxy userMapperProxy = new MyMapperProxy(UserMapper.class, sqlSession);
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[] {UserMapper.class},
userMapperProxy
);
// 调用 selectAll 方法
List<SysUser> user = userMapper.selectAll();
从代理类中可以看到,当调用一个接口的方法时,会先通过接口的全限定名称和当前调用的方法名的组合得到一个方法 id,这个 id 的值就是映射 XML 中 namespace 和具体方法 id 的组合。所以可以在代理方法中使用 sqlSession 以命名空间的方式调用方法。通过这种方式可以将接口和 XML 文件中的方法关联起来。这种代理方式和常规代理的不同之处在于,这里没有对某个具体类进行代理,而是通过代理转化成了对其他代码的调用。
由于方法参数和返回值存在很多种情况,因此 MyBatis 的内部实现会比上面的逻辑复杂得多,正是因为 MyBatis 对接口动态代理的实现,我们在使用接口方式的时候才会如此容易。如果大家对 MyBatis 源码感兴趣,可以通过第 11 章的内容了解 MyBatis 的源码并深入学习。
通过本节这个简单的例子,我们可以了解 MyBatis 动态代理实现的方式,同时也学会一种编程思路:可以通过动态代理这个桥梁将对接口方法的调用转换为对其他方法的调用。