浅谈mybatis的运行流程

前言

简单分享一下mybatis的运行流程,dao和mapper文件是如何绑定的,希望对正在看mybatis源码的小伙伴能有一些帮助。长篇文字可能比较枯燥,最近比较忙,等我后面有时间的时候,我会把源码贴上来,阅读起来会好一点。

流程

  1. 无论是在配置文件里配置mybatis的包扫描还是在springboot启动器上添加@MapperScan注解,本质上都是向ioc容器注册了一个MapperScannerConfigurer的BeanDefinition。注册时间为ioc容器invokeBeanFactoryPostProcessors时,ConfigurationClassPostProcessor这个类干的事情。ConfigurationClassPostProcessor这个类是一个BeanDefinitionRegistryPostProcessor。如果不明白BeanDefinitionRegistryPostProcessor是什么,建议先去学习ioc的源码,学习是循序渐进的,不知道ioc启动的流程和getBean()流程,不建议阅读mybatis框架源码。
  2. MapperScannerConfigurer也是一个BeanDefinitionRegistryPostProcessor类,在ConfigurationClassPostProcessor之后执行,因为ConfigurationClassPostProcessor实现了PriorityOrdered接口,优先级是最高的,仅次于容器启动时不经过BeanDefinition定义而直接添加到BeanFactory的beanFactoryPostProcessors缓存中的beanFactoryPostProcessor,(自己添加这种beanFactoryPostProcessor的话可以实现ApplicationContextInitializer接口,手动往里面添加。)
  3. MapperScannerConfigurer会在容器invokeBeanFactoryPostProcessors时,运行postProcessBeanDefinitionRegistry方法,主要做一件事,就是把配置的包路径下的符合过滤器规则的类(这个过滤器默认是添加全部,具体可以到源码里看一下),全部封装成BeanDefinition,然后添加到ioc容器的BeanDefinition容器中。而这个BeanDefinition是一个MapperFactoryBean类型,我们写的DAO类会被赋值给MapperFactoryBean的mapperInterface属性(其实是把beanName比如UserMapper赋值给构造方法参数),这个过程里也会给MapperFactoryBean添加sqlSessionFactory和sqlSessionTemplate,但是正常流程这个时候sqlSessionTemplate和sqlSessionFactory都是null,所以没有添加。
  4. ConfigurationClassPostProcessor在执行逻辑的时候,也会把sqlSessionFactory和sqlSessionTemplate也加载到BeanDefinition容器里。
  5. 最后ioc初始化所有非懒加载的bean。会初始化sqlSessionFactory和sqlSessionTemplate
  6. 初始化sqlSessionFactory做的事情就是首先build一个sqlSessionFactory,然后把所有的mapper.xml文件解析。主要包括:先拿到mapper.xml的namespace,然后解析所有的节点,把增删改查封装成一个个MapperStatement放到MapperStatements缓存里。然后根据namespace利用反射拿到DAO的class,因为namespace就是DAO的全路径名。拿到class放在一个knownMappers容器里,key为class,value是new了一个MapperProxyFactory,同时把class当成构造参数传给MapperProxyFaceory的mapperInterface属性。
  7. 我们假设我们有一个UserMapper.java和一个UserMapper.xml,有四个增删改查的方法,此时在内存里,在ioc容器的BeanDefinition容器里,有一个key为userMapper字符串,value为MapperFactoryBean的BeanDefinition的键值对,在MapperRegistry的knownMappers容器里,有一个key为UserMapper.class,MapperProxyFaceory为value的键值对,在MapperStatements容器里,有四个增删改查的方法,key为namespace+方法名(我们假设为save,update,delete,select),value为MapperStatement的键值对。此时,MapperFactoryBean的mapperInterface的属性和MapperProxyFaceory的mapperInterface属性都是UserMapper.class
  8. 然后,ioc容器仍在初始化所有的非懒加载bean。我们假设我们有一个UserService,使用@service注解注入到ioc容器,那么此时会初始化UserService这个bean, 而这个bean依赖了我们的UserMapper, 所以会触发UserMapper这个bean的加载。
  9. ioc容器根据beanName为”userMapper“去加载bean,由于在BeanDefinition集合中,拿到的是一个MapperFactoryBean,所以会触发MapperFactoryBean的getObject()方法。
  10. getObject()方法里,根据MapperFactoryBean的mapperInterface属性去knownMappers里拿MapperProxyFactory,拿到之后,调用newInstance()方法,newInstance()方法里,new了一个MapperProxy,这是代理对象的InvocationHandler。然后调用Proxy.newProxyInstance,返回一个真正的代理对象,所以我们获取的UserMapper的bean,是一个代理对象。
  11. 这里有一个问题,过程5-10,都是在ioc容器加载这一步,但是加载DAO类的bean需要依赖sqlSessionFactory,所以sqlSessionFactory的加载顺序一定要再普通的DAObean之前,mybatis是让MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport里有一个属性sqlSessionTemplate,SqlSessionTemplate里有sqlSessionFactory,所以无论是先加载的sqlSessionFactory还是普通的DAO类bean,都会先初始化sqlSessionFactory,保证DAO类的bean加载的时候,有sqlSessionFactory可用。
  12. 至此。ioc容器初始化完成。容器里,有一个单例的UserMapper的代理类(为什么是单例,可以去看一下MapperFactoryBean)
  13. 调用增删改查方法,实际上调用的是代理类MapperProxy的invoke()方法。在invoke方法里,new了一个PlainMethodInvoker,然后走PlainMethodInvoker的invoke()方法。首先,把namespace和方法名拼接在一起,去MapperStatements缓存里拿到具体的MapperStatement,根据增删改查的类型,去调用不同的jdbc方法,返回结果,这里PlainMethodInvoker会有缓存,可以自己去看一下。

总结:

  1. 在内存里,每一个DAO类,在容器初始化时,都会生成一个单例的代理对象(假设所有的DAO都被service依赖,不引用你写他干嘛呢)。
  2. 每一个mapper.xml被解析后,放到公共的knownMappers容器和公共的MapperStatements容器。
  3. 所有的类,共用一个sqlSessionFactory。
  4. 还有一些细节,可以去源码里看,流程只是一个大概框架,框架搭好了,自己去填充细节。

问题:

在debug源码的过程中,发现了以前没有关注过的一个点,BeanFactory的factoryBeanInstanceCache缓存。FactoryBean在getBean()的时候,factoryBean都有缓存的BeanWrapper。debug源码发现,是在第一个调用getBean方法的bean填充属性的时候,也就是populateBean()方法,填充bean的时候,根据类型去获取bean,如果这个bean是一个FactoryBean,还需要额外获取FactoryBean的具体类型去判断,这一步会从所有的FactoryBean里拿类型。ioc容器在这一步把所有的FactoryBean的BeanWrapper缓存了下来。

你可能感兴趣的:(浅谈mybatis的运行流程)