Mybatis Mapper接口是如何找到實現類的-源碼分析

KeyWords: Mybatis 原理,源碼,Mybatis Mapper 接口實現類,代理模式,動態代理,Java動態代理,Proxy.newProxyInstance,Mapper 映射,Mapper 實現程序員

MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。咱們在使用 Mybaits 進行 ,一般只須要定義幾個 Mapper 接口,而後在編寫一個 xml 文件,咱們在配置文件中寫好 sql , Mybatis 幫咱們完成 Mapper 接口道具體實現的調用。以及將結果映射到 model bean 中。spring

咱們在項目中所編寫的衆多的 Mapper 類只是一個接口(interface ),根據 Java 的多態性咱們知道,可使用接口接口做爲形參,進而在運行時肯定具體實現的對象是什麼。可是,對於 Mapper 接口,咱們並無編寫其實現類!Mybatis是如何找到其實現類,進而完成具體的 CRUD 方法調用的呢?原理何在?sql

Mapper 接口是怎麼找到實現類的

爲了弄清楚 Mapper 接口是如何找到實現類的,咱們先回憶一下 Mybatis 是怎麼使用的,根據實際的例子,進而一點點的去分析。這裏的使用指的是Mybatis 單獨使用,而不是整合 spring , 由於整合 spring 的話,還須要涉及 Mapper dao 裝載到 spring 容器的問題,spring 幫忙建立數據源配置等問題。數據庫

一般咱們使用 Mybatis 的主要步驟是:緩存

  • 構建 SqlSessionFactory ( 經過 xml 配置文件 , 或者直接編寫Java代碼)
  • 從 SqlSessionFactory 中獲取 SqlSession
  • 從SqlSession 中獲取 Mapper
  • 調用 Mapper 的方法 ,例如:blogMapper.selectBlog(int blogId)

從一段代碼看起

上面咱們歸納了使用 Mybatis 的4個步驟。這4個步驟看起來很簡單,可是用代碼寫出來就不少。咱們不妨先記着這4個步驟,再去看代碼,會容易點。session

// 1. 
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 2. 
SqlSession session = sqlSessionFactory.openSession();
try {
  // 3. 
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 4.
  Blog blog = mapper.selectBlog(1);
} finally {
  session.close();
}

在這塊代碼中,第 1 部分咱們使用了 Java 編碼的形式來實現 SqlSessionFactory ,也可使用 xml 。若是使用xml的話,上面的第一部分代碼就是這樣的:mybatis

String resource = "org/mybatis/example/mybatis-config.xml"; // xml內容就不貼了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

咱們本次的目標是弄清楚 「 Mapper 是如何找到實現類的 」,咱們注意上面代碼 3 , 4 的位置:app

// 3. 
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 4.
  Blog blog = mapper.selectBlog(1);

這裏 mapper 能夠調用selectBlog(1) 這個方法,說明 mapper 是個對象,由於對象才具備方法行爲實現啊。BlogMapper接口是不能實例化的,更沒有具體方法實現。咱們並無定義一個類,讓它實現BlogMapper接口,而在這裏它只是經過調用session.getMapper() 所獲得的。由此,咱們能夠推斷:確定是session.getMapper() 方法內部產生了BlogMapper的實現類。有什麼技術能夠根據BlogMapper 接口生成了一個實現類呢?想到這裏,對於有動態代理 使用經驗的程序員來講,很容易想到,這背後確定是基於動態代理技術,具體怎麼實現的呢?下面咱們來根據源碼一探究竟。框架

Mapper 接口的註冊

從上面的代碼中,咱們知道 BlogMapper 接口的實現類是從session.getMapper中得來的,大概是基於動態代理技術實現。咱們既然可以從SqlSession中獲得BlogMapper接口的,那麼咱們確定須要先在哪裏把它放進去了,而後 SqlSession 才能生成咱們想要的代理類啊。上面代碼中有這麼一行:

configuration.addMapper(BlogMapper.class);

跟着這個 addMapper 方法的代碼實現是這樣的:

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

咱們看到這裏 mapper 實際上被添加到 mapperRegissry 中。繼續跟進代碼:

public class MapperRegistry {
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers 
                                                  = new HashMap<Class<?>, MapperProxyFactory<?>>();
  
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 {
        knownMappers.put(type, new MapperProxyFactory<T>(type)); // 注意這裏
 
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;

      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}

看到這裏咱們知道上面所執行的configuration.addMapper(BlogMapper.class); 其實最終被放到了HashMap中,其名爲knownMappersknowMappers是MapperRegistry 類的一個私有屬性,它是一個HashMap 。其Key 爲當前Class對象,value 爲一個MapperProxyFactory 實例。

這裏咱們總結一下: 諸如BlogMapper 之類的Mapper接口被添加到了MapperRegistry 中的一個HashMap中。並以 Mapper 接口的 Class 對象做爲 Key , 以一個攜帶Mapper接口做爲屬性的MapperProxyFactory 實例做爲valueMapperProxyFacory從名字來看,好像是一個工廠,用來建立Mapper Proxy的工廠。咱們繼續往下看。

Mapper接口的動態代理類的生成

上面咱們已經知道,Mapper 接口被到註冊到了MapperRegistry中——放在其名爲knowMappers 的HashMap屬性中,咱們在調用Mapper接口的方法的時候,是這樣的:

BlogMapper mapper = session.getMapper(BlogMapper.class);

這裏,咱們跟蹤一下session.getMapper() 方法的代碼實現,這裏 SqlSession 是一個接口,他有兩個實現類,一個是DefaultSqlSession,另一個是SqlSessionManager,這裏咱們用的是DefaultSqlSession. 爲何是DefaultSqlSession呢?由於咱們在初始化SqlSessionFactory的時候所調用的SqlSessionFactoryBuilder的build()方法裏邊配置的就是DefaultSqlSession, 因此,咱們進入到DefaultSession類中,看看它對session.getMapper(BlogMapper.class)是怎麼實現的:

public class DefaultSqlSession implements SqlSession {
  private Configuration configuration;  
  
