带你跳出源码地狱,从原理上理解MyBatis对Spring源码的扩展实现

有道无术,术尚可求也!有术无道,止于术!

今天咱们大概从如下几点去讲解MyBatis对于Spring的一个扩展思路!java

大纲

本文章只对原理和部分重要代码进行分析,源码的详细分析请跳转到:【 牛逼哄哄的Spring是怎么被MyBatis给征服了?】 具体观看,本文带你从原理上全面的理解MyBatis扩展Spring源码的体系!

1、FactoryBean是干什么?

首先咱们至少要知道一个事情,就是FactoryBean的一个大体结构:web

FactoryBean的大体结构

能够看到,整个 FactoryBean有三个方法:缓存

  • getObject(): 返回具体建立的真实对象!
  • getObjectType(): 返回建立对象的类型!
  • isSingleton(): 建立的该对象是否是单例对象!

此时,至少咱们已经知道了,咱们能够经过一个FactoryBean来生产一个对象,能够获取这个对象的类型以及这个对象是否是单例!可是离开了Spring它就什么也不是,那么Spring封装这个东西是干吗的呢?微信

1. 自定义Spring实例化的bean

正是由于FactoryBean的存在咱们才可以插手或者改变一个Bean的建立过程!,为何这么说呢?我举个例子:app

就拿你们经常使用的MyBatis为例,咱们都知道MyBatis的使用通常都是使用一个接口,映射一个XML文件,MyBatis内部通过动态代理,动态的为接口生成一个实现类,从而让咱们可以经过接口直接调用里面的逻辑!框架

可是MyBatis经过Spring管理以后,同窗们是否疑惑过,咱们明明没有使用MyBatis那一套逻辑,仅仅经过一个@Autowired注解,就可以直接注入到Service使用,那么MyBatis的动态代理逻辑大概是在哪里作的?编辑器

没错就是在FactoryBean里面作的!函数

MyBatis使用FactoryBean进行动态代理

熟悉MyBatis用法的同窗看到这个代码是否是就十分的熟悉了?这一段正是MyBatis经过接口生成动态代理的一段逻辑!那么此时咱们至少知道了Spring可以FactoryBean调用 getObject()方法可以建立一个对象,并把对象管理起来!post

2. 不遵循Spring的生命周期

这个为何呢?做者的想法是,正是由于Spring的做者想要放权给使用者,让使用者本身实现建立一个bean的逻辑,因此Spring并不会过多的插手该Bean的实例化过程,使得一个Bean的实例化彻底又使用者本人去实现!学习

这个类并不会像其它普通的bean那样在Spring容器初始化的时候就进行实例化,而是会相似于懒加载的一种机制,再获取的时候才会进行建立和返回!至因而不是单例,要取决于isSingleton()方法的返回值!

固然,这个建立出来的bean也会被缓存,AOP等逻辑也会对该类生效,固然这都是后话!

3. FactoryBean的总结

相信上述文章看完以后你对Factory会有一个基本的认识,咱们总结如下Spring调用它的基本流程!

FactoryBean的调用流程

2、自定义扫描器

Spring只是一个项目管理的框架,他也是由JAVA语言编写的,因此它必须遵循JAVA语法的规范!咱们可以使用Spring帮助咱们管理咱们开发过程当中的一些类,可以自动注入或者AOP代理等逻辑!

可是咱们是否发现,Spring它只可以管理咱们指定的包下的类,或者咱们手动添加的一些类!并且Spring也没有办法去帮咱们扫描一些抽象类或者接口,可是咱们有时候由于一些特殊的开发,咱们必需要打破Spring原有的扫描过程,好比咱们就要Spring帮咱们管理一个接口、帮咱们扫描一些加了特定注解的类等特殊需求,这个时候,咱们就不可以使用Spring为咱们提供的扫描逻辑了,须要咱们自定义一个扫描逻辑!

1. 栗子

举个例子(咱们仍是以MyBatis为例):

咱们经过上面FactoryBean的学习咱们理解了一件事,Spring中MyBatis可以经过FactoryBean进行动态代理的建立并返回,可是咱们都知道使用jdk动态代理所必须的一个元素:接口,由于jdk动态代理就是基于接口来作的!

这些接口从哪里来呢?要知道Spring是不会把接口也扫描的,因此此时就须要咱们的自定义扫描器了,咱们使用自定义扫描器将接口扫描到,而后经过修改BeanDefinition强行指定为FactoryBean类型的bean, 把咱们的接口传入进去,而后再将BeanDefinition加入bean工厂,此时咱们须要的一个必须元素接口就有了!

