从Mybatis整合Spring说到Mapper接口的实现类

118 阅读2分钟

现在开发中,我们很少单独使用Mybatis,都是整合Spring框架一起使用,这便涉及到Mapper装载到Spring容器的问题。那首先Mapper是个接口,我们为什么不需要写实现类,Mapper又是如何找到实现类的呢?其次Mapper的实现类又是如何装载到Spring容器里的呢?

  1. 关于Mapper如何找到实现类,相信很多同学都知道是通过动态代理实现,下面我们具体看一下整个过程。 首先说一下Mybatis的整体工作流程,大致分为以下几步:
  • 构建SqlSessionFactory,现在我们使用springboot框架都是通过代码形式构建
  • 通过SqlSessionFactory来获取SqlSession
  • 通过SqlSession来获取Mapper
  • 执行Mapper具体的方法

重点就是通过SqlSession获取Mapper的过程,DefaultSqlSession中调用Configuration对象的getMapper方法

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

Configuration中调用MapperRegistry的getMapper方法

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

MapperRegistry中有个knownMappers变量,是个Map对象,key便是Mapper的class对象,value是MapperProxyFactory,在启动初始化阶段所有Mapper会被注册到MapperRegistry当中。MapperRegistry中getMapper的具体实现如下:

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

可以看到通过获取的MapperProxyFactory,最终生成MapperProxy代理对象

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    ···省略部分代码
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

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

执行MapperProxy的invoke方法,若是Mapper中Method非默认方法,最终会通过PlainMethodInvoker生成一个MapperMethod对象,最终执行mapperMethod的excute方法。

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return this.mapperMethod.execute(sqlSession, args);
}

MapperMethod的excute方法,会通过SqlSession去执行sql。

  1. 为什么在Spring的容器中可以注入Mybatis的Mapper接口的动态代理对象呢?

通过@Mapper或@MapperScan注解,或者使用MapperScannerConfigurer这个类,可以把每个接口对应的MapperFactoryBean注册到Spring容器中。如下代码:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  
  private Class<T> mapperInterface;
 
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
}

我们知道将FactoryBean注入到容器,当我们getBean的时候,其实最终是通过FactoryBean的getObject()方法来获取bean,可以看到MapperFactoryBean的getObject()方法实现就是通过SqlSession来获取Mapper接口的代理对象,这样就又回到了我们上面所说的怎样获取Mapper的实现类。