spring、mybatis整合源碼簡單分析

配置

<bean id="localDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://192.168.31.14:3366/lios?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        ...
    </bean>
    <!-- 創建SqlSessionFactory,同時指定數據源-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="localDataSource"/>
        <property name="configLocation" value="classpath:sqlmap-config.xml"/>
        <property name="mapperLocations">
            <list>
               <value>classpath*:com/lios/mybatis/mapper/*.xml</value>
            </list>
        </property>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.lios.mybatis.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

MapperScannerConfigurer這個bean有什麼作用呢,MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,該接口可以讓我們實現自定義並註冊bean,具體可以參考關於BeanDefinitionRegistryPostProcessor接口使用的文章,無疑分析入口還是從 org.springframework.context.support.AbstractApplicationContext#refresh方法開始.

分析

掃描basePackages,封裝MapperFactoryBean,註冊到spring容器

AbstractApplicationContext類的 refresh方法裏,會調用:

invokeBeanFactoryPostProcessors(beanFactory);

調用BeanFactory的後置處理器,向容器中註冊自定義Bean,一直跟到 PostProcessorRegistrationDelegate類的 invokeBeanFactoryPostProcessors方法中這段代碼:

// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
boolean reiterate = true;
while (reiterate) {
    reiterate = false;
    postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
    for (String ppName : postProcessorNames) {
        if (!processedBeans.contains(ppName)) {
            //getBean方法會初始化MapperScannerConfigurer
            BeanDefinitionRegistryPostProcessor pp = beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class);
            registryPostProcessors.add(pp);
            processedBeans.add(ppName);
            // 調用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法
            pp.postProcessBeanDefinitionRegistry(registry);
            reiterate = true;
        }
    }
}

跟到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法中,關鍵代碼:

...
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

這段代碼用於掃描MapperScannerConfigurer中配置的basePackage路徑下的文件.繼續根進 ClassPathBeanDefinitionScanner類的 scan方法:

// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);

doScan方法會調用 ClassPathMapperScanner#doScan類中的doScan方法:

// 調用父類doScan方法,掃描basePackage下的mapper的接口文件,封裝成Set<BeanDefinitionHolder>
doScan(basePackages);
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
  logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
  processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;

processBeanDefinitions方法很重要:

GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
  definition = (GenericBeanDefinition) holder.getBeanDefinition();
  // the mapper interface is the original class of the bean
  // but, the actual class of the bean is MapperFactoryBean
  definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
  // 上面的註釋其實說的很清楚了,mapper接口實際的實體爲MapperFactoryBean
  definition.setBeanClass(this.mapperFactoryBean.getClass());
  // 設置MapperFactoryBean屬性addToConfig元素
  definition.getPropertyValues().add("addToConfig", this.addToConfig);
  ...
  // 設置MapperFactoryBean屬性sqlSessionTemplate元素
  definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
}

經過上面的流程,basePackages下的mapper接口已經註冊到容器中.

實例化MapperFactoryBean中SqlSessionFactory,解析xml配置文件

繼續回到 AbstractApplicationContext類中的 refresh中,會在該方法中初始化所有單例且是懶加載的bean,如果在應用中注入使用mapper接口時:

@Autowired
UserInfoDao userInfoDao;

就會初始化該mapper實例,其實就是初始化 MapperFactoryBean,spring會檢查該bean的屬性是否爲對象,依次初始化,由於 MapperFactoryBean中的屬性SqlSessionTemplate、addToConfig,由於SqlSessionTemplate已經在配置文件配置,繼而又會去初始化SqlSessionTemplate的屬性 org.mybatis.spring.SqlSessionFactoryBean,因爲SqlSessionFactoryBean實現了 InitializingBean接口,所以在初始化時會調用其 afterPropertiesSet方法:

@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
          "Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}

buildSqlSessionFactory方法非常關鍵,用來解析mppaer xml文件,關鍵代碼:

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();

這裏不作具體分析。

生成mapper接口動態代理類

當MapperFactoryBean中的屬性初始化完後,則繼續執行MapperFactoryBean的初始化流程,在 AbstractBeanFactory類的 doGetBean方法中:

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

調用了 AbstractBeanFactory類的getObjectForBeanInstance方法:

object = getObjectFromFactoryBean(factory, beanName, !synthetic);

因爲MapperFactoryBean實現了FactoryBean接口,所以纔可以向下執行代碼, 繼續調用了 FactoryBeanRegistrySupport類的getObjectFromFactoryBean方法:

Object object = doGetObjectFromFactoryBean(factory, beanName);

繼續調用 FactoryBeanRegistrySupport類中的doGetObjectFromFactoryBean方法:

...
object = factory.getObject();
...

原來調用了FactoryBean的getObject方法,這時則斷點執行到了 MapperFactoryBean的getObject方法中:

return getSqlSession().getMapper(this.mapperInterface);

繼續執行到 org.apache.ibatis.session.Configuration的getMapper方法:

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

上面代碼是不是很熟悉,原來爲mapper 接口創建了代理類 MapperProxy<T>,當調用mapper接口中具體的方法操作數據庫時,其實執行的的是 MapperProxy<T>中的invoke方法:

try {
  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);

上面還有一個關鍵點就是,xml中解析的配置如何與spring容器中mapper bean相關聯呢,其實通過 DaoSupport類中的 checkDaoConfig方法,在 DaoSupport類的 afterPropertiesSet方法中調用,具體看MapperFactoryBean中的checkDaoConfig實現:

@Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    // mybatis中配置類
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 添加mapper關聯
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

到此爲止,已經分析完了mybatis與spring結合的源碼簡單說明,省略了大量的細節,以及mapper xml文件解析、sql執行流程沒有分析,後續文章會做分析。由於作者水平有限,文章存在錯誤之處,肯請斧正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章