自定义扫描器结合FactoryBean

3、ImportBeanDefinitionRegistrar

1. 调用时机

ImportBeanDefinitionRegistrar也是Spring生命周期中重要的一环,上周咱们学到,Spring再执行BeanFactoryPostProcessor时,会实现执行系统内置的一个后置处理器---ConfigurationClassPostProcessor,它的做用就是扫描项目指定路径下的类,转换成对应的BeanDefinition!可是它的做用可不止这一个哦!

它除了有扫描指定包下的类的功能,还有解析@Import注解的功能,ImportBeanDefinitionRegistrar就是@Import中一个比较特殊的类,它会被Spring自动的回调内部的registerBeanDefinitions()方法!

那么由此可知它的调用时机再ConfigurationClassPostProcessor以后剩余其余的全部BeanFactoryPostProcessor以前

2. 回调方法以及意义

上面咱们也说到了,他会回调registerBeanDefinitions()方法,那么意义何在呢?若是只是可以进行回调的话,BeanDefinitionRegistryPostProcessor也能完成相似的功能,它的特殊之处在于什么呢?咱们看一下它的方法签名!

image-20200914224036880

咱们重点关注第一个参数,他在回调的时候,会将标注@Import注解的类的全部的元信息封装成AnnotationMetadata类,携带回去!

那么携带回去有什么意义呢?举个例子,依旧以MyBatis为例!

咱们试想如下,上面咱们说呢,咱们能够经过自定义扫描器将一个个接口转换成FactoryBean而后交给Spring管理,可是咱们要扫描那个包下的类呢?

使用过Spring整合MyBatis的人都应该知道,咱们通常都会在启动类上标注一个注解@MapperScan指定Mapper接口的包路径,它的目的就是为了向registerBeanDefinitions方法传递扫描的路径,以此完成扫描!

image-20200914225321751

4、BeanDefinitionRegistryPostProcessor

1. 概念

虽然这个BeanDefinitionRegistryPostProcessor上周复习的时候,我作过大量的源码层面的讲解!可是今天依旧要简单说一下!

上周的学习咱们知道BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor的子类,他们两个有什么区别吗?

咱们要知道,BeanFactoryPostProcessor只可以对已经存在的 BeanDefinition进行修改,可是没有办法进行添加和删除,可是BeanDefinitionRegistryPostProcessor不同,他对父类进行了扩展,提供了添加和删除的API,咱们能够经过该类进行增长和删除bean工厂的BeanDefinition!

2.举个例子

咱们依旧是以MyBatis为例!

咱们此时经过自定义扫描器把接口转换成了一个bd,可是咱们要如何向Spring工厂添加咱们扫描到的Bd呢?就是使用这个BeanDefinitionRegistryPostProcessor来进行注册bean定义!

BeanDefinitionRegistryPostProcessor

5、MyBatis如何扩展的Spring呢?

1. 扩展步骤(初始化步骤)

我相信,经过上面的关键点的讲解,你如今内心应该有了一个差很少的概念!MyBatis扩展Spring的方式大概以下:

  1. 首先咱们须要在配置类标注一个注解MapperScan,而且传入Mapper接口所在包路径!

  2. MapperScan会经过@Import注解向Spring注入一个MapperScannerRegistrar类,他是ImportBeanDefinitionRegistrar类型的,会被Spring自动回调registerBeanDefinitions方法!

  3. MapperScannerRegistrarregisterBeanDefinitions方法会构建一个类型为MapperScannerConfigurerBeanDefinition ,他是BeanDefinitionRegistryPostProcessor类型的!而后注册进Spring容器里面!

  4. Spring生命周期会自动回调MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法!

  5. postProcessBeanDefinitionRegistry方法内部建立了一个自定义的扫描器ClassPathMapperScanner,扫描你传入的包路径下的全部的接口,并转换为BeanDefinition !

  6. 获取到全部指定接口的BeanDefinition以后,遍历全部的BeanDefinition,而后修改他的BeanClassMapperFactoryBean类,他是FactoryBean类型的!

  7. 设置完BeanClass以后,经过definition.getPropertyValues().add()方法,传入该BeanDefinition表明的接口!

  8. 将全部的BeanDefinition经过 六、7步骤设置以后,所有注册到bean工厂中!由BeanFactory对这些FactoryBean进行管理,和生命周期的管理!

    注意,此时这些类并无被实例化,被实例化的是你传入的FactoryBean类,真实的类尚未被实例化!

