Mybatis的mapper只需要声明 Interface ,就能实现 CRUD 的操作。显然这个过程中,Mybatis对这个接口做了一些事情。在探究这个过程之前我们先回忆一下我们使用Mybatis的流程

Mybatis的使用流程

通常我们使用 Mybatis 的主要步骤是:

  • 构建 SqlSessionFactory ( 通过 xml 配置文件 , 或者直接编写Java代码)
  • 从 SqlSessionFactory 中获取 SqlSession
  • 从SqlSession 中获取 Mapper
  • 调用 Mapper 的方法 ,例如:blogMapper.selectBlog(int blogId)
  1. // 1.
  2. DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
  3. TransactionFactory transactionFactory = new JdbcTransactionFactory();
  4. Environment environment = new Environment("development", transactionFactory, dataSource);
  5. Configuration configuration = new Configuration(environment);
  6. configuration.addMapper(BlogMapper.class);// 添加Mapper接口
  7. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
  8. // 2.
  9. SqlSession session = sqlSessionFactory.openSession();
  10. try {
  11. // 3.
  12. BlogMapper mapper = session.getMapper(BlogMapper.class);
  13. // 4.
  14. Blog blog = mapper.selectBlog(1);
  15. } finally {
  16. session.close();
  17. }

不难看出,1和2主要是Mybatis初始化的过程,4已经是在调用具体的CRUD方法。mapper接口转化为类的过程就发生在第3步

  1. // 3.
  2. BlogMapper mapper = session.getMapper(BlogMapper.class);

经验丰富的老司机应该已经猜到这个过程是采用了动态代理的技术,下面我们看一下这个具体的过程

Mapper转换

1.在转换之前,首先得把Mapper存下来

  1. configuration.addMapper(BlogMapper.class);// 添加Mapper接口

2.addMapper 则是把Mapper存到了 mapperRegistry 里面

  1. public <T> void addMapper(Class<T> type) {
  2. mapperRegistry.addMapper(type);
  3. }

3.点击去看一下,mapperRegistry本质就是一个HashMap(knownMappers),值得注意的是,在存储的时候value值为 MapperProxyFactory 实例

  1. knownMappers.put(type, new MapperProxyFactory<>(type));

从MapperProxyFactory名字来看,不难看出这是一个创建MapperProxy的工厂

4.动态代理类生成

现在回到

  1. BlogMapper mapper = session.getMapper(BlogMapper.class);

追溯下去,其他这个方法调用的是 Configuration 的 getMapper,而它则是将 knownMappers里面的值取了出来,并 通过 newInstance 方法转换为 MapperProxy 实例。

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

而 newInstance 实质就是 Proxy.newProxyInstance 生成代理对象 MapperProxy

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

最后再贴一下 MapperProxy 方法调用的逻辑,核心方法就是 invoke 和 execute

  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. try {
  4. if (Object.class.equals(method.getDeclaringClass())) {
  5. return method.invoke(this, args); // 重点
  6. } else if (method.isDefault()) {
  7. if (privateLookupInMethod == null) {
  8. return invokeDefaultMethodJava8(proxy, method, args);
  9. } else {
  10. return invokeDefaultMethodJava9(proxy, method, args);
  11. }
  12. }
  13. } catch (Throwable t) {
  14. throw ExceptionUtil.unwrapThrowable(t);
  15. }
  16. final MapperMethod mapperMethod = cachedMapperMethod(method);
  17. return mapperMethod.execute(sqlSession, args); // 重点
  18. }

其实Java的框架源码写得都非常清晰,习惯阅读源码的可以直接看session下面的Configuration,非常简短且清晰。
image.png

5.时序图

image.png

参考文章

Mybatis Mapper接口是如何找到实现类的-源码分析https://www.cnblogs.com/demingblog/p/9544774.html

两张图彻底搞懂MyBatis的Mapper原理!https://segmentfault.com/a/1190000018440184