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直接返回false
if (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 #59
definition.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。