Mybatis源码学习(六):Mapper接口“实例化”

前文回顾

在上一篇文章中我们对Mybatis的流程进行了一次Debug,对于整体流程有了更清晰的认识。在我们Debug的过程中也提出了一个问题,**我们使用的Mapper接口并没有写接口的实现,那么这个接口是如何执行业务逻辑的呢?**今天我们将带着这个问题进行进一步学习。

一、Mapper回顾

在解答今天的问题之前,我们写回顾一下Mapper接口,这里以一个简单的查询为例。首先我们会定义一个Mapper接口如下

1、定义Mapper接口

public interface PersonMapper {
    List<Person> selectAll();

    Person selectById(@Param("id")Long id);
}

2、编写Mapper.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmxy.mapper.PersonMapper">
  
  <resultMap id="BaseResultMap" type="com.cmxy.entity.Person">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
  resultMap>
  
  
  <select id="selectAll" resultMap="BaseResultMap" >
    select *  from t_person
  select>
  
  <select id="selectById" resultMap="BaseResultMap">
    select * from t_person where id = #{id}
  select>
mapper>

1、namespace(命名空间)指向了PersonMapper
2、resultMap:定义了结果集映射

3、调用对应方法

   public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        //读取mybatis配置文件并获取输入流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //构建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            PersonMapper mapper = session.getMapper(PersonMapper.class);
            Person person = mapper.selectById(1L);
            System.out.println(person);
        }
    }

二、Mapper接口“实例化”

我们知道在Java中,接口是不能被实例化的。我们自己又没有实现Mapper接口所提供的方法那么Myabtis是如何通过Mapper接口来执行我们所编写的SQL呢?带着这个问题我们进行Debug。

问题1:接口是如何被“实例化”的

Mybatis源码学习(六):Mapper接口“实例化”_第1张图片
这里我们可以看到我们写的mapper并不是一个接口,而是一个名为org.apache.ibatis.binding.MapperProxy@78691363的对象,从该类的命名我们可以看出这个是一个代理类。所以这里我们得出第一个论,在Mybatis中执行接口中方法的并不是接口本身,而是接口的实现类。那么问题来了

问题2:该实现类是谁、什么时候、怎么创建的

在我们的编程中并没有显式的编写Mapper接口的实现类,那么该实现类是如何出现的呢。结合该类的名字MapperProxy,我们可以大概猜测这是一个代理类。代理又分为静态代理和动态代理,很明显这里是动态代理(如果是静态代理则需要自己编码),说到动态代理我们有能想到的是JDK动态代理,那是否真的是这样呢。我们可以Debug获取Mapper的代码:PersonMapper mapper = session.getMapper(PersonMapper.class);

Mybatis源码学习(六):Mapper接口“实例化”_第2张图片
一路Debug我们来到了MapperRegistry这个类的个getMapper方法,其中核心代码为:
return mapperProxyFactory.newInstance(sqlSession); 通过Mapper代理工厂动态的创建Mapper的实现类。继续Debug
Mybatis源码学习(六):Mapper接口“实例化”_第3张图片
很明显这里是就是通过JDK动态代理来创建了接口的实现类。
小结:回答前面的问题
(1)Mapper接口的实现类是Mybatis创建的
(2)在调用getMapper方法时创建
(3)通过JDK动态代理来创建

三、Mapper实现类创建源码

前面我们大致了解Mybatis中是如何为我们所编写的Mapper接口生成实现类的,现在我们跟着源码具体探究一下生成源码的过程。继续刚才的Debug

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

在这个方法中,核心是如何创建MapperProxy这个对象,我们进入其构造函数来探究。进入MapperProxy类,我们看到了invoke方法(代理类的方法)

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //判断是否为Object类的方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

源码分析:首先Mybatis会判断当前类的方法是否属于Object,例如equals、hashcode等方法直接用被代理类执行即可,然后继续判断是不是default方法(JDK8之后接口可以定义Default方法)如果是Default方法也直接使用被代理类来这执行。最后从缓存里取被代理的方法,也就是被例子中的
Person selectById(@Param(“id”)Long id);和List selectAll();
Mybatis源码学习(六):Mapper接口“实例化”_第4张图片

然后调用的mapperMehtod.execute方法,这部分代码在前文中已经讲述过,这里不再赘述。
Mybatis源码学习(六):Mapper接口“实例化”_第5张图片

四、总结

至此我们已经知道了Mybatis是如何帮我创建接口的实现类,以及如何调用对应方法来执行SQL的,这里以一个图来简单梳理一下流程。
Mybatis源码学习(六):Mapper接口“实例化”_第6张图片
对于Mybatis的执行流程我们已经相对熟悉了,之后的文章会分析一些Mybatis中其他的知识,例如缓存、拦截器等等,希望对你有所帮助。

未完待续

你可能感兴趣的:(Mybatis源码学习,mybatis,学习,java)