作者|博戈评论|孙淑娟如果老手熟悉Spring框架,集成MyBatis其实很容易理解。当然,前提是老手已经熟悉MyBatis框架。在我们平时的应用开发过程中,使用MyBatis一般分为以下几个步骤:1.在配置类中添加MapperScan注解,例如:@MapperScan(basePackages={"com.test.dao"},annotationClass=Mapper.class);2.在basePackages指定的目录下创建MyBatis读取的接口文件,例如:@MapperpublicinterfaceTestMapper{...}3.在Service或其他地方使用这个Mapper来操作数据库。使用起来非常简单,但是大家有没有想过为什么这个Mapper这么简单的配置就可以对数据库进行操作呢?按理说这个Mapper是一个接口,不应该创建的!如果你有这样的疑问,就证明你是一个爱思考的好孩子。让我们开门见山。将Spring与MyBatis集成,只需要解决以下两个问题:1.Spring如何知道应该管理哪些类?要让Spring管理Bean的生命周期,需要先让Spring扫描对应的类,生成DeanDefinition,然后根据BeanDefinition生成Bean。下面简单总结一下Spring生成BeanDefinition的方式:包含Component、Configuration、ComponentScan、Import、ImportResource注解的类;Import注解中指定的类,Bean注解标记的方法所在的类;实现了ImportBeanDefinitionRegistrar接口,并在registerBeanDefinitions方法中调用注册中心直接注册的类;实现ImportSelector接口,以及selectImports方法返回的字符串对应的类;直接调用注册方法;此外,Spring还提供了一个扩展,允许开发者指定需要选择的类型对应的托管类:通过在includeFilters中添加注解类类型。我们在分析源码的时候,第一步就是找到它的入口。Spring整合MyBatis的入口,无疑是MapperScan的注解。MapperScan的注解包含Import(MapperScannerRegistrar.class)的注解。Spring对MyBatis的集成使用了Import和ImportBeanDefinitionRegistrar的方式。我们先通过一张流程图了解一下整体流程,然后慢慢品味。我们看一下MapperScannerRegistrar类的继承关系图:MapperScannerRegistrar是ImportBeanDefinitionRegistrar的实现类。Spring会调用这个类的registerBeanDefinitions方法来添加beanDefinition。这个方法具体是做什么的:获取MapperScan注解的配置信息,比如basePackages,annotationClass,basePackages表示需要扫描的路径,annotationClass指定添加这个注解类的类需要被Spring管理,对于比如添加Mapper注解的类需要Spring来管理。生成一个MapperScannerConfigurer类型的beanDefinition,将MapperScan注解的配置信息添加到beanDefinition的属性集合中。后续Spring会基于这个MapperScannerConfigurer做系列文章,看看它的继承关系:是BeanDefinitionRegistryPostProcessor的实现类,是一个BeanFactory后处理器。Spring会调用这个类的postProcessBeanDefinitionRegistry方法来添加beanDefinition操作,MapperScannerConfigurer这个类的具体实现如下:它定义了扫描器ClassPathMapperScanner,然后使用这个扫描器来扫描类。扫描了哪些类?使用Mapper注释扫描类并查看其关系。它是ClassPathBeanDefinitionScanner的子类,spring使用ClassPathBeanDefinitionScanner进行扫描。为什么ClassPathMapperScanner可以扫描带Mapper注解的类?看上面的代码,是通过调用registerFilters方法添加includeFilter(实际类型:TypeFilter)。这是Spring提供的扩展点。让我们指定需要扫描的类。这里我们使用了MappScan注解Annotation类型中的annotationClass属性配置,我们这里配置了Mapper,所以在调用scan方法开始扫描后,Spring会扫描包含Mapper注解的类作为BeanDefinition。注意这里的扫描能力还是通过调用Spring扫描器来实现的。ClassPathMapperScanner没有修改,但是扫描完成后,ClassPathMapperScanner会重新处理扫描到的BeanDefinition,主要是把原来的BeanClass改成MapperFactoryBean.class:而这个MapperFactoryBean就是FactoryBean的实现类,老手,一个Bean有什么特点喜欢FactoryBean吗?这是采访的高潮。做一个小总结:Spring扫描带有Mapper注解的类,生成BeanDefinition,并将该类型BeanDefinition的BeanClass的值改为MapperFactoryBean,也就是说它的类型不再是我们自己写的Mapper接口了。它是一个FactoryBean,让Spring可以成为恶魔。2、Mapper注解的类是一个接口,那么如何实例化呢?至此,其实老手们大概都知道,Spring在实例化一个Mapper实例的时候,其实是先实例化了MapperFactoryBean,然后调用它的getObject方法。我们知道Java中不能实例化接口,所以实例化的对象只能是代理对象,所以我们有理由猜测应该是使用getObject方法来创建代理对象。要创建一个代理对象,必须从以下两个方面着手:1.准备工作在这里,Spring准备了创建代理对象的接口类型和代理工厂。你是怎么准备的?看上面MapperFactoryBean类型的整体继承关系:它实现了InitializingBean,所以可以知道MapperFactoryBean初始化完成后,Spring会调用它的afterPropertiesSet方法,会执行checkDaoConfig方法:调用configuration中的addMapper方法这个方法,这个方法到底做了什么?你看得见路吗?其实就是以Mapper的接口类型为key,以MapperProxyFactory为value,然后添加到mapperRegistry对象的Map集合中。请注意,此类型也是MapperProxyFactory对象的构造参数。2.实例化上面的action都准备好了,接下来就是创建它们了。Spring在创建MapperFactoryBean对象后,最终会调用其getObject方法获取真正的对象:在getObject方法中,会调用getMapper方法,在该方法中,从knownMappers的Map集合中获取MapperProxyFactory对象。这个对象不是我们在准备添加的!它是用于创建代理对象的工厂。从上面的代码不难看出,代理对象确实是为我们自己的接口创建的,代理类的处理类是MapperProxy对象,也就是说所有接口对象的调用都会进入MapperProxy的Invoke方法,至此Spring成功连接MyBatis。笔者介绍的Bogo,在互联网行业工作10余年,先后担任过项目总监和架构师。目前专攻技术,喜欢研究技术原理。技术全面,以java为主,精通JVM底层机制和Spring全家桶底层框架原理,精通当前主流的中间件、服务网格等技术原理。
