聊一聊MyBatis的核心概念、Spring相關的核心內容,主要結合源碼理解Spring是如何整合MyBatis的。(結合右側目錄瞭解吧)html
建立SqlSession的工廠java
sql請求的會話,經過SqlSessionFactory獲取。spring
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的代理實例。sql
public interface UserMapper { User findByUserId(Integer userId); }
<mapper namepsace = "com.deepz.mybatis.user.UserMapper"> <select id = "findByUserId" resultType="User"> ... </select> </mapper>
以上就是我們熟悉的MyBatis使用代碼了,一個Mapper接口對應的就有一個XML文件。數據庫
是一種特殊的SpringBean,對應的真實實例是FactoryBean接口中getObject()方法的返回值,用於自定義複雜的Bean生成。緩存
AbstractBeanFactory#doGetBeansession
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#getObjectForBeanInstancemybatis
// 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的真實實例app
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 } }
在SpringBean的生命週期中,Bean的初始化環節Spring會調用AbstractAutowireCapableBeanFactory#invokeInitMethods() 回調實現了InitializingBean接口的Bean的InitializingBean#afterPropertiesSet()less
關於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繼承了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; }
這個接口是幹什麼的?它有本身的抽象方法,在Spring應用上下文生命週期的"invokeBeanFactoryPostProcessors"環節會回調相關方法。(這裏不是重點不過多聊)
咱們此次關注的重點是它的子接口-BeanDefinitionRegistryPostProcessor,Spring在上述環節中對該接口作了特殊處理,回調了它的獨有方法。(如上面的代碼段所示)
AbstractApplicationContext#refresh()方法即爲Spring應用上下文的生命週期的刷新入口,能夠看到在比較前置的環節就會先處理BeanFactoryProcessor類型的Bean。
追進源碼後發現最後會在PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法中循環處理全部BeanFactoryProcessor,其中若是是BeanDefinitionRegistryPostProcessor則會回調BeanDefinitionRegistryPostProcessor的獨有方法,值得注意的是該方法的入參是一個BeanDefinitionRegistry。
(關於BeanDefinitionRegistry有必要在單獨段落作介紹,因此請先移步下一段落吧)
在瞭解了這幾個接口後,咱們彙總一下就明白了BeanDefinitionRegistryPostProcessor的能力了。它能夠在Spring應用上下文前期先被實例化且回調相關接口方法,向Spring容器註冊或移除BeanDefinition甚至能夠在get出一個BeanDefinition後直接修改內部屬性,讓Bean變成你想要的模樣。
BeanDefinition是Spring實例化一個Bean的依據,它的內部維護了一個Bean的各類屬性,如BeanClass、BeanName、lazyInit(是否懶加載)、primary、scope等等。
而Spring在實例化一個Bean的時候須要先從一個Map中根據beanName獲取到對應的BeanDefinition才能去按需實例化SpringBean,以下。
DefaultListableBeanFactory#getBeanDefinition()
看看這個Map的定義
相信你們也猜到了,上面提到的就是維護這個Map的。
BeanDefinitionRegistry接口的方法以下:
能夠看到該接口的能力就是維護Spring容器中的BeanDefinition。
有了上面的回顧後,關於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這個Jar的項目結構了,應該能看到你們使用的時候熟悉的組件吧。如MapperScan註解。眼尖的夥伴應該能看到幾個類:SqlSessionFactoryBean、MapperFactoryBean、MapperScannerConfigurer、SpringManagedTransaction,這幾個類將會是接下來探討的重點。
剛剛咱們聊到了,首先須要幹掉的就是SqlSessionFactory的低端維護方式。咱們先看看SqlSessionFactory這個類的繼承樹。
能夠看到它實現了FactoryBean,這就意味着它必定有一個getObject()方法,用於返回交給Spring管理的實例;
它還實現了InitializingBean,這就意味着在這個Bean的初始化時,Spring會回調它的afterPropertiesSet()方法。(Spring事件本次不討論)
咱們先看看,這個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的了。
首先看看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代碼 -.- ......
接下來討論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、掃描、配置]。
老規矩,看看幾個核心接口的方法都作了什麼。
/** * {@inheritDoc} */ @Override public void afterPropertiesSet() throws Exception { // 幾乎啥也沒幹,就斷言了個掃描包路徑不爲空。 下一個! notNull(this.basePackage, "Property 'basePackage' is required"); }
能夠看到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); } }
回到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; }
以下是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整合了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> <!-- 省略 -->
總結下本文的內容吧:
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管理的目標。
原創不易,但願對你有幫助。歡迎多多指導和討論。