MyBatis源码的学习(2)---学习MyBatis中JAVA8新特性的使用

本文探讨了MyBatis 3.5.4-SNAPSHOT版本中如何利用Java8的新特性,特别是动态代理、默认方法、函数式编程和缓存技术。在获取Mapper对象时,MyBatis使用MapperRegistry的getMapper方法,通过MapperProxyFactory创建MapperProxy代理类。MapperProxy的invoke方法是执行SQL并转换结果的关键,其中运用了缓存技术提高效率。此外,文章还介绍了默认方法的概念,并以Function接口为例展示了如何使用computeIfAbsent方法。最后,通过比较Java8前后的代码,强调了函数式编程的简洁性和可读性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MyBatis的版本是:  3.5.4-SNAPSHOT

断点看从sqlSession中获取mapper对象。

我们知道,MyBatis使用的Java动态代理为接口(UserMapper)生成代理类。

简单说下:我们加载MaBatis的xml配置文件和Mapper文件,会生成一个 configuration 对象。这里对象里面有一个 

MapperRegistry类型对象(这个对象就是存储我们的Mapper.xml中的nameSpace对应的接口所对用的MapperProxyFactory对象。比如我这个:UserMapper 接口。)

这个MapperRegistry类的 getMapper 方法:第一个参数就是我们要被代理的接口(比如我的UserMapper 接口) ,第二个参数是sqlSession。逻辑很简单,先从Map对象 knownMappers 中获取到该接口UserMapper 对应的MapperProxyFactory类型的对象mapperProxyFactory。

然后mapperProxyFactory对象是什么呢?

查看源码:MapperProxyFactory类是我们的MapperProxy类的工厂类,再查看MapperProxy的源码。

public class MapperProxy<T> implements InvocationHandler, Serializable
看到没,实现了InvocationHandler接口。学过java动态代理的都知道,实现这个接口的类,需要重写invoke方法,而这个invoke方法就是对应的代理类到时候的执行逻辑。继续看我们之前的从MapperProxyFactory工厂里获取对象mapperProxy的方法:先我们初始化一个实现了InvocationHandler接口的类  MapperProxy,然后使用JAVA动态代理为我们的接口UserMapper生成代理类
protected T newInstance(MapperProxy<T> mapperProxy) {
    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);
  }
重点是:我们执行 
userMapper.selectUser("10");

这一句时,根据动态代理我们知道,最终会执行的就是MapperProxy类里面的的invoke方法:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(proxy, method, args).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
 第一个if条件对应的就是,代理类从Object继承的那些方法执行的时候,直接调用。 比如:
userMapper.toString();

重点就是这个方法的else中的逻辑,这个逻辑就是在做执行sql,然后经结果转换为java类型对象。

MapperMethodInvoker接口有俩个实现类,查看代码逻辑:
private MapperMethodInvoker cachedInvoker(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {//Function接口中的default方法时,执行这个if,比如 compose方法
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }
这里面用的是java8的新特性:default方法,map的  computeIfAbsent方法,Function函数式接口。我们第一次调用
userMapper.selectUser("10");

这一行代码时,这时候在methodCache中,是没有对应的实现了 MapperMethodInvoker接口的类的对象的。这里就是运用了缓存的技术:第一次调用时,我们先创建实现MapperMethodInvoker接口的类的对象,然后放到map中,下次执行这个方法时,直接从map中获取,不需要再次创建对象了。比如:同一个sqlsession

如果是第一次运行时,我们的缓存methodCache
还没有这个方法method对象的调用者MapperMethodInvoker,
同时这个method对象是一个非default的方法(1.8开始,接口中允许定义default方法
,比如:在我们的UserMapper接口中定义一个default方法,那么到这里执行逻辑直接走
我们的if(m.isDefault())分支中的代码),
那么,正常我们需要被代理的方法(比如:selectUser)
直接走else里的逻辑,
生成一个PlainMethodInvoker类的对象。

如果我们第N次(N>1)调用  selectUser 方法时,
我们直接从缓存methodCache中取到
这个方法method对象的调用者MapperMethodInvoker。

另外说一下,我这个MyBatis源码版本是比较新的,它里面用到
好多java1.8的新特性。

1.什么是默认方法?

 默认方法:在接口类型中,声明的具有主体的非静态方法(有具体实现的)。

举例:下面是我们的Function接口中的默认方法

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
}

2.map的 computeIfAbsent 方法

  default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
    }
翻译一下,就是 如果不存在则执行

也就是说类似我们先从map中get(k)操作,取出来的值如果是null,然后执行创建一个value,同时这个value不为null,然后再put进去map中。如果get出来的value有值,直接返回value。

if ((v = get(key)) == null)    <------取出来值为null
mappingFunction.apply(key)     <------执行生成一个value值的逻辑

接下来就是这个Function接口的apply方法

3.函数式接口Function的使用,其实我们经常用的另一个函数式接口Runnale接口。先看下面的俩行代码:

    map.computeIfAbsent("key2",m->{return m ;});
    map.computeIfAbsent("key3",(m)->{return m;} );

效果是一样的,只有一个入参时,()是可以省去的

再看以前的版本要实现相同逻辑

在1.8之前,如果要实现相同的逻辑,需要用匿名内部类如下:

package com.br.itwzhangzx02.learn;

import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

public class MapTest {

  @Test
  public void test01()  {
    Map map = new HashMap<String,String>();

    map.computeIfAbsent("key1",  new Function<String,String>() {
       @Override
      public String  apply(String t) {
        return  t ;
      }
    } );

    map.computeIfAbsent("key2",m->{return m ;});
    map.computeIfAbsent("key3",(m)->{return m;} );

    System.out.println(map.get("key1"));
    System.out.println(map.get("key2"));
    System.out.println(map.get("key3"));
  }
}

对比发现,函数式编程代码更加简洁,同时简单易读。

输入:->前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。
函数体:->后面的部分,即被{}包围的部分;可以是一段代码。
输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。
其实,mybatis源码中好多函数式编程,就比如在一些test类中:

继续回到我们之前的代码执行处:

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {//重点是这个方法:执行的时候在cachedInvoker中会执行我们的return new 
              //PlainMethodInvoker(new 
              //MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        return cachedInvoker(proxy, method, args).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

/*初始化 MapperMethod对象的持有者或调用者PlainMethodInvoker,其实这里叫适配器更合适点,这就是一个典型的对象型的适配器模式*/
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));


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

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

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

对象型适配器的类图如下:

经过适配后,我们的invoke逻辑会执行: 

mapperMethod.execute(sqlSession, args);简单理解:mapperMethod类就是管理我们的sql语句的抽象和java类中的方法签名,这样就将我们xml中的sql语句和我们Mapper接口中定义的方法method关联映射起来。
/*这是xml配置*/
<mapper namespace="learn.UserMapper">
  <select id="selectUser"  parameterType="String" resultType="learn.User">
    select * from u_user where usercode = #{id}
  </select>
</mapper>

/*sqlID:learn.UserMapper.selectUser  然后和我们的UserMapper接口中的方法selectUser关联映射起来*/


package learn;

public interface UserMapper {

  public User selectUser(String id);

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值