深入原始碼理解Spring整合MyBatis原理

DeepSleeping丶發表於2021-07-29

寫在前面

聊一聊MyBatis的核心概念、Spring相關的核心內容,主要結合原始碼理解Spring是如何整合MyBatis的。(結合右側目錄瞭解吧)

MyBatis相關核心概念粗略回顧

SqlSessionFactory

建立SqlSession的工廠

SqlSession

sql請求的會話,通過SqlSessionFactory獲取。

   String resource = "mybatis-config.xml";
   InputStream resourceAsStream = Resources.getResourceAsStream(resource);

   SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
   SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);  // 通過Builder獲取構建SqlSessionFactory(讀取mybatis-config.xml檔案配置)

   SqlSession sqlSession = sqlSessionFactory.openSession();    // 開啟Session
   UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
   User user = userMapper.findByUserId(1);

上述程式碼就是單獨使用MyBatis的時候的API例子。讀取mybatis-config.xml構建出SqlSessionFactory,通過Factory開啟SqlSession,使用SqlSession獲取Mapper的代理例項。

Mapper

public interface UserMapper {
    User findByUserId(Integer userId);
}
<mapper namepsace = "com.deepz.mybatis.user.UserMapper">
     <select id = "findByUserId" resultType="User">
          ...
    </select>
</mapper>

以上就是我們們熟悉的MyBatis使用程式碼了,一個Mapper介面對應的就有一個XML檔案。

Spring相關核心概念粗略回顧

FactoryBean介面

是一種特殊的SpringBean,對應的真實例項是FactoryBean介面中getObject()方法的返回值,用於自定義複雜的Bean生成。

FactoryBean型別的Bean的獲取

AbstractBeanFactory#doGetBean

Object sharedInstance = getSingleton(beanName);    // 從三級快取中根據beanName獲取SpringBean
if (sharedInstance != null && args == null) {              // 如果SpringBean不為空則說明命中快取,直接獲取SpringBean例項即可
	if (logger.isDebugEnabled()) {
		if (isSingletonCurrentlyInCreation(beanName)) {
			logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
					"' that is not fully initialized yet - a consequence of a circular reference");
		}
		else {
			logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
		}
	}
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);    // 獲取SpringBean真實例項(針對FactoryBean)
}

AbstractBeanFactory#getObjectForBeanInstance

// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {  // 如果不是FactoryBean型別則直接返回
	return beanInstance;
}

// 後續程式碼針對FactoryBean型別的SpringBean處理
Object object = null;
if (mbd == null) {
        object = getCachedObjectForFactoryBean(beanName);    // 從快取中獲取
}
if (object == null) {
	// Return bean instance from factory.
	FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
	// Caches object obtained from FactoryBean if it is a singleton.
	if (mbd == null && containsBeanDefinition(beanName)) {
		mbd = getMergedLocalBeanDefinition(beanName);
	}
	boolean synthetic = (mbd != null && mbd.isSynthetic());
	object = getObjectFromFactoryBean(factory, beanName, !synthetic);  // 獲取FactoryBean真實例項
}
return object;

FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
FactoryBeanRegistrySupport#getObjectFromFactoryBean最終會調到如下方法,通過FactoryBean的getObject()獲取FactoryBean的真實例項

Object object;
try {
	if (System.getSecurityManager() != null) {
		AccessControlContext acc = getAccessControlContext();
		try {
			object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
				@Override
				public Object run() throws Exception {
						return factory.getObject();
					}
				}, acc);
		}
		catch (PrivilegedActionException pae) {
			throw pae.getException();
		}
	}
	else {
		object = factory.getObject();  // 顯示呼叫FactoryBean#getObject
	}
}

InitializingBean介面

在SpringBean的生命週期中,Bean的初始化環節Spring會呼叫AbstractAutowireCapableBeanFactory#invokeInitMethods() 回撥實現了InitializingBean介面的Bean的InitializingBean#afterPropertiesSet()

