Mybatis源码解析(一):MyBatis 是如何执行的
Mybatis源码解析(二):Mybatis 插件原理
Mybatis源码解析(三):Spring 整合 Mybatis 原理
1 关于 Spring 的前置知识
MyBatis 与 Spring 框架整合需要借助于 MyBatis Spring 模块,在理解 MyBatis Spring 模块的实现原理之前,需要先了解一下 Spring 中的一些概念。
1.1 一些 Spring 中的概念
BeanDefinition
BeanDefinition 用于描述 Spring Bean 的配置信息,Spring 配置 Bean 的方式通常有3种:
- XML 配置文件;
- Java 注解,例如
@Service、@Component等注解; - Java Config 方式,Spring 从 3.0 版本开始支持使
@Configuration注解,通过 Java Config 方式配置 Bean。
BeanDefinitionRegistry
BeanDefinitionRegistry 是 BeanDefinition 容器,所有的 Bean 配置解析后生成的 BeanDefinition 对象都会注册到 BeanDefinitionRegistry 对象中。Spring 提供了扩展机制,允许用户在 Spring 框架启动时,动态地往 BeanDefinitionRegistry 容器中注册 BeanDefinition 对象。
BeanFactory
BeanFactory 是 Spring 的 Bean 工厂,负责 Bean 的创建及属性注入。它同时是一个 Bean 容器,Spring 框架启动后,会根据 BeanDefinition 对象创建 Bean 实例,所有的单例 Bean 都会注册到 BeanFactory 容器中。
BeanFactoryPostProcessor
BeanFactoryPostProcessor 是 Spring 提供的扩展机制,用于在所有的 Bean 配置信息解析完成后修改 BeanFactory 信息。例如,向 BeanDefinitionRegistry 容器中增加额外的 BeanDefinition 对象,或者修改原有的 BeanDefinition 对象。BeanFactoryPostProcessor 是一个接口,该接口中只有一个方法:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
当我们配置的 Bean 实现该接口时,Spring 解析 Bean 配置完成后,就会调用所有 BeanFactoryPostProcessor 实现类的 postProcessBeanFactory() 方法。
ImportBeanDefinitionRegistrar
该接口的实现类作用于 Spring 解析 Bean 的配置阶段,当解析 @Configuration 注解时,可以通过 ImportBeanDefinitionRegistrar 接口的实现类向 BeanDefinitionRegistry 容器中添加额外的 BeanDefinition 对象:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}
}
ImportBeanDefinitionRegistrar 接口实现类的 registerBeanDefinitions() 方法会在 Spring 解析 @Configuration 注解时调用。ImportBeanDefinitionRegistrar 接口需要配合 @Import 注解使用,importingClassMetadata 参数为 @Import 所在注解的配置信息,registry 参数为 BeanDefinition 容器。
BeanPostProcessor
Bean 的后置处理器,在 Bean 初始化方法(init-method 属性指定的方法或 afterPropertiesSet() 方法)调用前后,会执行 BeanPostProcessor 中定义的拦截逻辑:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
BeanPostProcessor 接口中定义了两个方法,postProcessBeforelnitialization() 方法会在所有 Bean 初始化方法调用之前执行,postProcessAfterlnitialization() 方法会在所有 Bean 的初始化方法调用之后执行,BeanPostProcessor 通常用于处理 Spring Bean 对应的 Java 类中的注解信息或者创建 Bean 的代理对象。
ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner 是 BeanDefinition 扫描器,能够对指定包下的 Class 进行扫描,将 Class 信息转换为 BeanDefinition 对象注册到 BeanDefinitionRegistry 容器中。ClassPathBeanDefinitionScanner 支持自定义的过滤规则,例如我们可以只对使用某种注解的类进行扫描。Spring 中的 @Service、@Component 等注解配置 Bean 都是通过 ClassPathBeanDefinitionScanner 实现的。MyBatis Spring 模块中 Mapper 接口的扫描使用到了 ClassPathBeanDefinitionScanner 类。
FactoryBean
FactoryBean 是 Spring 中的工厂 Bean,通常用于处理 Spring 中配置较为复杂或者由动态代理生成的 Bean 实例,实现了该接口的 Bean 不能作为普通的 Bean 使用,而是作为单个对象的工厂。当我们通过 Bean 名称获取 FactoryBean 实例时,获取到的并不是 FactoryBean 对象本身,而是 FactoryBean 对象的 getObject() 方法返回的实例。例如如下 MybatisSqlSessionFactoryBean 配置:
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"
<property name="dataSource" ref="dataSource" />
</bean>
SqlSessionFactoryBean 是一个 FactoryBean,通过名称 sqlSessionFactory 从 Spring 容器中获取 Bean 时,获取到的实际上是 SqlSessionFactoryBean 对象的 getObject() 方法返回的 SqlSessionFactory 对象。
1.2 Spring 容器启动过程
除了上一节的这些概念外,还需要熟悉一下 Spring 框架的启动过程,Spring 框架的启动过程大致可以分为以下几步:
- 对所有 Bean 的配置信息进行解析,其中包括 XML 配置文件、Java 注解以及 Java Config 方式配置的 Bean ,将 Bean 的配置信息转换为
BeanDefinition对象,注册到BeanDefinitionRegistry容器中; - 从
BeanDefinitionRegistry容器中获取实现了BeanFactoryPostProcessor接口的 Bean 定义,然后实例化 Bean,调用所有BeanFactoryPostProcessor对象的postProcessBeanFactory()方法,在postProcessBeanFactory()方法中可以对 Bean 工厂的信息进行修改; - 根据
BeanDefinitionRegistry容器中的BeanDefinition对象实例化所有的单例 Bean,并对Bean 的属性进行填充; - 执行所有实现了
BeanPostProcessor接口的 Bean 的postProcessBeforelnitialization()方法。该方法中可以对原始的 Bean 进行包装; - 执行 Bean 的初始化方法,初始化方法包括配置 Bean 时通过
init-method属性指定的方法,或者通过实现InitializingBean接口重写的afterPropertiesSet()方法; - 执行所有实现了
BeanPostProcessor接口的 Bean 的postProcessAfterinitialization()方法。
2 Mybatis Spring 简单配置案例
Spring 配置 Bean 的方式有多种,例如 XML 文件、Java 注解以及 JavaConfig等方式。这里我们使用 JavaConfig 方式配置 Mybatis 需要的 Bean。
DataSourceConfig.class
@Configuration
@MapperScan(basePackages = {"com.example.learn.common.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
@Bean("dataSource")
@ConfigurationProperties(prefix = "spring.datasource") // 引入 application.properties 中数据源配置信息
public DataSource dataSource() {
// 注入数据源
return DruidDataSourceBuilder.create().build();
}
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
// 通过 Mybatis Spring 模块提供的 SqlSessionFactoryBean 创建 MyBatis 的 SqlSessionFactory 对象
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean.getObject();
}
@Bean("sqlSessionTemplate")
public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
// 创建MybatisSpring模块中的Sq1sessionTemplate对象
return new SqlSessionTemplate(sqlSessionFactory);
}
}
application.properties
spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
上面代码通过 JavaConfig 方式配置 MyBatis,主要完成以下功能:
- 创建
DataSource实例。通过DruidDataSourceBuilder构建了一个DruidDataSource实例; - 配置
SqlSessionFactory对象。SqlSession是 MyBatis 提供的与数据库交互的接口,SqlSessionFactory通过工厂模式创建SqlSession,并通过Spring来管理SqlSessionFactory对象的生命周期。上面的代码中使用 MyBatis Spring 模块中提供的SqlSessionFactoryBean来构建SqlSessionFactory对象; - 配置
SqlSessionTemplate对象。使用 MyBatis 时可以通过SqlSessionFactory对象的openSession()方法获取一个SqlSession对象与数据库进行交互,MyBatis Spring模块提供了SqlSessionTemplate用于完成数据库交互,在整合 Spring 容器中只存在一个SqlSessionTemplate实例; - 通过
MapperScan注解扫描Mapper接口。在MyBatis中Mapper对象是通过动态代理生成的,调用SqlSession对象的getMapper()方法每次返回的是一个新的代理对象。MyBatis Spring 模块提供了一个MapperScan注解,用于扫描特定包下的Mapper接口,并创建Mapper代理对象,然后将Mapper对象添加到 Spring 容器中。
3 Mybatis Spring 原理分析
前面介绍了 Spring 框架中的一些概念以及 Spring 框架的启动过程。在理解 Spring 的基础上,下面介绍 MyBatis 中动态代理创建的 Mapper 对象是如何与 Spring 容器进行关联的。
MyBatis 与 Spring 框架整合需要借助 MyBatis Spring 模块,该模块中提供了一个 MapperScan 注解,该注解用于扫描指定包中的 Mapper 接口并创建 Mapper 动态代理对象,其关键代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
// 扫描包路径
String[] value() default {};
// 扫描包路径
String[] basePackages() default {};
// 扫描 Mapper 接口对应的 Class 对象
Class<?>[] basePackageClasses() default {};
// Bean 名称生成策略
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
// 只扫描使用某种注解修饰的类或接口
Class<? extends Annotation> annotationClass() default Annotation.class;
// 只扫描某种类型的子类型
Class<?> markerInterface() default Class.class;
// 指定使用哪个 SqlSessionTemplate 对象
String sqlSessionTemplateRef() default "";
// 指定使用哪个 SqlSessionFactory 对象
String sqlSessionFactoryRef() default "";
// 指定使用自定义的 MapperFactoryBean 返回 Mybatis代理对象作为 Spring 的 Bean
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
MapperScan 注解通过 @Import 注解导入了一个 BeanDefinition 注册类 MapperScannerRegistrar,MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口。
在上文提到过,Spring中的 ImportBeanDefinitionRegistrar 用于在 Spring 解析 Bean 的配置阶段往 BeanDefinitionRegistry 容器中注册额外的 BeanDefinition 对象。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取 MapperScan 注解中的配置信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 调用 registerBeanDefinitions() 方法注册 BeanDefinition 对象
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
// MapperScannerConfigurer 用于整合 Mybatis Spring 模块自定义的 BeanDefinition 扫描器 ClassPathMapperScanner
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 以下是解析 Mapper 注解中的信息,添加到 MapperScannerConfigurer 对象中
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(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
如上面的代码所示,MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口的registerBeanDefinitions() 方法,该方法调用了重载的 registerBeanDefinitions() 进行处理。在重载方法中,首先创建了一个 MapperScannerConfigurer 的构建者对象,然后获取 MapperScan 注解的属性信息,加到 MapperScannerConfigurer 对象的属性中,最终将该 MapperScannerConfigurer 对象注册到 BeanDefinitionRegistry 中。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// Mybatis Spring 模块自定义的 BeanDefinition 扫描器
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);
// MapperFactoryBeanClass 默认是 MapperFactoryBean.class
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
// 注册扫描过滤规则
scanner.registerFilters();
// 对包中的类进行扫描生成 BeanDefinition 对象
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
}
MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,该接口是标准BeanFactoryPostProcessor SPI 的扩展,允许在常规 BeanFactoryPostProcess 检测开始之前注册更多的 bean 定义,其执行逻辑在 postProcessBeanDefinitionRegistry 方法中;
postProcessBeanDefinitionRegistry 中,使用 ClassPathMapperScanner 扫描 Mapper 的 basePackage注册 Mapper 的 BeanDefination。ClassPathMapperScanner 是 Spring 中 ClassPathBeanDefinitionScanner 的子类,用于扫描特定包下 的 Mapper 接口,将 Mapper 接口信息转换为对应的 BeanDefinition 对象。下面是ClassPathMapperScanner 类 doScan() 方法的实现:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
// mapperFactoryBeanClass 属性为 MapperFactoryBean.class;
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的 doscan() 方法,将包中的 Class 转换为 BeanDefinitionHolder 对象
Set<BeanDefinitionHolder> 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;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
// 获取 BeanDefinition 对象
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 将 BeanDefinition 对象的 beanclass 属性设置为 MapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
......
}
}
}
在 ClassPathMapperScanner 类的 doScan() 方法中,首先调用父类的 doScan() 方法,将指定包下的 Mapper 接口信息转换为 BeanDefinitionHolder 对象,BeanDefinitionHolder 中持有一个 BeanDefinition 对象及 Bean 的名称和所有别名;
所有的 Mapper 接口转换为 BeanDefinitionHolder 对象后,接着调用 processBeanDefinitions() 方法,对所有 BeanDefinitionHolder 对象进行处理;
在 processBeanDefinitions() 方法中,对所有 BeanDefinitionHolder 对象进行遍历,获取BeanDefinitionHolder 对象中持有的 BeanDefinition 对象,然后对 BeanDefinition 对象的信息进行修改,将 BeanDefinition 对象的 beanClass 属性设置为 MapperFactoryBean.class;
将 BeanDefinition 对象的 beanClass 属性设置为 MapperFactoryBean.class 这一步很重要,当 Spring 将所有的 Bean 配置信息转换为 BeanDefinition 对象后,就会根据 BeanDefinition 对象来实例化 Bean,由于 BeanDefinition 对象的 beanClass 属性被设置为 MapperFactoryBean.class,因此 Spring 在创建 Bean 时实例化的是 MapperFactoryBean 对象;
MapperFactoryBean 实现了 FactoryBean 接口,FactoryBean 是单个 Bean 的工厂 Bean,当我们根据 Mapper 类型从 Spring 容器中获取 FactoryBean 时,获取到的并不是 FactoryBean 本身,而是 FactoryBean 的 getObject() 方法返回的对象。
看下 MapperFactoryBean 的 getObject() 方法的实现:
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里我们找到了想要的答案,在 MapperFactoryBean 的 getObject() 方法中,调用 SqlSession 对
象的 getMapper() 方法返回一个 Mapper 动态代理对象。