mybatis如何与spring的结合
1.前言
在现在的java项目开发中,MyBatis和Spring是两个非常流行的框架。MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。而Spring则是一个广泛使用的开源框架,用于构建企业级应用程序。将这两个框架整合在一起,可以充分利用它们各自的优势,构建出高效、易于维护的数据访问层。
2.整合关键组件与技术
我们在代码的开发过程中,只需要引入对应的jar包的pom文件,并在spring的java文件中引入了@MapperScan注解,并在@MapperScan注解中指定对应的基础包类就可以了。剩下的只需要在基础包下进行创建对应的Mapper接口文件就可以了。
那么问题来了,
1.spring是什么时候进行加载这些接口文件呢
2.通过什么方式进行加载文件呢。
3. 加载的文件都是接口,那如何对这些接口进行初始化呢
针对上面的三个问题,我们来点击进入@MapperScan注解查看一些Mybatis与Spring的之间的“勾当”
2.1@MapperScan注解
下面的代码是MapperScan注解的注解,其中不少的代码因为不涉及此次分析,我都进行了隐藏。在下面的代码中我们可以看到一切的解密应该都在MapperScannerRegistrar这个类中。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};
}
MapperScannerRegistrar类,这个类继承了ImportBeanDefinitionRegistrar这个类,ImportBeanDefinitionRegistrar这个类呢在Spring的启动的过程中,是一个比较重要的类,现在简单的说,ImportBeanDefinitionRegistrar这个类在spring启动的过程中,会进行执行registerBeanDefinitions方法,感兴趣的同学,可以看我前面发表的文章:spring组件动态注册Bean。那么在MapperScannerRegistrar这个类做了什么呢,我觉得最重要的两行代码是:
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
上面两行代码的主要含义是:mybatis组件向spring容器进行注册了一个MapperScannerConfigurer对象。
那么这个MapperScannerConfigurer对象会在spring的容器初始化的时候进行执行一些操作。具体的执行时机,也可以看我前面发表的文章:spring组件动态注册Bean。
MapperScannerRegistrar中的详细代码源码如下:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));}}void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);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);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);}String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");if (StringUtils.hasText(sqlSessionTemplateRef)) {builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));}String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");if (StringUtils.hasText(sqlSessionFactoryRef)) {builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));}List<String> basePackages = new ArrayList();basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));if (basePackages.isEmpty()) {basePackages.add(getDefaultBasePackage(annoMeta));}String lazyInitialization = annoAttrs.getString("lazyInitialization");if (StringUtils.hasText(lazyInitialization)) {builder.addPropertyValue("lazyInitialization", lazyInitialization);}String defaultScope = annoAttrs.getString("defaultScope");if (!"".equals(defaultScope)) {builder.addPropertyValue("defaultScope", defaultScope);}builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}}
2.2 MapperScannerConfigurer类的说明
MapperScannerConfigurer类 实现了BeanDefinitionRegistryPostProcessor类中的postProcessBeanDefinitionRegistry方法,这个方法会在spring启动的过程中进行执行.
在这个方法中,我们可以看到mybatis进行实例化了一个ClassPathMapperScanner(扫描器),这块不知道大家有没有这样的疑问,那就是mybatis为啥要进行实例化出来一个扫描器,为啥不用spring自己的扫描器呢,原理是因为spring的扫描器,mybatis用不了,为啥用不了呢,因为spring的扫描器,扫描的是加了@Component,@ManagedBean,@Name等注解的非接口类。这点针对mybatis而言是不适用的。所以mybatis自己创建了一个新的扫描器:ClassPathMapperScanner
MapperScannerConfigurer类中的核心代码块:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {this.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);scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);if (StringUtils.hasText(this.lazyInitialization)) {scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));}if (StringUtils.hasText(this.defaultScope)) {scanner.setDefaultScope(this.defaultScope);}scanner.registerFilters();scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));}
}
2.3 ClassPathMapperScanner类
ClassPathMapperScanner这个类是继承于spring的内置的扫描器ClassPathBeanDefinitionScanner,关于spring的ClassPathBeanDefinitionScanner类中有两个比较重要的属性includeFilters和excludeFilters这两个属性都为List集合,从字面意思上我们就可以看出来,includeFilters这个Filter的集合就是决定哪些类能被扫描到,excludeFilters这的Filter的集合来决定哪些类不能被扫描到。
2.3.1 扫描过滤器设置
我们从构造函数中可以看到,ClassPathMapperScanner类在进行初始化的时候,useDefaultFilters属性传入的是false,那么可以证明ClassPathMapperScanner将不使用spring默认的includeFilters和excludeFilters。
在上面MapperScannerConfigurer类中,我们看到了 scanner.registerFilters();这行代码,说明在ClassPathMapperScanner初始化完成之后,调用了registerFilters方法。有自己的includeFilters和excludeFilters。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner{public ClassPathMapperScanner(BeanDefinitionRegistry registry) {super(registry, false);//进行调用父类的构造函数,/**** public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {* this(registry, useDefaultFilters, getOrCreateEnvironment(registry));* } ****/}/**** MapperScan注解的简单示例:** @MapperScan(basePackages = "xxx.xxx.xxx",* annotationClass = A.class,* markerInterface = =TMappper.class)***/public void registerFilters() {boolean acceptAllInterfaces = true;//代表着接口上只有添加了A注解的类 才能被扫描到if (this.annotationClass != null) {this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}//设置了markerInterface代表着所有的接口都不会进行扫描,因为下面matchClassName直接返回falseif (this.markerInterface != null) {this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {protected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}//为true的时候 代表着包下面的所有接口都会进行扫描if (acceptAllInterfaces) {this.addIncludeFilter((metadataReader, metadataReaderFactory) -> {return true;});}//排除了类名以package-info结尾的类 这种情况感觉很少会用到this.addExcludeFilter((metadataReader, metadataReaderFactory) -> {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");});}
}
2.3.2 doScan方法的执行
在给ClassPathMapperScanner类初始化完成之后,就会执行ClassPathMapperScanner的scan方法进行扫描类的信息,此处要注意调用的是ClassPathMapperScanner的doScan方法。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {//调用父类的doscan方法,进行扫描 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> {return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";});} else {//mybatis扫描的的核心this.processBeanDefinitions(beanDefinitions);}return beanDefinitions;}
2.3.3 ClassPathMapperScanner#processBeanDefinitions方法
这个方法主要是针对扫描出来的BeanDefinition对象,进行一个“狸猫换太子”的思想,其主要核心代码如下:
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;for (BeanDefinitionHolder holder : beanDefinitions) {definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59definition.setBeanClass(this.mapperFactoryBeanClass);
}
这个代表着什么意思呢,举个例子说明一下:
假如现在有两个mapper,AMapper,BMapper,正常情况下被spring扫描完成后,AMapper对应的class的应该是AMapper.class,BMapper对应的class应该是BMapper.class,但是现在不是了都变成了MapperFactoryBean.class,那么spring在针对AMapper,BMapper进行初始化的时候,就不会进行调用AMapper,BMapper的实例化方法,而是会进行调用MapperFactoryBean类中的getObject方法.通过这个MapperFactoryBean工厂来进行生产Mapper对象。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}
}
3.总结
那我们来简单总结一下,首先@MapperScan注解中的MapperScannerRegistrar类,往spring容器中添加了MapperScannerConfigurer类;其次 MapperScannerConfigurer类在spring的启动过程中,会进行启动一个ClassPathMapperScanner类的扫描器,该扫描继承于spring内置的扫描器ClassPathBeanDefinitionScanner,覆盖其中的过滤器的逻辑和doscan方法来进行扫描mapper信息;最后将扫描出来的Mapper信息进行“狸猫换太子”,使用mybatis的MapperFactoryBean类来进行替换对用的mapper的Class信息,在进行构建对应的Mapper的时候 其实就是调用MapperFactoryBean工厂类来进行生产对应的Mapper。