Mybatis在Mapper接口上使用了动态代理的一种非常规的用法。
假设有如下一个Mapper接口:
public interface UserMapper{
List<SysUser> selectAll();
}
这里使用Java动态代理方法创建一个代理类:
public MyMapperProxy<T> implements InvocationHandler{
private Class<T> mapperInterface;
private SqlSession sqlSession;
public MyMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
this.mapperInterface = mapperInterface;
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 针对不同的sql类型,需要调用sqlSession不同的方法
// 接口方法中的参数也有很多情况,这里只考虑没有参数的情况
List<T> list = sqlSession.selectList(mapperInterface.getCanonicalName() + "." + method.getName());
// 返回值也有很多情况,这里不做处理直接返回
return list;
}
}
测试代码如下:
// 获取SqlSession
SqlSession sqlSession = getSqlSession();
// 获取UserMapper接口
MyMapperProxy userMapperProxy = new MyMapperProxy<>(UserMapper.class, sqlSession);
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从入门到精通》刘增辉