關於SpringBean生命週期歡迎移步筆者的相關總結隨筆《深入原始碼理解SpringBean生命週期

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
			throws Throwable {

	boolean isInitializingBean = (bean instanceof InitializingBean);  // Bean實現了InitializingBean介面
	if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
		if (logger.isDebugEnabled()) {
			logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
		}
		if (System.getSecurityManager() != null) {
			try {
				AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
					@Override
					public Object run() throws Exception {
						((InitializingBean) bean).afterPropertiesSet();
						return null;
					}
				}, getAccessControlContext());
			}
			catch (PrivilegedActionException pae) {
				throw pae.getException();
			}
		}
		else {
			((InitializingBean) bean).afterPropertiesSet();  // 顯式回撥InitializingBean的afterPropertiesSet()方法
		}
	}

	if (mbd != null) {
		String initMethodName = mbd.getInitMethodName();
		if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
				!mbd.isExternallyManagedInitMethod(initMethodName)) {
			invokeCustomInitMethod(beanName, bean, mbd);
		}
	}
}

BeanDefinitionRegistryPostProcessor介面

可以看到BeanDefinitionRegistryPostProcessor繼承了BeanFactoryPostProcessor,那麼該介面的實現類一定會在Spring應用上下文生命週期中回撥相關介面方法。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean definition registry after its
	 * standard initialization. All regular bean definitions will have been loaded,
	 * but no beans will have been instantiated yet. This allows for adding further
	 * bean definitions before the next post-processing phase kicks in.
	 * @param registry the bean definition registry used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

BeanFactoryPostProcessor介面

這個介面是幹什麼的?它有自己的抽象方法,在Spring應用上下文生命週期的"invokeBeanFactoryPostProcessors"環節會回撥相關方法。(這裡不是重點不過多聊)

我們這次關注的重點是它的子介面-BeanDefinitionRegistryPostProcessor,Spring在上述環節中對該介面做了特殊處理,回撥了它的獨有方法。(如上面的程式碼段所示)

BeanDefinitionRegistryPostProcessor介面回撥

AbstractApplicationContext#refresh()方法即為Spring應用上下文的生命週期的重新整理入口,可以看到在比較前置的環節就會先處理BeanFactoryProcessor型別的Bean。

追進原始碼後發現最後會在PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法中迴圈處理所有BeanFactoryProcessor,其中如果是BeanDefinitionRegistryPostProcessor則會回撥BeanDefinitionRegistryPostProcessor的獨有方法,值得注意的是該方法的入參是一個BeanDefinitionRegistry

(關於BeanDefinitionRegistry有必要在單獨段落做介紹,所以請先移步下一段落吧)

在瞭解了這幾個介面後,我們彙總一下就明白了BeanDefinitionRegistryPostProcessor的能力了。它可以在Spring應用上下文前期先被例項化且回撥相關介面方法,向Spring容器註冊或移除BeanDefinition甚至可以在get出一個BeanDefinition後直接修改內部屬性,讓Bean變成你想要的模樣。

BeanDefinitionRegistry介面

BeanDefinition是什麼?

BeanDefinition是Spring例項化一個Bean的依據,它的內部維護了一個Bean的各種屬性,如BeanClass、BeanName、lazyInit(是否懶載入)、primary、scope等等。

而Spring在例項化一個Bean的時候需要先從一個Map中根據beanName獲取到對應的BeanDefinition才能去按需例項化SpringBean,如下。

DefaultListableBeanFactory#getBeanDefinition()

看看這個Map的定義
BeanDefinition

相信大家也猜到了,上面提到的就是維護這個Map的。
BeanDefinitionRegistry介面的方法如下:

可以看到該介面的能力就是維護Spring容器中的BeanDefinition。

Spring整合MyBatis正片

有了上面的回顧後,關於Spring整合MyBatis的祕密就很容易揭曉了。

在跟進原始碼前,我們先思考下如果是你,你會怎麼將MyBatis整合進來?達到效果:通過@Autowired將Mapper注入進來便可以直接使用。

首先,我們有了Spring,第一個要幹掉的就是SqlSessionFactory的維護了,我們要想辦法讀取”mybatis-config.xml“配置後,將SqlSessionFactory作為一個SpringBean交給SpringIOC管理。

其次,同樣的,我們不可能每次都通過sqlSession.getMapper()來獲取我們需要的Mapper代理例項,所以第二個要幹掉的就是Mapper的維護,我們同樣要想辦法將所有的Mapper處理成SpringBean交給SpringIOC,這樣我們就能夠將SpringBean依賴注入到任何地方了。

思考過後,我們來看看Spring是怎麼做的吧。當我們需要結合Spring使用MyBatis的時候,第一步便是新增一個mybatis-spring的Jar到專案裡來,那麼祕密都在這裡了。

MyBatis-Spring

