• Spring原理:Mybatis整合原理


    Mybatis整合原理

    通过之前的了解,我们再来看Mybatis的@MapperScan是如何实现的,现在理解起来就非常简单了。

    我们可以直接打开查看:

     

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target({ElementType.TYPE})
    3. @Documented
    4. @Import({MapperScannerRegistrar.class})
    5. @Repeatable(MapperScans.class)
    6. public @interface MapperScan {
    7. String[] value() default {};
    8. String[] basePackages() default {};
    9. ...

    我们发现,和Aop一样,它也是通过Registrar机制,通过@Import来进行Bean的注册,我们来看看MapperScannerRegistrar是个什么东西,关键代码如下:

     

    1. void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    2. BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    3. builder.addPropertyValue("processPropertyPlaceHolders", true);
    4. Classextends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    5. if (!Annotation.class.equals(annotationClass)) {
    6. builder.addPropertyValue("annotationClass", annotationClass);
    7. }
    8. Class markerInterface = annoAttrs.getClass("markerInterface");
    9. if (!Class.class.equals(markerInterface)) {
    10. builder.addPropertyValue("markerInterface", markerInterface);
    11. }
    12. Classextends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    13. if (!BeanNameGenerator.class.equals(generatorClass)) {
    14. builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    15. }
    16. Classextends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    17. if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    18. builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    19. }
    20. String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    21. if (StringUtils.hasText(sqlSessionTemplateRef)) {
    22. builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    23. }
    24. String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    25. if (StringUtils.hasText(sqlSessionFactoryRef)) {
    26. builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    27. }
    28. List basePackages = new ArrayList();
    29. basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
    30. basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
    31. basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
    32. if (basePackages.isEmpty()) {
    33. basePackages.add(getDefaultBasePackage(annoMeta));
    34. }
    35. String lazyInitialization = annoAttrs.getString("lazyInitialization");
    36. if (StringUtils.hasText(lazyInitialization)) {
    37. builder.addPropertyValue("lazyInitialization", lazyInitialization);
    38. }
    39. String defaultScope = annoAttrs.getString("defaultScope");
    40. if (!"".equals(defaultScope)) {
    41. builder.addPropertyValue("defaultScope", defaultScope);
    42. }
    43. builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    44. registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    45. }

    虽然很长很多,但是这些代码都是在添加一些Bean定义的属性,而最关键的则是最上方的MapperScannerConfigurer,Mybatis将其Bean信息注册到了容器中,那么这个类又是干嘛的呢?

     

    1. public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    2. private String basePackage;

    它实现了BeanDefinitionRegistryPostProcessor,也就是说它为Bean信息加载提供了后置处理,我们接着来看看它在Bean信息后置处理中做了什么:

    1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    2. if (this.processPropertyPlaceHolders) {
    3. this.processPropertyPlaceHolders();
    4. }
    5. //初始化类路径Mapper扫描器,它相当于是一个工具类,可以快速扫描出整个包下的类定义信息
    6. //ClassPathMapperScanner是Mybatis自己实现的一个扫描器,修改了一些扫描规则
    7. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    8. scanner.setAddToConfig(this.addToConfig);
    9. scanner.setAnnotationClass(this.annotationClass);
    10. scanner.setMarkerInterface(this.markerInterface);
    11. scanner.setSqlSessionFactory(this.sqlSessionFactory);
    12. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    13. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    14. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    15. scanner.setResourceLoader(this.applicationContext);
    16. scanner.setBeanNameGenerator(this.nameGenerator);
    17. scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    18. if (StringUtils.hasText(this.lazyInitialization)) {
    19. scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
    20. }
    21. if (StringUtils.hasText(this.defaultScope)) {
    22. scanner.setDefaultScope(this.defaultScope);
    23. }
    24. //添加过滤器,这里会配置为所有的接口都能被扫描(因此即使你不添加@Mapper注解都能够被扫描并加载)
    25. scanner.registerFilters();
    26. //开始扫描
    27. scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    28. }

    开始扫描后,会调用doScan()方法,我们接着来看(这是ClassPathMapperScanner中的扫描方法):

    1. public Set doScan(String... basePackages) {
    2. Set beanDefinitions = super.doScan(basePackages);
    3. //首先从包中扫描所有的Bean定义
    4. if (beanDefinitions.isEmpty()) {
    5. LOGGER.warn(() -> {
    6. return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
    7. });
    8. } else {
    9. //处理所有的Bean定义,实际上就是生成对应Mapper的代理对象,并注册到容器中
    10. this.processBeanDefinitions(beanDefinitions);
    11. }
    12. //最后返回所有的Bean定义集合
    13. return beanDefinitions;
    14. }

    通过断点我们发现,最后处理得到的Bean定义发现此Bean是一个MapperFactoryBean,它不同于普通的Bean,FactoryBean相当于为普通的Bean添加了一层外壳,它并不是依靠Spring直接通过反射创建,而是使用接口中的方法:

    1. public interface FactoryBean {
    2. String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    3. @Nullable
    4. T getObject() throws Exception;
    5. @Nullable
    6. Class getObjectType();
    7. default boolean isSingleton() {
    8. return true;
    9. }
    10. }

    通过getObject()方法,就可以获取到Bean的实例了。

    注意这里一定要区分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)。

    我们也可以自己编写一个实现:

     

    1. @Component("test")
    2. public class TestFb implements FactoryBean {
    3. @Override
    4. public Student getObject() throws Exception {
    5. System.out.println("获取了学生");
    6. return new Student();
    7. }
    8. @Override
    9. public Class getObjectType() {
    10. return Student.class;
    11. }
    12. }
    1. public static void main(String[] args) {
    2. log.info("项目正在启动...");
    3. ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
    4. System.out.println(context.getBean("&test")); //得到FactoryBean本身(得加个&搞得像C语言指针一样)
    5. System.out.println(context.getBean("test")); //得到FactoryBean调用getObject()之后的结果
    6. }

    因此,实际上我们的Mapper最终就以FactoryBean的形式,被注册到容器中进行加载了:

    1. public T getObject() throws Exception {
    2. return this.getSqlSession().getMapper(this.mapperInterface);
    3. }

    这样,整个Mybatis的@MapperScan的原理就全部解释完毕了。

    在了解完了Spring的底层原理之后,我们其实已经完全可以根据这些实现原理来手写一个Spring框架了。

  • 相关阅读:
    30天Python入门(第六天:深入了解Python中的元组)
    在 Maui 中自绘组件1:绘制
    [C语言] 自制的贪吃蛇游戏
    Sql case函数
    我的 Kafka 旅程 - 性能调优
    初入职场两三事【研发篇】
    【GPU】Nvidia CUDA 编程高级教程——支持点对点访问的多 GPU
    树模型(三)决策树
    商简智能:世界领先的高级计划与排程APS供应商
    第5章-宏观业务分析方法-5.3-主成分分析法
  • 原文地址:https://blog.csdn.net/Leon_Jinhai_Sun/article/details/126553597