Spring整合Mybtais會進行以下的配置(條條大路通羅馬,方式不惟一)。java
private static final String ONE_MAPPER_BASE_PACKAGE = "com.XXX.dao.mapper.one"; @Bean public MapperScannerConfigurer oneMapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage(ONE_MAPPER_BASE_PACKAGE); mapperScannerConfigurer. setSqlSessionFactoryBeanName("oneSqlSessionFactoryBean"); return mapperScannerConfigurer; } @Primary @Bean(name="oneSqlSessionFactoryBean") public SqlSessionFactoryBean oneSqlSessionFactoryBean( @Qualifier("oneDataSource") DruidDataSource oneDataSource) { return getSqlSessionFactoryBeanDruid(oneDataSource,ONE_MAPPER_XML); }
短短不到20行代碼,就完成了Spring整合Mybatis。spring
Amazing!!! 這背後到底發生了什麼?sql
還要從MapperScannerConfigurer 和SqlSessionFactoryBean 着手。數據庫
beanDefinitionRegistryPostProcessor從 base package遞歸搜索接口,將它們註冊爲MapperFactoryBean。注意接口必須包含至少一個方法,其實現類將被忽略。session
1.0.1之前是對BeanFactoryPostProcessor進行擴展,1.0.2之後是對 BeanDefinitionRegistryPostProcessor進行擴展,具體緣由請查閱https://jira.springsource.org/browse/SPR-8269mybatis
basePackage能夠配置多個,使用逗號或者分號分割。app
經過annotationClass或markerInterface,能夠設置指定掃描的接口。默認狀況下這個2個屬性爲空,basePackage下的全部接口將被掃描。ide
MapperScannerConfigurer爲它建立的bean自動注入SqlSessionFactory或SqlSessionTemplate若是存在多個SqlSessionFactory,須要設置sqlSessionFactoryBeanName或sqlSessionTemplateBeanName來指定具體注入的sqlSessionFactory或sqlSessionTemplate。post
不能傳入有佔位符的對象(例如: 包含數據庫的用戶名和密碼佔位符的對象)。可使用beanName,將實際的對象建立推遲到全部佔位符替換完成後。注意MapperScannerConfigurer支持它本身的屬性使用佔位符,使用${property}這個種格式。ui
從類圖上看MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口。各個接口具體含義以下:
查詢,MapperScannerConfigurer的afterPropertiesSet方法以下,無具體擴展信息。
@Override public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); }
結合MapperScannerConfigurer的註釋與類圖分析,肯定其核心方法爲:postProcessBeanDefinitionRegistry
@Override public void postProcessBeanDefinitionRegistry( BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { //1. 佔位符屬性處理 processPropertyPlaceHolders(); } 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); //2.設置過濾器 scanner.registerFilters(); //3.掃描java文件 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
從源碼中看到除了processPropertyPlaceHolders外,其餘工做都委託了ClassPathMapperScanner
以前說BeanDefinitionRegistryPostProcessor在BeanFactoryPostProcessor執行前調用,
這就意味着Spring處理佔位符的類PropertyResourceConfigurer尚未執行!
那MapperScannerConfigurer是如何支撐本身的屬性使用佔位符的呢?這一切的答案都在
processPropertyPlaceHolders這個方法中。
private void processPropertyPlaceHolders() { Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) { BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext) .getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer 沒有暴露方法直接替換佔位符, // 建立一個 BeanFactory包含MapperScannerConfigurer // 而後執行BeanFactory後處理便可 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); } }
看完processPropertyPlaceHolders,能夠總結 MapperScannerConfigurer支持它本身的屬性使用佔位符的方式
找到全部已經註冊的PropertyResourceConfigurer類型的Bean
使用new DefaultListableBeanFactory()來模擬Spring環境,將MapperScannerConfigurer註冊到這個BeanFactory中,執行BeanFactory的後處理,來替換佔位符。
MapperScannerConfigurer的類註釋中有一條:
經過annotationClass或markerInterface,能夠設置指定掃描的接口,默認狀況下這個2個屬性爲空,basePackage下的全部接口將被掃描。 scanner.registerFilters(),就是對annotationClass和markerInterface的設置。
public void registerFilters() { boolean acceptAllInterfaces = true; // 若是指定了annotationClass, if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // 重寫AssignableTypeFilter以忽略實際標記接口上的匹配項 if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } if (acceptAllInterfaces) { // 默認處理全部接口 addIncludeFilter(new TypeFilter() { @Override public boolean match( MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }); } // 不包含以package-info結尾的java文件 // package-info.java包級文檔和包級別註釋 addExcludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); } }); }
雖然設置了過濾器,如何在掃描中起做用就要看scanner.scan方法了。
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); // 註冊註解配置處理器 if (this.includeAnnotationConfig) { AnnotationConfigUtils .registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }
doScan方法以下:
public Set<BeanDefinitionHolder> doScan(String... 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; }
位於ClassPathMapperScanner的父類ClassPathBeanDefinitionScanner的doScan方法,就是
掃描包下的全部java文件轉換爲BeanDefinition(實際是ScannedGenericBeanDefinition)。
processBeanDefinitions就是將以前的BeanDefinition轉換爲MapperFactoryBean的BeanDefinition。
至於過濾器如何生效(即annotationClass或markerInterface)呢?我一路追蹤源碼
終於在ClassPathScanningCandidateComponentProvider的isCandidateComponent找到了對過濾器的處理
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return isConditionMatch(metadataReader); } } return false; }
MapperScannerConfigurer實現了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
從指定的 basePackage的目錄遞歸搜索接口,將它們註冊爲MapperFactoryBean
建立Mybatis的SqiSessionFactory,用於Spring上下文中進行共享。
SqiSessionFactory能夠經過依賴注入到與mybatis的daos中。
datasourcetransactionmanager,jtatransactionmanager與sqlsessionfactory想結合實現事務。
SqlSessionFactoryBean實現了ApplicationListener ,InitializingBean,FactoryBean接口,各個接口的說明以下:
應該重點關注afterPropertiesSet和getObject的方法。
afterPropertiesSet方法
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看方法名稱就知道在這裏進行了SqlSessionFactory的建立,具體源碼不在贅述。
getObject方法
public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
實現了InitializingBean的afterPropertiesSet,在其中建立了Mybatis的SqlSessionFactory
實現了FactoryBean的getObject 返回建立好的sqlSessionFactory。
看完這SqlSessionFactoryBean和MapperScannerConfigurer以後,不知道你是否有疑問!通常在Spring中使用Mybatis的方式以下:
ApplicationContext context=new AnnotationConfigApplicationContext(); UsrMapper usrMapper=context.getBean("usrMapper"); 實際上調用的是 sqlSession.getMapper(UsrMapper.class);
SqlSessionFactoryBean建立了Mybatis的SqlSessionFactory。MapperScannerConfigurer將接口轉換爲了MapperFactoryBean。那又哪裏調用的sqlSession.getMapper(UsrMapper.class)呢???
MapperFactoryBean是這一切的答案(MapperFactoryBean:注意看個人名字---Mapper的工廠!!)
可以注入MyBatis映射接口的BeanFactory。它能夠設置SqlSessionFactory或預配置的SqlSessionTemplate。
注意這個工廠僅僅注入接口不注入實現類
看類圖,又看到了InitializingBean和FactoryBean!!!
再次重點關注afterPropertiesSet和getObject的實現!
DaoSupport類中有afterPropertiesSet的實現以下:
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException( "Initialization of DAO failed", var2); } }
initDao是個空實現,checkDaoConfig在MapperFactoryBean中有實現以下:
protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { 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(); } } }
關鍵的語句是configuration.addMapper(this.mapperInterface),將接口添加到Mybatis的配置中。
getObject方法超級簡單,就是調用了sqlSession.getMapper(UsrMapper.class);
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
實現了InitializingBean的afterPropertiesSet方法,在其中將mapper接口設置到mybatis的配置中。
實現了FactoryBean的getObject 方法,調用了sqlSession.getMapper,返回mapper對象。
Spring整合Mybatis核心3類:
MapperScannerConfigurer
實現了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,在其中從指定的 basePackage的目錄遞歸搜索接口,將它們註冊爲MapperFactoryBean類型的BeanDefinition
SqlSessionFactoryBean
實現了InitializingBean的afterPropertiesSet,在其中建立了Mybatis的SqlSessionFactory。
實現了FactoryBean的getObject 返回建立好的sqlSessionFactory。
MapperFactoryBean
實現了InitializingBean的afterPropertiesSet方法,將mapper接口設置到mybatis的配置中。
實現了FactoryBean的getObject 方法,調用了sqlSession.getMapper,返回mapper對象。