如下便是MyBatis-Spring這個Jar的專案結構了,應該能看到大家使用的時候熟悉的元件吧。如MapperScan註解。眼尖的夥伴應該能看到幾個類:SqlSessionFactoryBean、MapperFactoryBean、MapperScannerConfigurer、SpringManagedTransaction,這幾個類將會是接下來探討的重點。
MyBatis-Spring

SqlSessionFactoryBean

剛剛我們聊到了,首先需要幹掉的就是SqlSessionFactory的低端維護方式。我們先看看SqlSessionFactory這個類的繼承樹。

SqlSessionFactoryBean繼承樹
可以看到它實現了FactoryBean,這就意味著它一定有一個getObject()方法,用於返回交給Spring管理的例項;
它還實現了InitializingBean,這就意味著在這個Bean的初始化時,Spring會回撥它的afterPropertiesSet()方法。(Spring事件本次不討論)

將SqlSessionFactory交給Spring管理-FactoryBean#getObject()

我們先看看,這個SqlSessionFactoryBean交給Spring管理的物件是怎樣構建的。

  /**
   * {@inheritDoc}
   */
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {   // 如果sqlSessionFactory為空,則顯式呼叫afterPropertiesSet()方法
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;       // 返回sqlSessionFactory
  }

  @Override
  public Class<? extends SqlSessionFactory> getObjectType() {
    return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
  }

可以看到原始碼中首先是判空,如果為空則顯式呼叫afterPropertiesSet()方法,最後將sqlSessionFactory返回。那麼可以猜出,afterPropertiesSet()方法大概率是構造sqlSessionFactory的了。

SqlSessionFactory的構建

首先看看afterPropertiesSet()方法

  /**
   * {@inheritDoc}
   */
  @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()中。
  }

跟進看看buildSqlSessionFactory()方法,請重點看標了註釋的,其他的可以不用太關注細節,主要是一些配置的初始化工作。

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    // 初始化Configuration全域性配置物件。
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      // 讀取指定的配置檔案
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }

    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }
    
    // 如下將是對MyBatis的基礎配置做初始化,如掃描註冊別名、註冊Plugins、註冊TypeHandler、配置快取、配置資料來源等等。
    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }

    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }

    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }

    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }

    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }

    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    if (this.cache != null) {
      configuration.addCache(this.cache);
    }

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    // 配置事務管理類,將不再由MyBatis管理(之前配置為“JDBC”對應的MyBatis的JdbcTransactionFactory)。
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    
     // 掃描Mapper.xml以及對應的Mapper介面
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
    
    //前置條件準備好後,建立SqlSessionFactory物件
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

總體就是讀取MyBatis的配置,初始化Configuration全域性配置物件,根據整合的配置建立出SqlSessionFactory。

經過了上述步驟,SqlSessionFactory就可以被交給Spring管理了,解決了第一個問題,可以從Spring上下文中獲取到SqlSessionFactory這個Bean了。

ClassPathXmlApplication applicationContext = new ClassPathXmlApplicationContext("spring.xml");
SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 省略後續MyBatis程式碼 -.-
......

Mapper的代理物件-MapperFactoryBean

接下來討論Spring如何實現通過@Autowired將Mapper注入進來後就能直接使用的問題。

首先思考一下,如何將一個類交給Spring管理?@Component系列註解?而MyBatis是一個個Interface而不是Class,在上面加註解是沒用的,我們需要的是將MyBatis對Mapper生成的代理物件交給Spring管理。那該怎麼做呢?Spring的做法是將Mapper一對一地包裝成了MapperFactoryBean,而MapperFactoryBean維護了Mapper的型別,通過該型別獲取Mapper代理例項。

MapperFactoryBean的繼承樹
可以看到這個MapperFactoryBean同樣實現了FactoryBean介面,那麼按照慣例我們看看它的getObject()做了什麼。

  // Mapper介面型別
  private Class<T> mapperInterface;

  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    // 與MyBatis單獨使用類似,都是通過sqlSession呼叫getMapper()方法獲取對應的Mapper。
    // 需要注意的是入參是一個介面型別,而出參是MyBatis生成的代理物件
    return getSqlSession().getMapper(this.mapperInterface);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;  // Object的型別
  }

可以看出Spring將Mapper包裝成了MapperFactoryBean,其中的mapperInterface欄位就是Mapper的型別,在交給Spring管理的時候依舊是通過sqlSession.getMapper(Class type)返回Mapper的代理物件的。

