Mybatis(四) - Mybatis是如何对Mapper接口进行代理的

Mybatis是如何对Mapper接口进行代理的

本篇文章是通过看视频学习总结的内容, 如有错误的地方请谅解,并联系博主及时修改,谢谢您的阅读.

注意: 本篇博客接着前三篇博客,主要是从第一篇博客的测试例子中开始延申,直到到源码的分析。

源码地址: mybatis 中文注释版

前三篇博客地址:

Mybatis(三) - Mybatis是如何通过SqlSessionFactory得到SqlSession的
Mybatis(二) - Mybatis是如何创建出SqlSessionFactory的
Mybatis(一) - Mybatis 最原始是使用方式

前言:在前三篇博客中论述了如何通过配置文件得到SqlSessionFactory 的, 如果通过SqlSessionFactory 得到 SqlSession,在本文中主要讲解 Mybatis 通过Session 如何得到 Mapper 接口的实例方法,如果对代码存在疑问,请查看 《Mybatis(一) - Mybatis 最原始是使用方式》的 Junit 测试代码, 也可以直接阅读一下代码:

@Test
    public void customPagePlugin() {
        SqlSession session = this.sqlSessionFactory.openSession();
        // 本文要分析的是这句代码
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> page = mapper.getUserPage("10086");
        page.stream().forEach(System.out::println);
    }
一、源码跟进
  • 1.1 进入 session.getMapper(UserMapper.class) 方法,在第二篇文章中分析到了 SqlSessionFactory 得到的 SqlSession 对象其实就是 DefaultSqlSession 对象,那么在这里也只是进入到 org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper() 即可。这里可以看到,在 DefaultSqlSession 对象中,存在了 Configuration 对象,得到Mapper 实例却调用了它里面的方法,然后继续跟进到 Configuration.getMapper()
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}
  • 1.2 进入到 org.apache.ibatis.session.Configuration#getMapper() 方法中,又调用了 org.apache.ibatis.binding.MapperRegistry#getMapper() 方法,于是继续跟进:
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  • 1.3 进入到 org.apache.ibatis.binding.MapperRegistry#getMapper() 中,很明显第一句话就是通过 Mapper(接口)Class 对象,在Map 中获取到了一个 MapperProxyFactory 类型,这里先跳过 MapperProxyFactory 的研究,接下来就是通过的到的 MapperProxyFactory 对象,去创建 mapper(接口) 的代理对象,那么继续跟进到 org.apache.ibatis.binding.MapperProxyFactory#newInstance()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从初始化阶段完成的mapper接口注册中获取到当前接口对应的代理类
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      T t = mapperProxyFactory.newInstance(sqlSession);
      return t;
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  • 1.4 进入 org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession) 方法中,这里就特别明显了,直接 new 出一个 MapperProxy 对象,传入了 sqlSession、 mapperInterface、methodCache 三个参数作为构造器参数。然后调用重载的 newInstance 方法,传入MapperProxy ,最终使用 JDK 动态代理完成了 Mapper(接口) 的代理
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  // 这个方法是我们关注的
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
二、步骤分析
  • 2.1 为什么 DefaultSqlSession 中会保存一份 Configuration 对象,在调用getMapper() 的时候,为什么又会到 Configuration 中调用 MapperRegistry.getMapper()

在最开始的加载配置文件的时候,说到这么 Configuration 对象是全局唯一的配置类,是一个单例对象,那么既然是全局的单例配置对象,肯定里面包含了完整的执行一个sql 的所有需要的内容,在文章《Mybatis(三) - Mybatis是如何通过SqlSessionFactory得到SqlSession的》阐述了执行一条 sql 所需要的所有内容。初始化阶段创建出 SqlSessionFactory 对象时,就已经完成了配置文件的解析,将所有的 mapper(接口) 全部初始化到 Configuration.mapperRegistor 容器中保存,现在需要使用到 Mapper(接口) 代理,那么肯定是从容器中取出能够代理接口创建的工厂对象,拿到代理 mapper(接口) 的代理工厂对象,代理 mapper(接口)

  • 2.2 MapperProxyFactory 是如何创建的?

这个问题需要追溯到初始化阶段,找到 org.apache.ibatis.binding.MapperRegistry#addMapper() 方法,这个方法是在初始化节点,解析 mybatis-config.xml 文件中 <Mappers> 标签的时候调用的,代码如下:

// 解析 mybatis-config.xml 文件中 <Mappers> 标签,加入到 Configuration.MapperRegistry容器中
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

这里 knownMappers.put(type, new MapperProxyFactory<>(type)) 中,就可以看到 MapperProxyFactory 是通过new 关键字传入 Mapper(接口) 作为构造器参数进行创建的,然后给成员变量private final Class<T> mapperInterface; 赋值。所以在最终调用的时候,我只需要通过 mapper.class 集合获取到指定对象的 代理工厂。

  • 2.3 为什么使用 JDK 动态代理,而不是 Cglib 动态代理?

Mybatis 源码中大量使用了 JDK 的动态代理,并没有使用 CglibJDK 动态代理最终原理是通过创建一个类,实现接口中所有的方法,从而实现代理。而 Cglib 则是通过类来集成原有的类,实现的动态代理,结合两个动态代理到 mybatis 环境中,mybatis 设计是通过 mapper 接口加上方法名称来锁定一个唯一的 sql 语句,那么问题就来了,如果使用类继承(继承谁?),没法继承啊,对吧。

总结:
  • mybatis 创建接口的代理对象是通过 JDK 动态代理实现的,而不是 Cglib
  • mybatis 是通过全局唯一的接口名称加上方法名称锁定一个 sql 语句
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值