2. 扩展步骤(实例化步骤)

  1. 在使用或者获取这些bean的时候,Spring会首先获取你要使用的接口类型!
  2. 遍历当前容器内全部的bean逐个对比,当有匹配的直接返回!可是,由于Mapper接口还并无被实例化!因此并无找到,因此在遍历到 FactoryBean的时候,会调用 getObjectType方法,将返回值与你要使用的接口类型做比对!
  3. 当 FactoryBean的返回类型匹配的时候,Spring会调用 FactoryBeangetObject方法将对象建立出来!
  4. 建立过程当中,经过以前传入的接口,作 jdk动态代理,完成MyBatis的代理逻辑!
  5. 对象建立完成后,经过 isSingleton方法的返回值判断,若是是单例对象,就将该对象缓存起来!并返回!

至此,咱们完成了整个MyBatis整合Spring的所有过程!

3.源码重点讲解

1)自定义扫描器

在MyBatis内部是如何自定义扫描器的呢?并且还能打破Spring原有的扫描流程,将接口扫描进项目!

image-20200915215932029

整段代码大体分为两部分:

  1. 毋庸置疑,他是建立了一个Mybatis本身的扫描器,这个扫描器是ClassPathBeanDefinitionScanner子类,这也是Spring为咱们提供的扩展点之一,咱们能够基于该扫描器,扩展任意的类变成bd,固然,他须要符合咱们的预设规则!什么是预设规则呢?咱们能够看到在我圈的第一个红框里面彷佛作了一个注册的操做,注册的什么呢?

    image-20200915220235102

一般状况下该判断就都是为true的,因此这里会执行一个添加的逻辑,添加到哪里了呢?

image-20200915220432302

它添加到了一个集合里面!至此,咱们至少知道了,这里会向集合里面添加一个过滤器,至于有什么用,咱们后面会说到,你这里先记住!

  1. 咱们再看第二个红框,开始执行扫描操做了!具体里面的代码我就不粘贴了,他会调用父类的扫描逻辑,咱们直接看父类是如何作的!

    image-20200915220818278


    这里将包路径转换为对应的bd,如何作的呢?

image-20200915221123343

这么长的逻辑,咱们重点关注两个判断:

  • 第一个判断,会判断该类是否被过滤,到底该不应转换为 BeanDefinition,还记得咱们刚刚注册的那个过滤器吗?一个过滤器被添加进集合里面了,他就是在这里被使用的!
image-20200915221801594

由于那个过滤器的定义因此这里必定会返回为true!m因此咱们第一个判断过了!一个类别转换成了BeanDefinition

  • 第二个判断,会调用子类的 isCandidateComponent方法,这里是判断一个类到底需不须要被添加进集合里面返回,咱们常识得知,Spring是不会替咱们管理一个接口类的,可是Mapper类又恰恰是一个接口,因此这时MyBatis不得不改写原有的逻辑使得它支持扫描接口并转换为bd,咱们看下里面的逻辑!
image-20200915221458473

由于MyBatis的Mapper类是一个接口,因此这里会返回为true!  因此咱们第二个判断进去了,一个接口的BeanDefinition被添加进集合!并返回!

至此,咱们大概知道了扫描器的工做原理!咱们看一下将接口扫描到以后作了那些操做呢?

2)经过BeanDefinition操做建立流程

image-20200915222512900
  • 他会循环遍历全部扫描到的接口bd,向每个bd的构造方法传递一个值,他是当前bd所表明的接口的全限定名!

    上面介绍MyBatis扩展FactoryBean的时候说到!它经过jdk建立动态代理,可是接口时哪里来的?就是经过

     definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);

    注入进去的!咱们都知道Spring建立对象是基于definition建立的,因此,咱们能够经过definition来注入咱们想要注入的值,他经常使用的用法还有相似下面的:

    image-20200915223229818

    MyBatis 中正是使用构造函数 的方式注入了一个接口的值!

    image-20200915223354790
  • 强行将接口的类型转换为FactoryBean类型的!

    至于为何转换为FactoryBean文章开篇说的很清楚了,这里就不详细赘述了,他是为了延迟初始化,使用jdk动态代理返回一个对象!从而完成MyBatis的功能!

3. 总结

MyBatis整合Spring的实例化过程

才疏学浅,若是文章中理解有误,欢迎大佬们私聊指正!欢迎关注做者的公众号,一块儿进步,一块儿学习!



       
❤️「转发」 「在看」 ,是对我最大的支持❤️



本文分享自微信公众号 - JAVA程序狗(javacxg)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索