    @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this); //最後會去調用MapperRegistry.getMapper
  }
}

如代碼所示,這裏的 getMapper 調用了 configuration. getMapper , 這一步操做其實最終是調用了 MapperRegistry,而此前咱們已經知道, MapperRegistry是存放了一個HashMap的,咱們繼續跟蹤進去看看,那麼這裏的get,確定是從這個hashMap中取數據。咱們來看看代碼:

public class MapperRegistry {
  
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();// Mapper 映射
  
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory =
                                  (MapperProxyFactory<T>) knownMappers.get(type);
    
    try {
      return mapperProxyFactory.newInstance(sqlSession); // 重點看這裏
    } catch (Exception e) {
    }
  }
}

咱們調用的session.getMapper(BlogMapper.class);最終會到達上面這個方法,這個方法,根據BlogMapper的class對象,以它爲keyknowMappers 中找到了對應的value —— MapperProxyFactory(BlogMapper) 對象,而後調用這個對象的newInstance()方法。根據這個名字,咱們就能猜到這個方法是建立了一個對象,代碼是這樣的:

public class MapperProxyFactory<T> { //映射器代理工廠

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

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  // 刪除部分代碼,便於閱讀

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用了JDK自帶的動態代理生成映射器代理類的對象
    return (T) Proxy.newProxyInstance(
             mapperInterface.getClassLoader(),
             new Class[] { mapperInterface }, 
             mapperProxy);
  }

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

}

看到這裏,就清楚了,最終是經過Proxy.newProxyInstance產生了一個BlogMapper的代理對象。Mybatis 爲了完成 Mapper 接口的實現,運用了代理模式。具體是使用了JDK動態代理,這個Proxy.newProxyInstance方法生成代理類的三個要素是:

  1. ClassLoader —— 指定當前接口的加載器便可
  2. 當前被代理的接口是什麼 —— 這裏就是 BlogMapper
  3. 代理類是什麼 —— 這裏就是 MapperProxy

代理模式中,代理類(MapperProxy)中才真正的完成了方法調用的邏輯。咱們貼出MapperProxy的代碼,以下:

public class MapperProxy<T> implements InvocationHandler, Serializable {// 實現了InvocationHandler
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //代理之後,全部Mapper的方法調用時,都會調用這個invoke方法
   
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);  //  注意1
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }

    final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了緩存
    //執行CURD
    return mapperMethod.execute(sqlSession, args); // 注意2
  }    
  
  
}

咱們調用的 Blog blog = mapper.selectBlog(1); 實際上最後是會調用這個MapperProxyinvoke方法。這段代碼中,if 語句先判斷,咱們想要調用的方法是否來自Object類,這裏的意思就是,若是咱們調用toString()方法,那麼是不須要作代理加強的,直接還調用原來的method.invoke()就好了。只有調用selectBlog()之類的方法的時候,才執行加強的調用——即mapperMethod.execute(sqlSession, args);這一句代碼邏輯。

mapperMethod.execute(sqlSession, args);這句最終就會執行增刪改查了,代碼以下:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {         //insert  處理,調用SqlSession的insert
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) { // update
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {   // delete
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      // 刪除部分代碼 
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
     // 刪除部分代碼
    return result;
  }

再往下一層,就是執行JDBC那一套了,獲取連接,執行,獲得ResultSet,解析ResultSet映射成JavaBean。

至此,咱們已經摸清楚了Blog blog = mapper.selectBlog(1); 中,BlogMapper接口調用到獲得數據庫數據過程當中,Mybaitis 是如何爲接口生成實現類的,以及在哪裏出發了最終的CRUD調用。實際上,若是咱們在調用Blog blog = mapper.selectBlog(1);以前,把從slqSession中獲得的 mapper 對象打印出來就會看到,輸出大概是這樣的:

com.sun.proxy.$Proxy17

動態代理沒錯吧,Java動態代理實在是太美妙了。

總結

上面咱們用層層深刻的方式摸清楚了 Mapper接口是如何找到實現類的。咱們分析了 Mapper接口是如何註冊的,Mapper接口是如何產生動態代理對象的,Maper接口方法最終是如何執行的。總結起來主要就是這幾個點:

1. Mapper 接口在初始SqlSessionFactory 註冊的。
2. Mapper 接口註冊在了名爲 MapperRegistry 類的 HashMap中, key = Mapper class value = 建立當前Mapper的工廠。
3. Mapper 註冊以後,能夠從SqlSession中get
4. SqlSession.getMapper 運用了 JDK動態代理,產生了目標Mapper接口的代理對象。
5. 動態代理的 代理類是 MapperProxy ,這裏邊最終完成了增刪改查方法的調用。
相關文章
相關標籤/搜索