<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring 如何整合Mybatis,源碼不難嘛!

          共 12543字,需瀏覽 26分鐘

           ·

          2020-10-18 23:05

          Java技術(shù)棧

          www.javastack.cn

          關(guān)注閱讀更多優(yōu)質(zhì)文章



          Spring整合Mybatis會(huì)進(jìn)行如下的配置(條條大路通羅馬,方式不唯一)。

          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。

          Amazing!!!

          這背后到底發(fā)生了什么?

          還要從MapperScannerConfigurer 和SqlSessionFactoryBean 著手。

          MapperScannerConfigurer

          類(lèi)注釋

          • beanDefinitionRegistryPostProcessor從 base package遞歸搜索接口,將它們注冊(cè)為MapperFactoryBean。注意接口必須包含至少一個(gè)方法,其實(shí)現(xiàn)類(lèi)將被忽略。

          • 1.0.1以前是對(duì)BeanFactoryPostProcessor進(jìn)行擴(kuò)展,1.0.2以后是對(duì) BeanDefinitionRegistryPostProcessor進(jìn)行擴(kuò)展,具體原因請(qǐng)查閱https://jira.springsource.org/browse/SPR-8269

          • basePackage可以配置多個(gè),使用逗號(hào)或者分號(hào)分割。

          • 通過(guò)annotationClass或markerInterface,可以設(shè)置指定掃描的接口。默認(rèn)情況下這個(gè)2個(gè)屬性為空,basePackage下的所有接口將被掃描。

          • MapperScannerConfigurer為它創(chuàng)建的bean自動(dòng)注入SqlSessionFactory或SqlSessionTemplate如果存在多個(gè)SqlSessionFactory,需要設(shè)置sqlSessionFactoryBeanName或sqlSessionTemplateBeanName來(lái)指定具體注入的sqlSessionFactory或sqlSessionTemplate。

          • 不能傳入有占位符的對(duì)象(例如:包含數(shù)據(jù)庫(kù)的用戶(hù)名和密碼占位符的對(duì)象)。可以使用beanName,將實(shí)際的對(duì)象創(chuàng)建推遲到所有占位符替換完成后。注意MapperScannerConfigurer支持它自己的屬性使用占位符,使用${property}這個(gè)種格式。

          類(lèi)圖找關(guān)鍵方法

          MapperScanConfigurer

          從類(lèi)圖上看MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口。各個(gè)接口具體含義如下:

          • ApplicationContextAware:當(dāng)spring容器初始化后,會(huì)自動(dòng)注入ApplicationContext

          • BeanNameAware :設(shè)置當(dāng)前Bean在Spring中的名字

          • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的時(shí)候會(huì)執(zhí)行

          • BeanDefinitionRegistryPostProcessor:對(duì)BeanFactoryPostProcessor的擴(kuò)展,允許在BeanFactoryPostProcessor執(zhí)行前注冊(cè)多個(gè)bean的定義。需要擴(kuò)展的方法為postProcessBeanDefinitionRegistry。

          查詢(xún),MapperScannerConfigurer的afterPropertiesSet方法如下,無(wú)具體擴(kuò)展信息。

          @Override?
          public?void?afterPropertiesSet()?throws?Exception?{
          notNull(this.basePackage,?"Property?'basePackage'?is?required");?
          }

          結(jié)合MapperScannerConfigurer的注釋與類(lèi)圖分析,確定其核心方法為:postProcessBeanDefinitionRegistry。

          現(xiàn)在都是 Spring Boot 天下了,這個(gè)倉(cāng)庫(kù)推給大家學(xué)習(xí)下:https://github.com/javastacks/spring-boot-best-practice

          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.設(shè)置過(guò)濾器
          ??scanner.registerFilters();
          ??//3.掃描java文件
          ??scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,?
          ??????????ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
          }

          從源碼中看到除了processPropertyPlaceHolders外,其他工作都委托了ClassPathMapperScanner。關(guān)注公眾號(hào)Java技術(shù)棧回復(fù)spring獲取一份完整Spring系列教程。

          processPropertyPlaceHolders處理占位符

          之前說(shuō)BeanDefinitionRegistryPostProcessor在BeanFactoryPostProcessor執(zhí)行前調(diào)用,

          這就意味著Spring處理占位符的類(lèi)PropertyResourceConfigurer還沒(méi)有執(zhí)行!

          推薦閱讀:Spring import配置文件使用占位符

          那MapperScannerConfigurer是如何支撐自己的屬性使用占位符的呢?這一切的答案都在processPropertyPlaceHolders這個(gè)方法中。

          private?void?processPropertyPlaceHolders()?{
          ??Map?prcs?=
          ???????applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
          ??if?(!prcs.isEmpty()?&&?applicationContext?
          ??????????????????????instanceof?GenericApplicationContext)?{
          ????BeanDefinition?mapperScannerBean?=?
          ????????????((GenericApplicationContext)?applicationContext)
          ????????????????????????.getBeanFactory().getBeanDefinition(beanName);
          ????//?PropertyResourceConfigurer?沒(méi)有暴露方法直接替換占位符,
          ????//?創(chuàng)建一個(gè)?BeanFactory包含MapperScannerConfigurer
          ????//?然后執(zhí)行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,可以總結(jié) MapperScannerConfigurer支持它自己的屬性使用占位符的方式

          1. 找到所有已經(jīng)注冊(cè)的PropertyResourceConfigurer類(lèi)型的Bean

          2. 使用new DefaultListableBeanFactory()來(lái)模擬Spring環(huán)境,將MapperScannerConfigurer注冊(cè)到這個(gè)BeanFactory中,執(zhí)行BeanFactory的后處理,來(lái)替換占位符。

          ClassPathMapperScanner的registerFilters方法

          MapperScannerConfigurer的類(lèi)注釋中有一條:

          通過(guò)annotationClass或markerInterface,可以設(shè)置指定掃描的接口,默認(rèn)情況下這個(gè)2個(gè)屬性為空,basePackage下的所有接口將被掃描。

          scanner.registerFilters(),就是對(duì)annotationClass和markerInterface的設(shè)置。

          public?void?registerFilters()?{
          ??boolean?acceptAllInterfaces?=?true;

          ??//?如果指定了annotationClass,
          ??if?(this.annotationClass?!=?null)?{
          ????addIncludeFilter(new?AnnotationTypeFilter(this.annotationClass));
          ????acceptAllInterfaces?=?false;
          ??}
          ??//?重寫(xiě)AssignableTypeFilter以忽略實(shí)際標(biāo)記接口上的匹配項(xiàng)
          ??if?(this.markerInterface?!=?null)?{
          ????addIncludeFilter(new?AssignableTypeFilter(this.markerInterface)?{
          ??????@Override
          ??????protected?boolean?matchClassName(String?className)?{
          ????????return?false;
          ??????}
          ????});
          ????acceptAllInterfaces?=?false;
          ??}

          ??if?(acceptAllInterfaces)?{
          ????//?默認(rèn)處理所有接口
          ????addIncludeFilter(new?TypeFilter()?{
          ??????@Override
          ??????public?boolean?match(
          ??????MetadataReader?metadataReader,?
          ??????MetadataReaderFactory?metadataReaderFactory)?throws?IOException?{
          ????????return?true;
          ??????}
          ????});
          ??}

          ??//?不包含以package-info結(jié)尾的java文件
          ??//?package-info.java包級(jí)文檔和包級(jí)別注釋
          ??addExcludeFilter(new?TypeFilter()?{
          ????@Override
          ????public?boolean?match(MetadataReader?metadataReader,?
          ????MetadataReaderFactory?metadataReaderFactory)?throws?IOException?{
          ??????String?className?=?metadataReader.getClassMetadata().getClassName();
          ??????return?className.endsWith("package-info");
          ????}
          ??});
          }

          雖然設(shè)置了過(guò)濾器,如何在掃描中起作用就要看scanner.scan方法了。

          ClassPathMapperScanner的scan方法

          public?int?scan(String...?basePackages)?{
          ???int?beanCountAtScanStart?=?this.registry.getBeanDefinitionCount();
          ???doScan(basePackages);
          ???//?注冊(cè)注解配置處理器
          ???if?(this.includeAnnotationConfig)?{
          ??????AnnotationConfigUtils
          ??????????????????.registerAnnotationConfigProcessors(this.registry);
          ???}
          ???return?(this.registry.getBeanDefinitionCount()?-?beanCountAtScanStart);
          }

          doScan方法如下:

          public?Set?doScan(String...?basePackages)?{
          ??Set?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的父類(lèi)ClassPathBeanDefinitionScanner的doScan方法,就是掃描包下的所有java文件轉(zhuǎn)換為BeanDefinition(實(shí)際是ScannedGenericBeanDefinition)。

          processBeanDefinitions就是將之前的BeanDefinition轉(zhuǎn)換為MapperFactoryBean的BeanDefinition。

          至于過(guò)濾器如何生效(即annotationClass或markerInterface)呢?關(guān)注公眾號(hào)Java技術(shù)棧回復(fù)spring獲取一份完整Spring系列教程。

          我一路追蹤源碼

          終于在ClassPathScanningCandidateComponentProvider的isCandidateComponent找到了對(duì)過(guò)濾器的處理

          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;
          }

          總結(jié)MapperScannerConfigurer的作用

          MapperScannerConfigurer實(shí)現(xiàn)了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法。

          從指定的 basePackage的目錄遞歸搜索接口,將它們注冊(cè)為MapperFactoryBean。現(xiàn)在都是 Spring Boot 天下了,這個(gè)倉(cāng)庫(kù)推給大家學(xué)習(xí)下:https://github.com/javastacks/spring-boot-best-practice

          SqlSessionFactoryBean

          類(lèi)注釋

          1. 創(chuàng)建Mybatis的SqiSessionFactory,用于Spring上下文中進(jìn)行共享。

          2. SqiSessionFactory可以通過(guò)依賴(lài)注入到與mybatis的daos中。

          3. datasourcetransactionmanager,jtatransactionmanager與sqlsessionfactory想結(jié)合實(shí)現(xiàn)事務(wù)。

          類(lèi)圖找關(guān)鍵方法

          sqlSessionFactoryBean

          SqlSessionFactoryBean實(shí)現(xiàn)了ApplicationListener ,InitializingBean,F(xiàn)actoryBean接口,各個(gè)接口的說(shuō)明如下:

          • ApplicationListener 用于監(jiān)聽(tīng)Spring的事件

          • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的時(shí)候會(huì)執(zhí)行

          • FactoryBean:返回的對(duì)象不是指定類(lèi)的一個(gè)實(shí)例,其返回的是該FactoryBean的getObject方法所返回的對(duì)象

          應(yīng)該重點(diǎn)關(guān)注afterPropertiesSet和getObject的方法。

          關(guān)鍵方法分析

          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看方法名稱(chēng)就知道在這里進(jìn)行了SqlSessionFactory的創(chuàng)建,具體源碼不在贅述。

          getObject方法

          public?SqlSessionFactory?getObject()?throws?Exception?{
          ??if?(this.sqlSessionFactory?==?null)?{
          ????afterPropertiesSet();
          ??}
          ??return?this.sqlSessionFactory;
          }

          總結(jié)SqlSessionFactoryBean

          實(shí)現(xiàn)了InitializingBean的afterPropertiesSet,在其中創(chuàng)建了Mybatis的SqlSessionFactory

          實(shí)現(xiàn)了FactoryBean的getObject 返回創(chuàng)建好的sqlSessionFactory。

          疑問(wèn)

          看完這SqlSessionFactoryBean和MapperScannerConfigurer之后,不知道你是否有疑問(wèn)!一般在Spring中使用Mybatis的方式如下:

          ApplicationContext?context=new?AnnotationConfigApplicationContext();
          UsrMapper??usrMapper=context.getBean("usrMapper");
          實(shí)際上調(diào)用的是
          sqlSession.getMapper(UsrMapper.class);

          SqlSessionFactoryBean創(chuàng)建了Mybatis的SqlSessionFactory。MapperScannerConfigurer將接口轉(zhuǎn)換為了MapperFactoryBean。那又哪里調(diào)用的sqlSession.getMapper(UsrMapper.class)呢???

          MapperFactoryBean是這一切的答案(MapperFactoryBean:注意看我的名字---Mapper的工廠!!)

          MapperFactoryBean說(shuō)明

          類(lèi)注釋

          能夠注入MyBatis映射接口的BeanFactory。它可以設(shè)置SqlSessionFactory或預(yù)配置的SqlSessionTemplate。
          注意這個(gè)工廠僅僅注入接口不注入實(shí)現(xiàn)類(lèi)

          類(lèi)圖找關(guān)鍵方法

          MapperFactoryBean

          看類(lèi)圖,又看到了InitializingBean和FactoryBean!!!

          • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的時(shí)候會(huì)執(zhí)行

          • FactoryBean:返回的對(duì)象不是指定類(lèi)的一個(gè)實(shí)例,其返回的是該FactoryBean的getObject方法所返回的對(duì)象

          再次重點(diǎn)關(guān)注afterPropertiesSet和getObject的實(shí)現(xiàn)!

          關(guān)鍵方法分析

          DaoSupport類(lèi)中有afterPropertiesSet的實(shí)現(xiàn)如下:

          public?final?void?afterPropertiesSet()
          ?????throws?IllegalArgumentException,?BeanInitializationException?{
          ????this.checkDaoConfig();
          ????try?{
          ????????this.initDao();
          ????}?catch?(Exception?var2)?{
          ????????throw
          ?????????new?BeanInitializationException(
          ?????????????????????????????"Initialization?of?DAO?failed",??var2);
          ????}
          }

          initDao是個(gè)空實(shí)現(xiàn),checkDaoConfig在MapperFactoryBean中有實(shí)現(xiàn)如下:

          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();
          ????}
          ??}
          }

          關(guān)鍵的語(yǔ)句是configuration.addMapper(this.mapperInterface),將接口添加到Mybatis的配置中。

          getObject方法超級(jí)簡(jiǎn)單,就是調(diào)用了sqlSession.getMapper(UsrMapper.class);

          public?T?getObject()?throws?Exception?{
          ????return?getSqlSession().getMapper(this.mapperInterface);?
          }

          總結(jié)MapperFactoryBean

          實(shí)現(xiàn)了InitializingBean的afterPropertiesSet方法,在其中將mapper接口設(shè)置到mybatis的配置中。

          實(shí)現(xiàn)了FactoryBean的getObject 方法,調(diào)用了sqlSession.getMapper,返回mapper對(duì)象。

          總結(jié)

          Spring整合Mybatis核心3類(lèi):

          MapperScannerConfigurer

          實(shí)現(xiàn)了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,在其中從指定的 basePackage的目錄遞歸搜索接口,將它們注冊(cè)為MapperFactoryBean類(lèi)型的BeanDefinition

          SqlSessionFactoryBean

          實(shí)現(xiàn)了InitializingBean的afterPropertiesSet,在其中創(chuàng)建了Mybatis的SqlSessionFactory。

          實(shí)現(xiàn)了FactoryBean的getObject 返回創(chuàng)建好的sqlSessionFactory。

          MapperFactoryBean

          實(shí)現(xiàn)了InitializingBean的afterPropertiesSet方法,將mapper接口設(shè)置到mybatis的配置中。

          實(shí)現(xiàn)了FactoryBean的getObject 方法,調(diào)用了sqlSession.getMapper,返回mapper對(duì)象。

          作者:溫安適
          來(lái)源:https://my.oschina.net/floor/blog/3125371





          關(guān)注Java技術(shù)棧看更多干貨



          戳原文,獲取精選面試題!
          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级免费Aa片 | 最新A视频在线观看 | 欧美日日 | 99热99这里只有精品6首页 | 99久久久成人国产精品 |