天天看點

Mybatis源碼解析(二)擷取Mapper(getMapper)

上期我們說了Mybatis是怎麼建立會話的Mybatis源碼解析(一)建立會話SqlSession,在建立會話的過程中,會讀取

mybatis

的配置檔案和

mapper.xml

檔案來封裝一些内容,主要有相關的配置資訊,最重要的還是建構了一個map來存儲

MappedStatement

資訊,我們會在這篇文章詳細講解

我們同樣看下面的測試方法:

@Test
    public void testFindAll() throws IOException {
        //加載主配置檔案,目的是為了建構SqlSessionFactory對象
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

        //建立SqlSessionFactory對象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

        //通過SqlSessionFactory工廠對象建立SqlSesssion對象
        SqlSession sqlSession = factory.openSession();

        //通過Session建立UserDao接口代理對象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        List<User> userList = mapper.findAll();
        for (User user : userList) {
            System.out.println(user);
        }

    }
           

上一篇我們講解了建立

SqlSession

,也就是下面三句代碼

//加載主配置檔案,目的是為了建構SqlSessionFactory對象
 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

 //建立SqlSessionFactory對象
 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

 //通過SqlSessionFactory工廠對象建立SqlSesssion對象
 SqlSession sqlSession = factory.openSession();
           

這一篇我們主要講解的是,mybatis是怎麼擷取Mapper的,擷取的Mapper對象又是什麼樣的?也就是下面的一句代碼

//通過Session建立UserDao接口代理對象
 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           

我們需要知道它的内部是怎麼實作的,那我們就繼續跟蹤源碼,一步一步走下去

DefaultSqlSession

類中

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
           

它調用了configuration裡面的

getMapper()

方法,繼續跟蹤。

Configuration

類中

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
           

我們可以看見一個屬性叫做

mapperRegistry

,這是個什麼東西呢?從它的英文意思我們可以知道他叫mapper注冊器,我們看看它到底是什麼,我們看看

MapperRegistry

Mybatis源碼解析(二)擷取Mapper(getMapper)

我們發現它其實就是維護了一個

HashMap

,這個map的key是你要建立的Mapper的class也就是例如

UserMapper.class

這樣的,value對應的是一個Mapper的代理工廠,工廠是什麼??工廠就是用來生産對象的啊,也就是說這個工廠是用來生産

Mapper

的代理對象的,我們回到我們的

Configuration

類裡面的

getMapper()

方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
           

我們再跟着進去,在

MapperRegistry

類中

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
           

首先我們看這句代碼

它是根據我們傳入的

UserMapper.class

作為key,從

knownMappers

擷取了一個

MapperProxyFactory

(Mapper代理工廠),為什麼這裡能擷取到?因為我們上一篇說了,它在建立

SqlSession

的時候就做了很多準備工作,在上一篇的時候我們一帶而過了其實我們現在可以看看

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
      	//以包的形式給出
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          //使用相對于類路徑的資源引用
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
            //使用完全限定資源定位符(URL)
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
			//使用映射器接口實作類的完全限定類名
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

           

這段代碼是我們上一篇中提到的,它其實是在解析mybatis配置檔案,我們看這行代碼

它其實就是把我們的

UserMapper.class

加入到了

knownMappers

裡面了,現在我們知道了可以擷取到

MapperProxyFactory

,接下來就要生成代理對象了,我們回到

getMapper()

方法

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
           

現在我們拿到工廠類了,我們就可以開始生産代理對象了也就是下面的這句代碼

我們進去

newInstance()

方法看看,在

MapperProxyFactory

類中

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

它調用了本類中的另一個

newInstance()

方法

@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
           

這個方法就再明顯不過了,就是一個動态代理,它的代理類是MapperProxy,我們再看看這個類

Mybatis源碼解析(二)擷取Mapper(getMapper)

它實作了

InvocationHandler

接口,當執行方法的時候,執行的是

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 if (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
           

擷取Mapper的流程就結束了,關于具體方法的執行我們下一篇再說