如果项目中的ORM框架用的是Mybatis,只需要配置一下数据源,然后再定义一个mapper接口和一个xml文件就可以实现Java对象与数据库表之间的映射,从而可以实现对数据库的访问。使用起来非常简单,这得益于Mybatis强大的封装能力。下面我们分析一下Mybatis的核心源码,看看Mybatis是怎么实现的。本篇主要介绍Mapper的初始化。
Mapper是一个接口,需要有具体的实现类来完成它定义的功能,Mybatis给所有的Mapper创建了动态代理,这些动态代理是通过Spring提供的FactoryBean来创建的(如果你对FactoryBean不熟悉,可以看看笔者的另一篇博文:BeanFactory与FactoryBean的区别及设计思想)。
1 为Mapper注册bean
Mapper的bean注册采用的是Import模式,也就是需要在某个类上(配置类或启动类)加Import注解并指定注册器,由该注册器完成注册。Mybatis是定义了一个注解MapperScan,
在MapperScan上加了Import注解,并指定了MapperScannerRegistrar为Mapper的注册器。MapperScan这个注解加在Springboot项目的启动类上就可以完成Mapper的注册。
在为配置类(启动类也作为配置类来处理)创建bean时,会调用ConfigurationClassPostProcessor.processConfigBeanDefinitions()方法,在这里会为启动类创建一个bean并放入candidates里,
在ConfigurationClassParser.parse()方法里最终会去收集启动类的所有Import资源,
并将这些资源放入到Map里。
我们再回到ConfigurationClassPostProcessor.processConfigBeanDefinitions()方法,
图中红框里的方法最终会去调用ImportBeanDefinitionRegistrar.registerBeanDefinitions()方法。所以要想自定义注册器来实现注册bean,需要自定义的注册器实现ImportBeanDefinitionRegistrar接口。
下面我们重点看下MapperScannerRegistrar这个类:
该类实现了ImportBeanDefinitionRegistrar接口。在方法的第一行是获取MapperScan注解的元数据,其中就包含在启动类上加的包名。
这个包名指定了Mapper所在的路径。
在方法的最后一行调用了ClassPathMapperScanner.doScan()方法
在这里会扫描包下的所有Mapper并生成bean。在processBeanDefinitions()方法里有两个比较重要的地方,
首先是将bean的构造器参数设为了Mapper类,然后是把bean的beanClass改为了MapperFactoryBean。下面会给bean设置一些属性,如sqlSessionFactory、sqlSessionTemplate等。
我们再看看MapperFactoryBean,这里有个构造器需要传入Mapper类。也就是说BeanFactory在为该bean创建实例的时候生成的是MapperFactoryBean实例。假设现在有个XxxMapper,BeanFactory会为这个Mapper生成一个BeanDefinition,而这个BeanDefinition的beanClass是MapperFactoryBean,最终创建的实例也是MapperFactoryBean的实例。如果是这样肯定满足不了我们的需求,我们需要的是Mapper的实例,下面我们就来看下BeanFactory是怎么处理MapperFactoryBean实例的。
2 给Mapper生成动态代理
MapperFactoryBean实现了FactoryBean,FactoryBean是一种能生产bean的bean。
在AbstractBeanFactory.getObjectForBeanInstance()方法里会判断bean实例是否是FactoryBean实例(如果beanName的前缀是以“&”开头会直接返回bean实例),如果是,会调用FactoryBean.getObject()方法获取实例。
在MapperFactoryBean里是通过sqlSessionTemplate来获取的,在上面已经说到为Mapper生成bean时设置了sqlSessionFactory、sqlSessionTemplate等属性。最终是调用MapperProxyFactory.newInstance()来为每个Mapper生成一个动态代理实例。
3 总结
以上简要介绍了Mapper初始化的过程,在为Mapper生成bean时将beanClass替换为了MapperFactoryBean。在获取实例的时候会判断bean实例是不是FactoryBean类型的,如果是再调用getObject方法为每个Mapper创建一个动态代理实例。