mybatis 源碼探究:兩次JDK動(dòng)態(tài)代理

mybatis中所有Dao接口的實(shí)現(xiàn)類(lèi)都是MapperProxy


問(wèn)題

問(wèn)題描述:在使用mybatis時(shí),我們只定義了接口,然后在XxxMapper.xml中寫(xiě)sql,若使用Mapper接口,甚至連XxxMapper.xml都可以省略,這一切究竟是如何實(shí)現(xiàn)的呢?


大多數(shù)項(xiàng)目都會(huì)使用spring+mybatis來(lái)搭建,本文參照mybatis-spring(1.2.0)分析上述問(wèn)題。

將mybatis集成到spring中,添加配置spring-dao.xml
<!-- 數(shù)據(jù)庫(kù)連接池信息 BoneCP configuration -->
    <bean id="dataSource"  class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
        <property name="driverClass" value="${jdbc.driverClass}" />
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    <!-- 集成Mybatis -->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!--MyBatis的DAO 無(wú)需實(shí)現(xiàn)類(lèi)配置 自動(dòng)掃描實(shí)現(xiàn)mapper.xml文件方法-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.foo.dao" />
        <property name="sqlSessionFactoryBeanName" value="sessionFactory" />
    </bean>

上述配置文件為mybatis的入口,閱讀源碼首先要找到入口,不能漫無(wú)目的地在源碼里閑逛。這里需要Spring的一點(diǎn)基礎(chǔ)知識(shí)(例如FactoryBean的作用)。與Spring的集成,本質(zhì)是是把自己扮成Spring的bean,然后Spring能感知到你的存在,從而對(duì)你進(jìn)行管理。咱們可以看到,mybatis在這里所有的配置信息,都是在<bean>標(biāo)簽里的。
這三個(gè)bean中,咱們挨個(gè)分析。

  1. id為dataSource的bean,最簡(jiǎn)單。只是封裝了一些數(shù)據(jù)庫(kù)的連接(包括配置)信息在里面。
  2. id為sessionFactory的bean,定義了SqlSessionFactoryBean,將dataSource bean作當(dāng)做了一個(gè)參數(shù)。暫且不管,繼續(xù)。
  3. 最后一個(gè)bean,可以看到,沒(méi)有定義id,說(shuō)明沒(méi)有人顯示地引用它。其實(shí)它就是spring-mybatis的入口。參數(shù)basePackage 是我們的Dao接口包,用于后面接口掃描使用。后一個(gè)參數(shù)sqlSessionFactoryBeanName,只是簡(jiǎn)單地給他賦了值。

咱們現(xiàn)在已經(jīng)知道spring-mybatis的入口了,開(kāi)干吧。


image1.png

以下的分析內(nèi)容,可以認(rèn)為是在對(duì)image1的解釋。
進(jìn)入MapperScannerConfigurer,

BeanDefinitionRegistryPostProcessor, 
InitializingBean, 
ApplicationContextAware, 
BeanNameAware 

發(fā)現(xiàn)其實(shí)現(xiàn)了這四個(gè)接口。又是Spring的知識(shí)了,這些都是spring的擴(kuò)展點(diǎn),不多做介紹。重點(diǎn)看,BeanDefinitionRegistryPostProcessor。這個(gè)接口里只有一個(gè)方法postProcessBeanDefinitionRegistry,會(huì)在BeanDefinition加載完之后執(zhí)行。進(jìn)入該方法:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
   //ignore unrelated code
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

忽略非重點(diǎn)代碼。省略的代碼,是在設(shè)置屬性。重點(diǎn)看保留下來(lái)的最后一句代碼。這句話(huà)的意思是掃描包。不知各位還記得spring-dao.xml中這么一句話(huà):

<property name="basePackage" value="com.foo.dao" />

那么這里的scan(),掃描了com.foo.dao包下面的所有類(lèi),即咱們定義的DAO接口。根據(jù)scan()方法層層推進(jìn),來(lái)到了,org.mybatis.spring.mapper.ClassPathMapperScanner#doScan()。這個(gè)方法里有三句話(huà)比較重要:

① definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
② definition.setBeanClass(MapperFactoryBean.class);
③ definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));

①:將我們自定義的DAO接口作為參數(shù)放入BeanDefinition中,屬性名為mapperInterface;
②:設(shè)置BeanDefinition的BeanClass,MapperFactoryBean。這里對(duì)于文章開(kāi)頭“mybatis中所有Dao接口的實(shí)現(xiàn)類(lèi)都是MapperProxy”的這句話(huà),已初見(jiàn)端倪;
③:新增屬性名為sqlSessionFactory的屬性。

