Mybatis源码阅读系列(五)

12 篇文章 0 订阅
9 篇文章 0 订阅

今天我们来看下MybatisMapperProxy的实现,为什么它能只写一个接口和xml文件,就能自动把两者关联起来,你可能会很奇怪,java里面的接口是不能去实际执行的,它必须有具体的实现类,那这个类是怎么生成的呢?生成这个类之前会有什么操作?这就要说道java里面的一大利器-动态代理了。

Mybatis的动态代理

首先说一下什么是代理,在设计模式里面有一种代理模式,我们有一个目标对象,但是这个目标对象的实现不能满足我们的需求,我可以构造一个代理对象,代理对象里面会有目标对象的实现,我还可以在上面添加逻辑,最后实际调用的是代理对象的实现。整个过程调用方几乎无感知,就像用原对象一样在使用代理对象。

你可能听过静态代理,但是我们基本用的都是动态代理,因为静态代理需要你自己去写,侵入性太强,而动态代理是在运行时自动生成对象,就是这个自动省去了很多功夫。

动态代理作为一种字节码增强的技术,它的实现库有很多,jdk自带的动态代理(这个只作用于接口),还有cglib(作用于类),还有Bytebuddy等。我们常用的框架里面使用代理的地方很多,Mybatisspring的aop和fegin等,这些都是利用的代理,因为动态生成的代理对象可以让你的整个逻辑很简洁,整个过程对使用者完全透明,大大减少了开发者编码量。而真正的实现转移到了代理里面。

Configuration.getMapper时首先拿到addMapper时候放在mapper里面初始化(占位)用的mapperProxyFactory<T>,然后创建了一个MapperProxy<T>对象,这个代理对象里面会有一个Map<Method, MapperMethodInvoker> methodCache方法缓存,因为一个类会有多个方法,每个方法会对应一个实现,在实际调用的时候会拿到methodCache里面对应的MapperMethodInvoker调用,如果没有会初始化放到缓存里。

当我们调用代理方法时,会先判断是否是object的方法,如果是直接执行,这些方法和mapper映射无关。

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      }
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

否则会构建缓存的Invoker

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (!m.isDefault()) {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
        try {
          if (privateLookupInMethod == null) {
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          }
          return new DefaultMethodInvoker(getMethodHandleJava9(method));
        } 
      });
      、、、、、、、、、、、、、、、、、、、、省略  
    } 
  }
PlainMethodInvoker

我们先不看PlainMethodInvoker的调用,先看下面的,下面会有一个DefaultMethodInvoker,这个类是干嘛的呢?它是针对接口的默认方法实现做的适配,这里有jdk8和9的区别貌似。

上面实现会调用cachedInvoker方法构建一个PlainMethodInvoker对象。它是MapperMethodInvoker的一个实现。而MapperMethodInvoker是一个接口,实现如下,里面就一个方法:

  interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

可以看到PlainMethodInvokerDefaultMethodInvoker都是它的实现。这里主要是看PlainMethodInvoker,它里面有一个MapperMethod的变量:

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      this.mapperMethod = mapperMethod;
    }

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

最后实际在mapper里面调用具体方法的时候其实是执行的mapperMethod.execute(sqlSession, args);方法,这个方法就是根据xml或者注解里面得到的sql去具体执行并组装数据返回了。

MapperMethod

MapperMethod的构造函数主要有两块,一个是sqlCommand,对应xml或者注解的sql部分,一个是MethodSignature,对应接口的方法签名。

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

这里面的SqlCommandMethodSignature是很重要的两个类,这个我们后面具体介绍。

Mapper代理调用过程梳理

首先在初始化配置的时候会生成一个MapperProxyFactory(type)这样一个接口代理工厂放到MapperRegistryknownMappers里,这个MapperProxyFactory主要是占位用,为了生成真正的MapperProxy

MapperProxyXXXMapper接口的代理类,我们用sqlsession.getMapper(XXX.class);其实返回的是MapperProxy, 我们实际调用的是MapperProxy代理的方法,也就是MapperProxy.invoke方法,为了更好的调用方法,mybatis封装了一个MapperMethodInvoker,它会缓存在map里面,就是methodCache,如果下次重复getMapper,直接拿缓存的,不会再生成。而MapperProxyFactory里面的methodCache是和MapperProxy里面的methodCache是同一个引用,所以MapperProxyFactory里面的getMethodCache就是得到所有的方法缓存列表。

然后用XXXMapper具体调用crud方法时,经过一连串的代理调用,最后会执行MapperMethod.execute方法,这个execute方法就是连接mapper里面crud方法和xml里sql的纽带,最后执行操作。具体实现是sqlCommand会在构建的时候设置一个type,主要是根据xml里面的标签或者注解,然后分别进行crud操作。而select的实现会比增删改复杂得多。有兴趣的可以自己看一下。

然后这里面还有一个Flush的type,这个主要是对批处理执行使用的,这个以前没用到过,看了源码之后才了解是干嘛的,你可以直接在mapper接口里面写一个方法,上面写一个@Flush注解,后面调用这个方法,该事务里面未提交的操作都是进行提交。

总结

Mybatis的整个动态代理实现流程还是挺复杂的,里面为了不同情况各种封装嵌套,一开始看着可能会很懵,我一开始也是,直到我画出了整个流程图之后才整个清晰了起来,你可以按照我文章描述去debug具体的调用栈,然后画出一个调用链路图,这样能加深理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值