Mybatis 源码分析:获取 Mapper 接口对象

我们知道使用 mybatis 作为 ORM 框架时,想要使用面向接口的方式操作数据库,即使用 mapper 文件形式,那么就需要获取 Mapper 接口对象,从而才能对数据库进行操作。那么问题来了,在 java 中是不可能对 interface 进行 new 的,那么 mybatis 是怎么做到面向 Mapper 接口的呢?那就从源码的角度揭开这层其实没有想象那么高深的面纱!
首先来看看 SqlSession(默认实现 DefaultSqlSession) 类的 getMapper() 方法。

@Override
public <T> T getMapper(Class<T> type) {
	return configuration.<T>getMapper(type, this);
}

非常简单,它把获取 mapper 接口的对象逻辑委托给了 Configuration 类的 getMapper() 方法。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

这看起来似乎有那么点意思了,它从 mapperRegistry 中去获取 mapper 接口对象,并且还把 SqlSession 传递过去。看过我之前的文章应该清楚 mapperRegistry 是什么东西,从字面意思来看,它就是 Mapper 注册中心,它的定义是:

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

那么接下来应该是到了揭开面纱的门口了,我们看看 MapperRegistry 类的 getMapper() 方法又干了啥呢?

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

可以看到,首先会从 knownMappers 获取 Mapper 接口对应的 MapperProxyFactory 对象,那 knownMappers 又是什么呢,在之前的文章有说到,它就是在构建 SqlSessionFactory 对象时,对 xml 配置文件解析后把所有的 mapper 接口都会放入在 knownMappers 这个 HashMap 中去,key 为 mapper 接口的全限定名,value 为 MapperProxyFactory 对象。knownMappers 字段的定义为:

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

然后,会使用 MapperProxyFactory 类的 newInstance() 方法,此时就是揭开面纱见证奇迹的时刻到了,看看这个方法到底干了啥

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

这里首先生成了一个 MapperProxy 对象,那这个对象又是啥呢,字面意思看起来是 Mapper 的代理对象,这个稍等说。方法之后又掉用了一个重载的 newInstance() 方法:

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

千呼万唤始出来,终于见到了最核心的一行代码,它使用了 jdk 自带的动态代理机制对 Mapper 接口生成了一个代理对象,在这里可以看到 MapperProxy 是作为 Proxy.newProxyInstance() 方法的第三个参数传递进去的,所以可以想象到 MapperProxy 肯定实现了 jdk 的 InvocationHandler 接口。

public class MapperProxy<T> implements InvocationHandler, Serializable {
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  	// ...
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

现在,我们就知道了获取 Mapper 接口对象的整个流程,至于之后对其进行方法调用的过程,且看下回分解~~~
最后,老规矩附上整个过程的时序图:
Mybatis 源码分析:获取 Mapper 接口对象