当前位置: 首页>资讯 >

每日简讯:【Spring源码】- 08 扩展点之mybatis集成

来源: 腾讯云 | 时间: 2023-03-28 14:24:49 |

概述

mybatis将与spring集成的代码拆分到了mybatis-spring模块,避免mybatisspring之间的耦合,如果你只需要纯粹的使用mybatis api,就避免了必须将spring依赖也耦合进来的问题。mybatis使用中一般是将Sql语句写在xml文件中,为方便操作,我们会创建一个Mapper接口文件进行映射,mybatis提供了采用动态代理方式对Mapper接口类进行包装,这样我们就可以像使用普通对象一样执行各种方法调用。

mybatisspring集成的一个核心任务就是将这些动态代理包装的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这个就是继承之前介绍过的SpringClassPathBeanDefinitionScanner类扫描器进行了扩展,它可以实现将包路径下至少含有一个方法的接口类注册到IoC中。

这里有个问题:注册进入的BeanDefinitionbeanClass指向的都是接口,到后续创建对象时会存在问题,接口是没法创建实例的。所以,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),偷偷的将BeanDefinitionbeanClass替换成了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集合注入;为了解决接口不能创建对象问题,再注入后又将BeanDefinitionbeanClass替换成FactoryBean的实现类:MapperFactoryBean,在该实现类中通过mybatis apiSqlSession#getMapper()获取到Mapper接口的动态代理类

扩展点引入

通过MapperScannerConfigurer,解决了如何将Mapper接口类注入到IoC容器的问题,现在还有另外一个问题,这个扩展点只有注册到Spring中才会起作用,那又如何将其注册到Spring中呢?

方式一:最直接方式就是直接创建MapperScannerConfigurer类型的Bean实例,比如:

        

这种方式是最简单直接的,但是使用角度来说不方便,所以,mybatis-spring-1.2新增了两种方式:标签方式和@MapperScan注解方式。

首先来看下标签方式,添加mybatisschema,然后就可以使用:

                                                                    

后台处理类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 annotationClass = (Class) 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 mapperFactoryBeanClass = (Class) 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 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注解将第三方集成进来,你还需要初始化一些依赖对象,比如这里的DataSourceSqlSessionFactory等。当一个项目集成了很多第三方模块时,每个模块都这样搞一下,配置的工作量就大了,比如最常使用的ssm集成配,传统Spring集成要搞一大堆配置。

所以,SpringBoot提出了一个比较优秀的思想:自动装配。需要什么模块直接把依赖添加进来,自动完成装配,对于个性化可以在属性文件中进行配置,从使用角度来说,即插即用,不需要有太多的编码。第三方程序和spring就像完全融入一体一样,简化项目构建时集成成本,也降低项目配置的复杂性,所以SpringBoot会被越来越多的项目所采用,进而也推动微服务的兴起。

SpringBoot中使用mybatis,直接依赖mybatis-spring-boot-starter,它会把mybatismybatis-springmybatis-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)引入,然后在AutoConfiguredMapperScannerRegistrarBeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class)这句又是动态注入MapperScannerConfigurer。不过,主动装配配置类中,还会把相关的依赖也一起创建、初始化,比如:SqlSessionFactorySqlSessionTemplate

@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

总结

从上面来看,mybatisspring集成的关键的是将mybatis-spring模块下MapperScannerConfigurer集成进来,因为,它是一个BeanDefinitionRegistryPostProcessor类型的扩展,内部通过自定义scanner扫描Mapper接口自动注册到IoC容器中,这一点在各种集成方式中是统一一样的。不同点在于:MapperScannerConfigurer扩展类是如何被引入的。传统的Spring方式通过@Mapper注解或自定义标签实现,但是对于一些依赖对象还是需要手工创建,比较繁琐;而SpringBoot利用自动装配,让第三方模块集成变成了一个插件,即插即用,无需太多编码。

分析了mybatis集成方式,从中也学习了如何利用Spring的各种扩展点进行定制,更重要的是也为我们开发自己模块和Spring集成提供了思路。

关键词:

 

热文推荐

每日简讯:【Spring源码】- 08 扩展点之mybatis集成

mybatis将与spring集成的代码拆分到了mybatis-spring模块,避免mybatis与spring之间的耦合,如果你只需要纯粹的使用mybati

2023-03-28

世界今日讯!和讯个股快报:2023年03月28日 科新发展(600234),MACD指标出现“白龙出水”信号

“白龙出水”指的是“指数平滑异同移动平均线”中的DIF曲线(因此线在电脑屏幕上为白色曲线),0轴为海平面,由于股价长期低迷致使DIF犹如一条沉

2023-03-28

全球要闻:2023年博鳌论坛召开!50多个国家和地区的两千多名代表出席

新京报贝壳财经讯(记者胡萌)3月28日,博鳌亚洲论坛2023年新闻发布会暨旗舰报告发布会举行。博鳌亚洲论坛秘书长、中国外交部原副部长李保东介绍

2023-03-28

BioWare调兵遣将会战《龙腾世纪》

BioWare调兵遣将会战《龙腾世纪》

2023-03-28

海口房产将来6年前景怎么样?2023滨江度假城是否更值得投资!

海口房产将来6年前景怎么样?2023滨江度假城是否更值得投资!,海口买房投资分析2023,滨江度假城怎样,滨江度假城楼盘地址,2023滨江度假城全

2023-03-28

