1. 程式人生 > >spring、mybatis整合原始碼簡單分析

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執行流程沒有分析,後續文章會做分析。由於作者水平有限,文章存在錯誤之處,肯請斧正,謝謝!