Javassm - Mybatis整合原理
约 1024 字大约 3 分钟
Mybatis整合原理
@MapperScan实现
通过之前的了解,我们再来看Mybatis的@MapperScan是如何实现的,现在理解起来就非常简单了。
我们可以直接打开查看:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
...
}
我们发现,和Aop一样,它也是通过Registrar机制,通过@Import来进行Bean的注册
我们来看看MapperScannerRegistrar是个什么东西,关键代码如下:
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
...
}
虽然很长很多,但是这些代码都是在添加一些Bean定义的属性,而最关键的则是最上方的MapperScannerConfigurer,Mybatis将其Bean信息注册到了容器中
MapperScannerConfigurer 类
那么这个类又是干嘛的呢?
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
...
}
它实现了BeanDefinitionRegistryPostProcessor,也就是说它为Bean信息加载提供了后置处理
我们接着来看看它在Bean信息后置处理中做了什么:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
// 初始化类路径Mapper扫描器,它相当于是一个工具类,可以快速扫描出整个包下的类定义信息
// ClassPathMapperScanner是Mybatis自己实现的一个扫描器,修改了一些扫描规则
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);
}
// 添加过滤器,这里会配置为所有的接口都能被扫描(因此即使你不添加@Mapper注解都能够被扫描并加载)
scanner.registerFilters();
// 开始扫描
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
开始扫描后,会调用doScan()方法,我们接着来看(这是ClassPathMapperScanner中的扫描方法):
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
// 首先从包中扫描所有的Bean定义
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> {
return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
});
} else {
// 处理所有的Bean定义,实际上就是生成对应Mapper的代理对象,并注册到容器中
this.processBeanDefinitions(beanDefinitions);
}
// 最后返回所有的Bean定义集合
return beanDefinitions;
}
通过断点我们发现,最后处理得到的Bean定义发现此Bean是一个MapperFactoryBean,它不同于普通的Bean
FactoryBean相当于为普通的Bean添加了一层外壳,它并不是依靠Spring直接通过反射创建,而是使用接口中的方法:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
通过getObject()方法,就可以获取到Bean的实例了。
FactoryBean和BeanFactory
注意这里一定要区分FactoryBean和BeanFactory的概念:
- BeanFactory是个Factory,也就是 IOC 容器或对象工厂,所有的 Bean 都是由 BeanFactory( 也就是 IOC 容器 ) 来进行管理。
- FactoryBean是一个能生产或者修饰生成对象的工厂Bean(本质上也是一个Bean),可以在BeanFactory(IOC容器)中被管理,所以它并不是一个简单的Bean。当使用容器中factory bean的时候,该容器不会返回factory bean本身,而是返回其生成的对象。要想获取FactoryBean的实现类本身,得在getBean(String BeanName)中的BeanName之前加上&,写成getBean(String &BeanName)。
我们也可以自己编写一个实现:
@Component("test")
public class TestFb implements FactoryBean<Student> {
@Override
public Student getObject() throws Exception {
System.out.println("获取了学生");
return new Student();
}
@Override
public Class<?> getObjectType() {
return Student.class;
}
}
public static void main(String[] args) {
log.info("项目正在启动...");
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
System.out.println(context.getBean("&test"));
// 得到FactoryBean本身(得加个&搞得像C语言指针一样)
System.out.println(context.getBean("test"));
// 得到FactoryBean调用getObject()之后的结果
}
因此,实际上我们的Mapper最终就以FactoryBean的形式,被注册到容器中进行加载了:
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
这样,整个Mybatis的@MapperScan的原理就全部解释完毕了。
在了解完了Spring的底层原理之后,我们其实已经完全可以根据这些实现原理来手写一个Spring框架了。