那麼Mapper對應的模型有了,是不是還缺點什麼?是的,我們需要掃描所有的Mapper,將他們包裝成MapperFactoryBean(如UserMapper,就需要有一個MapperFactoryBean,其中mapperInterface欄位是UserMapper.class)。這個重要的任務,Spring交給了我們接下來要聊的MapperScannerConfigurer了,通過類名就能感知到關鍵字:[Mapper、掃描、配置]

Mapper的掃描與配置-MapperScannerConfigurer

MapperScannerConfigurer

老規矩,看看幾個核心介面的方法都做了什麼。

afterPropertiesSet()

  /**
   * {@inheritDoc}
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    // 幾乎啥也沒幹,就斷言了個掃描包路徑不為空。 下一個!
    notNull(this.basePackage, "Property 'basePackage' is required");
  }

掃描Mapper並註冊BeanDefinition

可以看到MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor介面,文章開頭的Spring技術回顧聊到該介面的postProcessBeanDefinitionRegistry()方法會在Spring容器啟動的時候在較早的時機被回撥。


  private String basePackage;

  private boolean addToConfig = true;

  private SqlSessionFactory sqlSessionFactory;

  private SqlSessionTemplate sqlSessionTemplate;

  private String sqlSessionFactoryBeanName;

  private String sqlSessionTemplateBeanName;

  private Class<? extends Annotation> annotationClass;

  private Class<?> markerInterface;

  private ApplicationContext applicationContext;

  private String beanName;

  private boolean processPropertyPlaceHolders;

  private BeanNameGenerator nameGenerator;

  /**
   * {@inheritDoc}
   * 
   * @since 1.0.2
   */
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      // 解析並更新Spring配置檔案中MapperScannerConfigurer相關的配置
      processPropertyPlaceHolders();
    }

    //建立類路徑Mapper掃描器,並配置基本資訊如掃描的註解(過濾條件)等。
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    //根據配置好的資訊去掃描basePackage欄位中指定的包及其子包
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

我們來看看processPropertyPlaceHolders()做了什麼。[可以跳過,不重要]

  /*
   * BeanDefinitionRegistries are called early in application startup, before
   * BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
   * loaded and any property substitution of this class' properties will fail. To avoid this, find
   * any PropertyResourceConfigurers defined in the context and run them on this class' bean
   * definition. Then update the values.
   */

  // 上面Spring官方的註釋的意思如下:BeanDefinitionRegistriy在Spring啟動的時候回撥地太早了,在BeanFactoryPostProcessors之後(PropertyResourceConfigurer實現了BeanFactoryProcessor)
  // 方法呼叫到此處的時候,相關的配置資訊還沒被載入進來,都是空,會有問題。所以我們要提前主動觸發(getBeanOfType與getBean邏輯一致,都是先拿,拿不到就例項化再存入三級快取)PropertyResourceConfigurer的例項化,這樣相關的配置就能夠被載入進來了。
  private void processPropertyPlaceHolders() {
    // 先主動觸發該型別的Bean的例項化。
    Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);

    if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
      BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)
          .getBeanFactory().getBeanDefinition(beanName);

      // PropertyResourceConfigurer does not expose any methods to explicitly perform
      // property placeholder substitution. Instead, create a BeanFactory that just
      // contains this mapper scanner and post process the factory.
      DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      factory.registerBeanDefinition(beanName, mapperScannerBean);
      
      for (PropertyResourceConfigurer prc : prcs.values()) {
        prc.postProcessBeanFactory(factory);
      }

      PropertyValues values = mapperScannerBean.getPropertyValues();

      // 更新相關重要欄位資訊
      this.basePackage = updatePropertyValue("basePackage", values);
      this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
      this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
    }
  }

根據條件掃描Mapper並整合成BeanDefintions註冊進Spring

