寫在前面
聊一聊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的定義

相信大家也猜到了,上面提到的就是維護這個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,這幾個類將會是接下來探討的重點。

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

可以看到它實現了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同樣實現了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
那么Mapper對應的模型有了,是不是還缺點什么?是的,我們需要掃描所有的Mapper,將他們包裝成MapperFactoryBean(如UserMapper,就需要有一個MapperFactoryBean,其中mapperInterface字段是UserMapper.class)。這個重要的任務,Spring交給了我們接下來要聊的MapperScannerConfigurer了,通過類名就能感知到關鍵字:[Mapper、掃描、配置]。
Mapper的掃描與配置-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管理的目標。
原創不易,希望對你有幫助。歡迎多多指導和討論。
