几乎所有的框架都需要与 Spring 整合, Spring 也号称框架中的框架, 其拓展性就不用我说了, 直接看源码 (本章节采用的是 注解, 并非 xml 配置文件)
搭建环境
@Configuration
@ComponentScan("com.gp.ibatis")
@MapperScan("com.gp.ibatis.mapper")
public class MapperScanDemo {
private final Resource configResource = new ClassPathResource("conf.xml");
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setConfigLocation(configResource);
return sqlSessionFactoryBean;
}
@Bean
public DataSource dataSource(){
....
}
}
只需要添加一个注解, 并手动注册一个 Bean : SqlSessionFactoryBean, mybaits 就能在 Spring 中发挥左右了, 我们先来看看这个 @MapperScan 注解
@MapperScan
mybatis 自定义了三个重要的类 : MapperScannerConfigurer, MapperScannerRegistrar, ClassPathMapperScanner
MapperScannerRegistrar
@MapperScan 是一个复合注解, 里面包含一个 @Import(MapperScanRegistrar.class), 手动向 spring 中注册了一个对象 (并没有立即放入 BeanFactory 中, 先将其存放在一个 Map 中, 后面会分析)
该类何时干活的?
在准备完 BeanFactory (默认由 DefaultListableBeanFactory 实现) 后, 在 refresh() 中执行 invokeBeanFactoryPostProcessor() , 继续点进入一个静态方法, 在 PostProcessorRegistrationDelegate 的 invokeBeanFactoryPostProcessors 方法中有这样一段代码 :
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// 检查该 postProcessorNames 对应的类是否实现了 PriorityOrdered 接口
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
//如果执行完 beanFactory.getBean(), 该 bean 就会实例化并放入 单例池 中
//这里会拿到 spring 内部定义的类 : ConfigurationClassPostProcessor
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口
// 执行其 postProcessBeanDefinitionRegistry, 完成扫描
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
在 spring 在扫描的过程中, 会递归处理 @Import 注解, 详看源码 :
// 位于 ConfigurationClassParser#processImports() , 只提取一段
if (candidate.isAssignable(ImportSelector.class)) {
Class<?> candidateClass = candidate.loadClass();
// 实例化 ImportSelector 对象
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归处理
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// 这里就是处理 ImportBeanDefinitionRegistrar 的
// MapperScannerRegistrar 实现了该接口
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
// 这里就会把 MapperScannerRegistrar 对象放入一个 Map 中
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 将 @Import 的普通类视为 ConfigurationClass 继续进行处理
processConfigurationClass(candidate.asConfigClass(configClass));
}
从 spring 源码中可以看出, @Import 只能导入三种类型 : 1. ImportSelector 2. ImportBeanDefinitionRegistrar 3. 其他普通类 (类似 @Component 类)
可以实现 ImportSelector , ImportBeanDefinitionRegistrar 接口对 spring 进行拓展
那么何时处理缓存 ImportBeanDefinitionRegistrar 类型的集合呢?
在 spring 完成扫描后, 会遍历所有 configClass (包括 @Import 导入的类, @Component 的类都会被 spring 放入一个集合中, 视为 configClass )
在处理标注了 @MapperScan 的 configClass 时, 会依次处理 @Import 的类, @Bean 方法, 最后处理所有的 ImportBeanDefinitionRegistrar , MapperScannerRegistrar 到此时才开始工作
// 处理被 @Import 导入的类, 将其包装成 BeanDefinition 注册进 BeanFactory
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 处理所有的 @Bean 方法, 将 @Bean 方法转换成 BeanDefinition 注册进 BeanFactory
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 处理 @ImportResource 注解
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 处理 ImportBeanDefinitionRegistrar, 执行他们的 registerBeanDefinitions() 方法
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
点击 MapperScannerRegistrar 看会发现, 先是拿到元数据, 创建 beanClass 为 MapperScannerConfigurer 的 BeanDefinition 对象, 然后就是一堆的判断, 最后将 BeanDefinition 对象注册进 BeanFactory
MapperScannerConfigurer
该接口最重要的是实现了 BeanDefinitionRegistryPostProcessor 接口
还是在 PostProcessorRegistrationDelegate 的 invokeBeanFactoryPostProcessors 方法中, 执行完实现了 PriorityOrdered , Ordered 接口的 BeanDefinitionRegistryPostProcessor 后, 再来执行剩下的, 而此时 MapperScannerConfigurer 开始干活 (从这里可以看出 PriorityOrdered 的优先级高于 Ordered)
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 此类继承自 ClassPathBeanDefinitionScanner, 用于扫描工作
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 设置一些 mybatis 需要的参数
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(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
// 手动注册了一个 includeFilter, 始终返回 true
scanner.registerFilters();
// 扫描指定路径的所有接口
scanner.scan(...);
ClassPathMapperScanner
该类继承自 ClassPathBeanDefinitionScanner, 并重写了 doScan 和 isCandidateComponent 方法
// 扫描的方式和 spring 类似
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 这里根据过滤器判断该 class 文件对应的类是否标注 @Conditional 注解
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 该方法被 ClassPathMapperScanner 重写了
// 其实就是判断该类型是否为 接口且是独立开放的 (不为 private 或 default)
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
// 如果两个判断通过, mybatis 会认为这是一个符合的 dao
// 最后会被注册进 BeanFactory
candidates.add(sbd);
}
@Mapper
如果没有标注 @MapperScan 注解, 直接在接口上标注 @Mapper , 也能从容器中获取到, 这是为什么呢?
这种方式就是 spring boot 的自动配置类起作用了, MybatisAutoConfiguration 有两个内部类 :
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
....
}
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
MapperScannerRegistrarNotFoundConfiguration 作为一个配置类, 能被 spring boot 拿到, 当处理该类时, spring boot 就会判断容器中是否存在 MapperFactoryBean, MapperScannerConfigurer 这两种 bean 时, 如果不存在, 该配置类就会生效, Import 一个 AutoConfiguredMapperScannerRegistrar, 该类实现了 ImportBeanDefinitionRegistrar 接口, 在 spring 中同样的方式往容器中注册了 beanClass 为 MapperScanConfigurer 的 BeanDefinition 对象, 之后的流程就和上面一样了…
现在已经得到封装 Mapper 的 BeanDefinition 对象了, 还需要 mybatis 做一些工作, 修改此 Mapper 的 beanClass, 这个 Mapper 接口实际类是 MapperFactoryBean
还是 ClassPathMapperScanner 这个类, 在得到所有封装 Mapper 的 BeanDefinition 对象 (这个 BeanDefinition 被 BeanDefinitionHolder 封装了) 后, 调用他的 processBeanDefinitions 方法
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
....
// 修改此 Mapper 接口的 beanClass, MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
....
// 最后将此 Mapper 的注入模型修改为 by type
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
现在 Mapper 的 beanClass 已被修改
实例化 Mapper
spring 通过 JDK 提供的内省 : Introspector 得到 MapperFactoryBean 的 BeanInfo, 通过 BeanInfo 得到所有 PropertyDescriptor 对象 (这是一个数组), PropertyDescriptor 描述Java Bean 通过一对访问器方法导出的一个属性
利用这个 PropertyDescriptor 数组得到 sqlSessionFactory , 和一个 sqlSessionTemplate , 创建依赖关系, 但是只会把 SqlSessionFactory 充当 Mapper 的属性, 在实例化 Mapper 的时候, 就会为这个属性名为 sqlSessionFactory 注入值
由于封装 Mapper 的 BeanDefinition 的自动装配模型设置为 : byType, 我们查看 AbstractAutowireCapableBeanFactory 的 autowireByType 方法
protected void autowireByType(
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
....
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
// 得到属性 name
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
try {
// 通过 JDK 的 Introspector 得到 MapperFactoryBean 的所有 PropertyDescriptor
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
// Don't try autowiring by type for type Object: never makes sense,
// even if it technically is a unsatisfied, non-simple property.
if (Object.class != pd.getPropertyType()) {
MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
// Do not allow eager init for type matching in case of a prioritized post-processor.
boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
// 创建依赖关系, 相当于被属性标注 @Autowire 注解
DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
// 处理依赖关系, 完成依赖注入 (实例化 SqlSessionFactory)
Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
if (autowiredArgument != null) {
pvs.add(propertyName, autowiredArgument);
}
....
}
在这个时候就会实例化 SqlSessionFactory, 这个是一个 FactoryBean : SqlSessionFactoryBean , 这个 bean 同时实现了 InitializingBean 接口, 所以在实例化之前, 会先进行初始化, 执行 加载mybatis全局配置文件, 初始化数据源 … 等工作
有了 SqlSessionFactory 对象, MapperFactoryBean 就能调用 setSqlSessionFactory(SqlSessionFactory) 方法设置 SqlSession 属性 (实现类为 SqlSessionTemplate )
MapperFactoryBean
每个 Dao 接口都是一个 MapperFactoryBean, 调用它的 getObject 得到对象
// 代码很短, 就是 mybatis 的原代码, 利用 Sqlsession 得到 Mapper 的代理对
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}