回到ClassPathMapperScanner#scan(),該方法內部會繼續呼叫doScan()方法。

  /**
   * Calls the parent search that will search and register all the candidates.
   * Then the registered objects are post processed to set them as
   * MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //先呼叫父類ClassPathBeanDefinitionScanner#doScan()方法
    //掃描並將Bean資訊整合成BeanDefinition註冊進Spring容器,且包裝成BeanDefinitionHolder返回
    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 {
      // 重要!!! 將BeanDefinition的定義適配成MapperFactoryBean。(當前BeanDefinition的beanClass是Mapper Interface,是無法例項化的。)
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

加工Mapper的BeanDefinition-適配成MapperFactoryBean

如下是processBeanDefinitions()的核心程式碼片段

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    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
      // 新增建構函式引數值,當前Mapper的Class。
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      // 將Bean的型別定義修改為MapperFactoryBean,這樣例項化出來的就是一個MapperFactoryBean了
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);
        
       // 省略程式碼
        ......
     }
  }

這段程式碼非常關鍵!首先,它對Bean的建構函式引數值做了干預,將當前的BeanClassName設定進去了(如UserMapper.class),而從第二行程式碼中也能知道該Bean的Class被修改成了MapperFactoryBean,所以我們去看看MapperFactoryBean的建構函式就行了。

  public MapperFactoryBean(Class<T> mapperInterface) {
     // Mapper Interface。(如UserMapper.class)
    this.mapperInterface = mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface); // 等同於如:sqlSession.getMapper(UserMapper.class)
  }

其次它將Bean的例項化型別從無法例項化的Mapper Interface修改成了可以例項化的MapperFactoryBean型別。

以上操作後,就將一個Mapper Bean包裝成了MapperFactoryBean,而交給Spring管理的是Mapper對應的代理例項(通過mapperInterface欄位繫結關係),所以我們就能通過@Autowired將Mapper(MapperFactoryBean#getObject())依賴注入進來直接使用了。

到此,Spring整合MyBatis的內容就結束了。

Spring事務管理器-SpringManagedTransaction

再順便聊一下其中的變動-事務管理器,因為Spring整合了MyBatis所以後續的事務就應該由Spring來管理了。

之前在MyBatis中如果配置的是“JDBC",則是JdbcTransactionFactory

值得一提的是,SpringManagedTransaction 中除了維護事務關聯的資料庫連線和資料來源之外,還維護了一個 isConnectionTransactional 欄位(boolean 型別)用來標識當前事務是否由 Spring 的事務管理器管理,這個標識會控制 commit() 方法和rollback() 方法是否真正提交和回滾事務,相關的程式碼片段如下:

public void commit() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit){
        // 當事務不由Spring事務管理器管理的時候,會立即提交事務,否則由Spring事務管理器管理事務的提交和回滾
        this.connection.commit();
    }
}

總結

看一眼Xml方式的Spring整合MyBatis,通過兩個核心的元件就完成了Spring整合MyBatis。

     <!-- 省略 -->
 
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
 
    <bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- name="basePackage":(起始)包名, 從這個包開始掃描-->
        <property name="basePackage" value="com.deepz.mapper"/>
    </bean>

    <!-- 省略 -->

總結下本文的內容吧:

  • 回顧了MyBatis的相關概念:SqlSessionFactory、SqlSession、Mapper
  • 回顧了Spring的相關概念:FactoryBean、InitializingBean、BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor
  • 跟進Spring整合MyBatis的兩個重要步驟:1. SqlSessionFactory的維護 2. Mapper的維護
  • 最後還提了一下事務管理器的變化,畢竟是被Spring整合了,事務自然也得交給Spring管理

SqlSessionFactory的注入:

SqlSessionFactoryBean中的buildSqlSessionFactory()會讀取MyBatis的核心配置載入記憶體,並構建出SqlSessionFactory通過FactoryBean的getObject()交給Spring管理。

而buildSqlSessionFactory()方法的觸發時機有兩個:1. 在Bean初始化的時候Spring回撥InitializingBean的afterProperties();2. FactoryBean的getObject()方法會前置判斷SqlSessionFactory是否為空,是空則會呼叫。

Mapper的注入:
MapperScannerConfigurer在Spring應用上下文啟動的時候,在較早的時機回撥BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法

通過ClassPathMapperScanner來掃描指定過濾條件(包路徑、註解型別...)的類,包裝成BeanDefinition註冊進容器。

同時將這些BeanDefinition做“加工”處理,就是我們講的“processBeanDefinitions()”。它主要做的兩件事:1. 新增建構函式引數值,將當前BeanDefinition的Class傳遞進去,作為後續sqlSession.getMapper();的入參。2. 將BeanDefinition中的beanClass替換成MapperFactoryBean.class,使得Spring通過BeanDefinition例項化出來的是MapperFactoryBean,上演了一出狸貓換太子。最後注入進去的又是getObject()中MyBatis根據MapperFactoryBean中的mapperInterface欄位建立的代理物件, 完成了將Mapper交給Spring管理的目標。

原創不易,希望對你有幫助。歡迎多多指導和討論。

相關文章