世界訊息:【Spring源碼】- 08 擴展點之mybatis集成
2023-03-29 11:15:51 來源:騰訊云 小 中
概述
mybatis
將與spring
集成的代碼拆分到了mybatis-spring
模塊,避免mybatis
與spring
之間的耦合,如果你只需要純粹的使用mybatis api
,就避免了必須將spring
依賴也耦合進來的問題。mybatis
使用中一般是將Sql
語句寫在xml
文件中,為方便操作,我們會創建一個Mapper
接口文件進行映射,mybatis
提供了采用動態代理方式對Mapper
接口類進行包裝,這樣我們就可以像使用普通對象一樣執行各種方法調用。
mybatis
和spring
集成的一個核心任務就是將這些動態代理包裝的Mapper
對象注入到IoC
容器中,這樣其它Bean
就可以方便的使用如@Autowired
等方式進行依賴注入。
【資料圖】
MapperScannerConfigurer
需要將mybatis
生成的動態代理對象注入到IoC
容器中,自然我們想到之前的BeanFactoryPostProcessor
的子類BeanDefinitionRegistryPostProcessor
這個擴展類。MapperScannerConfigurer
就是實現了BeanDefinitionRegistryPostProcessor
接口,然后在該接口中通過類掃描器scanner
進行掃描注冊。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { 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);//指定引用的SqlSessionFactory scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); //basePackage指定掃描Mapper接口包路徑 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}
ClassPathMapperScanner
這個就是繼承之前介紹過的Spring
中ClassPathBeanDefinitionScanner
類掃描器進行了擴展,它可以實現將包路徑下至少含有一個方法的接口類注冊到IoC
中。
這里有個問題:注冊進入的BeanDefinition
中beanClass
指向的都是接口,到后續創建對象時會存在問題,接口是沒法創建實例的。所以,ClassPathMapperScanner
掃描器在注冊完成后,又會對BeanDefinition
進行處理。處理邏輯位于ClassPathMapperScanner#processBeanDefinitions()
方法中,其核心邏輯見下:
private void processBeanDefinitions(Set beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 definition.setBeanClass(this.mapperFactoryBeanClass); ... }}
其中最重要的一條語句:definition.setBeanClass(this.mapperFactoryBeanClass)
,偷偷的將BeanDefinition
的beanClass
替換成了MapperFactoryBean
,而不再指向Mapper
接口類。同時將Mapper
接口類作為參數傳入到了MapperFactoryBean
中,即調用下面構造方法:
public MapperFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface;}
MapperFactoryBean
實現了FactoryBean
接口,這樣實際上它是通過getObject()
方法獲取到對象然后注入到IoC
容器中。而在getObject()方法中,我們就可以使用mybatis api
獲取到Mapper
接口類的動態代理對象:SqlSession#getMapper()
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface);}
上面我們分析了如何將Mapper
接口類注入到IoC
容器中的實現思路,現在總結下主要有:
BeanDefinitionRegistryPostProcessor
接口實現擴展,然后動態向IoC
容器中注入Bean
;在注入時,會使用到ClassPathMapperScanner
類掃描器將所有的Mapper
接口類解析成BeanDefinition
集合注入;為了解決接口不能創建對象問題,再注入后又將BeanDefinition
的beanClass
替換成FactoryBean
的實現類:MapperFactoryBean
,在該實現類中通過mybatis api
:SqlSession#getMapper()
獲取到Mapper
接口的動態代理類擴展點引入
通過MapperScannerConfigurer
,解決了如何將Mapper
接口類注入到IoC
容器的問題,現在還有另外一個問題,這個擴展點只有注冊到Spring
中才會起作用,那又如何將其注冊到Spring
中呢?
方式一:最直接方式就是直接創建MapperScannerConfigurer
類型的Bean
實例,比如:
這種方式是最簡單直接的,但是使用角度來說不方便,所以,mybatis-spring-1.2
新增了兩種方式:
標簽方式和@MapperScan
注解方式。
首先來看下
標簽方式,添加mybatis
的schema
,然后就可以使用
:
后臺處理類NamespaceHandler
給
標簽注冊解析器MapperScannerBeanDefinitionParser
:
public class NamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser()); }}
再看下MapperScannerBeanDefinitionParser
解析器:
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); builder.addPropertyValue("processPropertyPlaceHolders", true); try { String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION); if (StringUtils.hasText(annotationClassName)) { @SuppressWarnings("unchecked") Class extends Annotation> annotationClass = (Class extends Annotation>) classLoader .loadClass(annotationClassName); builder.addPropertyValue("annotationClass", annotationClass); } String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE); if (StringUtils.hasText(markerInterfaceClassName)) { Class> markerInterface = classLoader.loadClass(markerInterfaceClassName); builder.addPropertyValue("markerInterface", markerInterface); } String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR); if (StringUtils.hasText(nameGeneratorClassName)) { Class> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName); BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class); builder.addPropertyValue("nameGenerator", nameGenerator); } String mapperFactoryBeanClassName = element.getAttribute(ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS); if (StringUtils.hasText(mapperFactoryBeanClassName)) { @SuppressWarnings("unchecked") Class extends MapperFactoryBean> mapperFactoryBeanClass = (Class extends MapperFactoryBean>) classLoader .loadClass(mapperFactoryBeanClassName); builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } } catch (Exception ex) { XmlReaderContext readerContext = parserContext.getReaderContext(); readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause()); } builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF)); builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF)); builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION)); builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE)); return builder.getBeanDefinition(); }
最關鍵的就是第一句BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
,又是將MapperScannerConfigurer
動態注入到Spring
中,下面一堆都是解析標簽屬性進行依賴注入。
再來看下@MapperScan
注解方式,如:@MapperScan(basePackages = "org.simon.demo01.mapper")
:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(MapperScannerRegistrar.class)public @interface MapperScan {}
@MapperScan
注解上面使用了使用了一種非常常見的擴展方式:@Import
擴展。通過@Import
注解,引入了MapperScannerRegistrar
,它是ImportBeanDefinitionRegistrar
類型,通常和@Import
注解組合使用,實現動態注入功能:
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //獲取注解上屬性 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { //創建一個MapperScannerConfigurer的BeanDefinition BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); /** * 下面就是解析注解屬性值,通過PropertyValue方式進行依賴注入到Bean中 */ builder.addPropertyValue("processPropertyPlaceHolders", true); Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { builder.addPropertyValue("annotationClass", annotationClass); } Class> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { builder.addPropertyValue("markerInterface", markerInterface); } ...//各種依賴注入 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); //將生成的BeanDefinition注冊到IoC中 registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}
方法中同樣有BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class)
這句,動態的將MapperScannerConfigurer
注入到Spring
中,然后是一堆的解析注解屬性進行依賴注入,這樣通過@Import
+ImportBeanDefinitionRegistrar
動態注入,就實現了將MapperScannerConfigurer
擴展點注冊到Spring
中。
SpringBoot自動裝配
不論是通過
標簽方式,還是@MapperScan
注解方式,這些是常規的第三方模塊與Spring
進行集成方式。這種集成方式比較繁瑣的是:你不光要通過
或@MapperScan
注解將第三方集成進來,你還需要初始化一些依賴對象,比如這里的DataSource
、SqlSessionFactory
等。當一個項目集成了很多第三方模塊時,每個模塊都這樣搞一下,配置的工作量就大了,比如最常使用的ssm
集成配,傳統Spring
集成要搞一大堆配置。
所以,SpringBoot
提出了一個比較優秀的思想:自動裝配。需要什么模塊直接把依賴添加進來,自動完成裝配,對于個性化可以在屬性文件中進行配置,從使用角度來說,即插即用,不需要有太多的編碼。第三方程序和spring
就像完全融入一體一樣,簡化項目構建時集成成本,也降低項目配置的復雜性,所以SpringBoot
會被越來越多的項目所采用,進而也推動微服務的興起。
在SpringBoot
中使用mybatis
,直接依賴mybatis-spring-boot-starter
,它會把mybatis
、mybatis-spring
和mybatis-spring-boot-autoconfigure
三個依賴包都添加進來。前面兩個依賴包好理解,這里關鍵是第三個依賴包,就是通過它實現了mybatis
自動裝配功能。下面我們來看下SpringBoot
是如何實現mybatis
的主動裝配。
1、首先,定義一個mybatis
主動裝配配置類,如下:
@org.springframework.context.annotation.Configuration@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })@ConditionalOnSingleCandidate(DataSource.class)@EnableConfigurationProperties(MybatisProperties.class)@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })public class MybatisAutoConfiguration implements InitializingBean { public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider, ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider databaseIdProvider, ObjectProvider> configurationCustomizersProvider) { ... } @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } applyConfiguration(factory); ...//省略一堆配置 if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } return factory.getObject(); } @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } } public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!AutoConfigurationPackages.has(this.beanFactory)) { logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); return; } logger.debug("Searching for mappers annotated with @Mapper"); List packages = AutoConfigurationPackages.get(this.beanFactory); if (logger.isDebugEnabled()) { packages.forEach(pkg -> logger.debug("Using auto-configuration base package "{}"", pkg)); } BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); builder.addPropertyValue("annotationClass", Mapper.class); builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); Stream.of(beanWrapper.getPropertyDescriptors()) .filter(x -> x.getName().equals("lazyInitialization")).findAny() .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}")); registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } } @org.springframework.context.annotation.Configuration @Import(AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { }}
這里主要利用MapperScannerRegistrarNotFoundConfiguration
類上的@Import(AutoConfiguredMapperScannerRegistrar.class)
引入,然后在AutoConfiguredMapperScannerRegistrar
中BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class)
這句又是動態注入MapperScannerConfigurer
。不過,主動裝配配置類中,還會把相關的依賴也一起創建、初始化,比如:SqlSessionFactory
、SqlSessionTemplate
。
@EnableConfigurationProperties(MybatisProperties.class)
把mybatis
相關配置引入進來,這樣在創建、初始化過程中的定制需求就可以通過配置修改。
2、有了這個主動裝配配置類還不行,下一步就是看如何讓主動裝配配置類生效。SpringBoot提供了SpringFactoriesLoader
工廠加載機制,類似于JDK
中的SPI
機制,實現將模塊META-INF/spring.factories
文件中配置注入到Spring
容器中。mybatis-spring-boot-autoconfigure
模塊下META-INF/spring.factories
文件中就有MybatisAutoConfiguration
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
3、使用時依賴添加進來,配置下屬性,就可以直接使用,基本不再需要編碼:
mybatis.mapper-locations: classpath:mapper/*Mapper.xmlmybatis.type-aliases-package: com.example.demo.entity
總結
從上面來看,mybatis
和spring
集成的關鍵的是將mybatis-spring
模塊下MapperScannerConfigurer
集成進來,因為,它是一個BeanDefinitionRegistryPostProcessor
類型的擴展,內部通過自定義scanner
掃描Mapper
接口自動注冊到IoC
容器中,這一點在各種集成方式中是統一一樣的。不同點在于:MapperScannerConfigurer
擴展類是如何被引入的。傳統的Spring
方式通過@Mapper
注解或
自定義標簽實現,但是對于一些依賴對象還是需要手工創建,比較繁瑣;而SpringBoot
利用自動裝配,讓第三方模塊集成變成了一個插件,即插即用,無需太多編碼。
分析了mybatis
集成方式,從中也學習了如何利用Spring
的各種擴展點進行定制,更重要的是也為我們開發自己模塊和Spring
集成提供了思路。
關鍵詞:
相關文章
- 世界訊息:【Spring源碼】- 08 擴展點之mybatis集成
- 當前快報:鄭州市房協出臺房企信用激勵措施:優先推薦AAA級房企參與保障性住房建設
- 萬達信息:公司已形成數百個人工智能行業模型
- 焦點熱議:工信部:1-2月電子信息制造業生產規模同比小幅收縮
- 世界微速訊:威馬汽車被凍結40億股權
- 周小川:理論上碳減排工具有最優選擇,我們需要尋找最優解
- 王傳福:比亞迪一季度銷量仍會同比增長80%以上
- 國家發改委產業司召開推動農機裝備產業高質量發展座談會_當前信息
- 已有超30城調整首套房貸利率下限至4%以下
- 當前快看:國家發改委趙辰昕:支持企業綠色轉型,企業也需實現更大的發展
- 江鹽集團、柏誠股份申購解讀,3月29日打新指南_每日關注
- 資本血腥——中國起跳?|熱門
- 【全球時快訊】煤炭股弱勢原因?
- 當前關注:半導體設備板塊快速走高 華峰測控大漲9%
- 氫能源概念股持續走強 致遠新能20CM漲停:每日頭條
- 國家藥監局:繼續加強醫療器械網絡交易服務第三方平臺合規建設|環球快看點
- 當前短訊!王傳福:比亞迪一季度銷量仍會同比增長80%以上
- 河北:到2025年規模以上屠宰企業產能占比達90%以上
- 剪彩花球圖片_剪彩花球制作圖解
- 環球熱點!新型城鎮化概念股早盤走低
- 世界速讀:海南板塊異動拉升 海南瑞澤漲停
- 達剛控股于陜西投資設立環境科技新公司-每日快看
- 工信部等:到2025年“百億龍頭、千億集群、萬億產業”的地方特色食品發展格局基本形成-滾動
- 國產軟件股震蕩走低-今日播報
- 汽車零部件板塊震蕩拉升-當前通訊
- 中物聯:1-2月份我國社會物流總額53.5萬億元 同比增長2.9%
- 焦點日報:中物聯:一季度社會物流總額有望實現5%以上的增長水平
- 湖南鋪排324個省重點建設項目 總投資超2萬億元-世界速看料
- 世界快資訊:一份量化基金經理名單
- 歐盟“糾偏”2035年“禁燃”協議 甲醇類eFuels合成燃料或成新風口:每日視訊
熱文推薦
排行推薦

世界微速訊:威馬汽車被凍結40億股權

周小川:理論上碳減排工具有最優選擇,我們需要尋找最優解

王傳福:比亞迪一季度銷量仍會同比增長80%以上