小商品城:3月27日融资买入6120.95万元,融资融券余额7.96亿元

3月27日,小商品城(600415)融资买入6120 95万元,融资偿还4860 49万元,融资净买入1260 47万元,融资余额7 78亿元。

2023-03-28

天天观热点:企业对劳动仲裁不执行怎么办?你们有办法吗?

仲裁委员会在收到申诉人的申诉书后,应在填写《立案审批表》之日起7日内决定是否受理。仲裁委员会决定不予立案的,将在作出决定之日起7日内制

2023-03-28

世界热门:防火板是什么材质的_防火板是什么材质

1、防火板是原纸(钛粉纸、牛皮纸)经过三聚氰胺与酚醛树脂的浸渍工艺,经过高温高压环境制成。2、防火板分类:1、矿棉板、

2023-03-28

再降500万!梅西最新身价仅4500万,排名跌出前100位

再降500万!梅西最新身价仅4500万,排名跌出前100位

2023-03-27

吉林高速:控股子公司中标约5.26亿元工程施工项目 当前聚焦

吉林高速晚间公告,公司的控股子公司吉林省科维交通工程有限公司通过公开投标的形式,中标“延吉至长春高速公路大蒲柴河至烟筒山段、烟筒山至

2023-03-27

马云回国了!首谈ChatGPT:人不应被人工智能所控制_环球速看

马云回国了,第一站是云谷学校。3月27日,多家媒体报道阿里巴巴创始人马云日前已回国。而据“云谷教育”公众号消息,马云今日下午来到杭州云谷

2023-03-27

当前热文:微型电动车品牌加入价格战 电动汽车需求与产业链价格

从年初特斯拉宣布降价开始,到近日湖北开启“史上最强”购车补贴季,截至目前,已有近100款车型加入价格战,燃油车、新能源汽车,自主、合资、

2023-03-27

大摩:重申中国移动(00941)“增持”评级 目标价升至75港元 新要闻

大摩:重申中国移动(00941)“增持”评级目标价升至75港元,大摩,中移动,中国移动

2023-03-27

当前简讯:长江师范学院怎么样环境_长江师范学院怎么样

1、还是不错的,全部新生都在新校区,我就是这所学校的,校风很好管理也很好!艺术类,文学与新闻学院,政管院都还不错!如果你

2023-03-27

全球要闻:鄂州花湖机场再开两条客运航线

鄂州花湖机场再开两条客运航线---湖北日报讯(记者夏中华、通讯员何旭峰)3月26日零时起至10月28日,全国民航开始执行2023年夏秋航季(以下简

2023-03-27

产业有序转移,为什么这一步棋广东必须走? 世界头条

产业有序转移,为什么这一步棋广东必须走?,广东,粤东,粤西,清远,广州,粤北地区,若干措施

2023-03-27

国家中小企业公共服务示范平台数据库上线,畅捷通成小微企业数智转型首选!

近日,国家中小企业公共服务示范平台信息数据库正式上线,并公布了767家国家中小企业公共服务示范平台的完整版名单,畅捷通位居前列。工信部此前

2023-03-27

【全球热闻】中铝集团董事长段向东会见力拓集团首席执行官石道成

据中国铝业集团消息,3月24日,中铝集团董事长段向东在集团总部会见力拓集团首席执行官石道成。双方就一步发挥战略协同效应,

2023-03-27

热风机不出热风怎么回事?|全球速递

可能原因如下:电热丝断路或烧毁。检查,若断开一点,可以用黄铜片将断头接好并卡紧。若损坏严重,应按原规格更换。4、温控器有问题。检查金属

2023-03-27

爱太痛

1、《爱太痛》是吴克群创作的一首歌曲,由吴克群作词作曲并演唱。2、收录于他2008年发行的专辑《MagiKGreatHi

2023-03-27

资讯

每日简讯:【Spring源码】- 08 扩展点之mybatis集成

mybatis将与spring集成的代码拆分到了mybatis-spring模块,避免mybatis与spring之间的耦合,如果你只需要纯粹的使用mybati

2023-03-28     
世界今日讯!和讯个股快报:2023年03月28日 科新发展(600234),MACD指标出现“白龙出水”信号

“白龙出水”指的是“指数平滑异同移动平均线”中的DIF曲线(因此线在电脑屏幕上为白色曲线),0轴为海平面,由于股价长期低迷致使DIF犹如一条沉

2023-03-28     
全球要闻:2023年博鳌论坛召开!50多个国家和地区的两千多名代表出席

新京报贝壳财经讯(记者胡萌)3月28日,博鳌亚洲论坛2023年新闻发布会暨旗舰报告发布会举行。博鳌亚洲论坛秘书长、中国外交部原副部长李保东介绍

2023-03-28     
BioWare调兵遣将会战《龙腾世纪》

BioWare调兵遣将会战《龙腾世纪》

2023-03-28     
海口房产将来6年前景怎么样?2023滨江度假城是否更值得投资!

海口房产将来6年前景怎么样?2023滨江度假城是否更值得投资!,海口买房投资分析2023,滨江度假城怎样,滨江度假城楼盘地址,2023滨江度假城全

2023-03-28     
小商品城:3月27日融资买入6120.95万元,融资融券余额7.96亿元

3月27日,小商品城(600415)融资买入6120 95万元,融资偿还4860 49万元,融资净买入1260 47万元,融资余额7 78亿元。

2023-03-28