從這里可以看出兩點(diǎn):

  1. 咱們自定義的DAO接口,被新增了兩個(gè)屬性(mapperInterface與sqlSessionFactory),那么在創(chuàng)建bean的時(shí)候,會(huì)給這兩個(gè)屬性賦值,即調(diào)用這兩個(gè)屬性的set方法;
  2. DAO接口BeanDefinition的BeanClass是MapperFactoryBean。并且細(xì)心的小伙伴發(fā)現(xiàn),這是一個(gè)工程bean(FactoryBean),在創(chuàng)建bean時(shí),是從其getObject()方法獲取真正的bean。

BeanDefinition加載完后,開(kāi)始創(chuàng)建bean。


image2.png
開(kāi)始image2的分析:

由于我們自定義DAO接口的BeanClasss是MapperFactoryBean,所以創(chuàng)建的是MapperFactoryBean,遂進(jìn)入MapperFactoryBean源碼。
上文咱們提到,會(huì)調(diào)用sqlSessionFactory的set方法(setSqlSessionFactory)。發(fā)現(xiàn)該方法在MapperFactoryBean的父類(lèi)SqlSessionDaoSupport中,進(jìn)入:

 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

層層跟進(jìn),直至最底層構(gòu)造方法:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

此處咱們看到了jdk動(dòng)態(tài)代理(SqlSessionInterceptor),這也是咱們image2中的標(biāo)注的第一次動(dòng)態(tài)代理。跟進(jìn):

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }

與數(shù)據(jù)庫(kù)的操作都是通過(guò)SqlSession來(lái)完成的。這里咱們看到生成了sqlSession,然后根據(jù)具體情況,來(lái)判斷是否自動(dòng)提交。
setSqlSessionFactory()方法到這算基本講完了,新建了一個(gè)SqlSessionTemplate,回到MapperFactoryBean。
由于MapperFactoryBean是FactoryBean的實(shí)現(xiàn)類(lèi),所以會(huì)調(diào)用其getObject()方法來(lái)獲取真正的bean。進(jìn)入該方法:

public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

無(wú)實(shí)際內(nèi)容,重點(diǎn)內(nèi)容都在getMapper()里面。by the way,這里的mapperInterface,咱們?cè)趇mage1里有提到,若遺忘,往上翻。由于咱們剛才通過(guò)setSqlSessionFactory()方法,新建了一個(gè)SqlSessionTemplate,所以這里通過(guò)SqlSessionTemplate#getMapper()層層跟進(jìn),直至出現(xiàn)明顯有特殊意義的代碼,到了MapperRegistry#getMapper():

 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);
   }
 }

跟進(jìn)mapperProxyFactory.newInstance(sqlSession):

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<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

這兩個(gè)方法,其實(shí)又是一次動(dòng)態(tài)代理(image2的第二次動(dòng)態(tài)代理)。
回到getMapper(),最后返回了MapperProxy 對(duì)象。


image3.png

通過(guò)image2的分析,咱們知道,我們自定義的DAO接口,最后被封裝成了MapperProxy。當(dāng)我們?cè)谡{(diào)用DAO接口里面的方法時(shí),實(shí)際是調(diào)用了MapperProxy#invoke()。

進(jìn)入MapperProxy的invoke()方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

這里通過(guò)動(dòng)態(tài)代理獲取到了MapperMethod方法,然后開(kāi)始execute。進(jìn)入:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

這一大段代碼基本都是比較熟悉的代碼。根據(jù)SqlCommandType判斷是增刪改查的哪一個(gè),然后調(diào)用SqlSession對(duì)應(yīng)的方法去執(zhí)行。請(qǐng)記住,這里的SqlSession已經(jīng)被動(dòng)態(tài)代理過(guò)了,實(shí)際上是調(diào)用了SqlSessionInterceptor#invoke()方法。


As always and let me know what you think, leave the comments below. Bye :)

項(xiàng)目完整源碼github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 心理學(xué)家表示,微笑是人類(lèi)最好的表情、最美的無(wú)聲語(yǔ)言。微笑代表著良好的心態(tài),是全社會(huì)最好的通行證。 請(qǐng)記住,如果您對(duì)...
    圣雅妃妮閱讀 302評(píng)論 0 1
  • 1. 模仿湖南兒歌《月亮粑粑》寫(xiě)一段荒誕不經(jīng)然而押韻的文字(不用一韻到底,可以幾句一變化): 風(fēng)兒飄飄,扇子搖搖,...
    塵世一眼閱讀 175評(píng)論 1 0
  • 我看著媽媽?zhuān)?想象著我老了的樣子。 花白的頭發(fā),渾濁的眼睛, 有很多故事的抬頭紋, 還有那被時(shí)間刻上烙印的老繭。 ...
    橘子小姐醬閱讀 206評(píng)論 3 0

友情鏈接更多精彩內(nèi)容