我们在使用Mybatis的时候,通常会在Mapper接口上添加@Mapper注解,或者为了方便而使用@MapperScan注解。接下来分别看看这两种注解是如何实现相关mapper的bean注册的。
@Mapper注解
Mapper bean的注册
我们在业务代码中可以通过@Autowired注解注入mybatis 的 mapper,那么这个mapper一定是由spring容器来管理的一个bean definition。
问题是@Mapper是 mybatis的注解,spring是如何知道要把它注册为bean的呢?
首先定位到org.mybatis.spring.boot.autoconfigure包中的顶层配置类 MybatisAutoConfiguration,其中有几个关键信息:
1)@Bean方法 public SqlSessionFactory sqlSessionFactory(DataSource dataSource),生成SqlSessionFactory;
2)@Bean方法 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory),生成SqlSessionTemplate;
3)静态内部类public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar
注意它实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法,也就意味着应该会有一个配置类通过@Import的方法引入这个类,使这个类得到解析。
另外,它的registerBeanDefinitions方法中,注册的bean definition的类型是 MapperScannerConfigurer,并且设置了PropertyValue,比如要扫描的注解是@Mapper,扫描的basePackage默认与启动类的@ComponentScan相同。
再看看MapperScannerConfigurer这个类,它实现了BeanDefinitionRegistryPostProcessor,大概就可以猜到,在注册bean definition的过程中,会使用到这个PostProcessor来进行mapper扫描和bean注册。
4)静态内部类public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean,
这是个配置类,并且使用了注解@Import(AutoConfiguredMapperScannerRegistrar.class),果然这里就引入了刚才的AutoConfiguredMapperScannerRegistrar这个注册器。
到这里,mapper bean注册的初步工作就比较明朗了:
1)解析配置类MybatisAutoConfiguration -> 解析内部配置类MapperScannerRegistrarNotFoundConfiguration -> 解析注册器AutoConfiguredMapperScannerRegistrar,实例化并绑定到引入它的配置类;
2)处理配置类MybatisAutoConfiguration,解析@Bean方法,注册两个bean definition:sqlSessionFactory 和 sqlSessionTemplate;
3)处理配置类MapperScannerRegistrarNotFoundConfiguration,使用引入的注册器AutoConfiguredMapperScannerRegistrar,调用重写方法,注册bean difinition:mapperScannerConfigurer;
4)在后续调用BeanDefinitionRegistryPostProcessor过程中,实例化了mapperScannerConfigurer,调用其postProcessBeanDefinitionRegistry方法。
接下来看看postProcessBeanDefinitionRegistry方法:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//通常加载属性配置所使用的的PropertyResourceConfigurer,实现的是BeanFactoryPostProcessor,而BeanFactoryPostProcessor的处理在BeanDefinitionRegistryPostProcessor之后,所以这里需要手动地进行一次提前处理使其生效。
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
//创建一个ClassPathMapperScanner,继承自spring的ClassPathBeanDefinitionScanner类,调用scan方法扫描@Mapper注解并注册bean definition,然后设置beanClass属性为org.mybatis.spring.mapper.MapperFactoryBean,设置bean definition的constructorArgumentValues为当前接口名。
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
这里使用mapperScannerConfigurer,完成了@Mapper注解的扫描和mapper bean的注册,并且设置beanClass属性为org.mybatis.spring.mapper.MapperFactoryBean(和MapperFactoryBean关联起来),设置bean definition的constructorArgumentValues为当前接口名(后续实例化的时候用),设置自动注入类型为AUTOWIRE_BY_TYPE(后续初始化的时候用)。
到这里mapper bean的注册就完成了。
Mapper bean的实例化
我们业务中的mapper是接口类型,要将它实例化然后调用其中的方法,需要找到一个实现类。
目前我们知道的,与具体类有关联的地方,就是前面在mapper bean注册的时候,将beanClass属性为org.mybatis.spring.mapper.MapperFactoryBean。
MapperFactoryBean是一个泛型类,实现了 FactoryBean<T>接口,表示它是一个工厂bean,它的作用是创建mapper的实现类。
源码如下:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
public boolean isAddToConfig() {
return addToConfig;
}
}
MapperFactoryBean类里面有构造方法,有重写的getObject()、getObjectType()等方法。
spring在实例化mapper bean 或者获取类型的时候,一旦发现它的beanClass是MapperFactoryBean这个工厂bean,就会先完成MapperFactoryBean的实例化和初始化,然后通过 getObject()来获取mapper bean对象,或者通过getObjectType()来获取mapper bean的类型。
1、MapperFactoryBean的实例化
MapperFactoryBean有两个构造函数:
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
由于之前mapper bean definition中设置了constructorArgumentValues,将当前mapper接口的Class对象作为参数值添加了进去;所以在使用构造函数实例化的时候,会根据设置的构造函数参数,选择下面这个含参的构造函数,完成工厂bean的实例化。
2、MapperFactoryBean的初始化
初始化的第一个重要方法,populateBean,属性填充。
之前mapper bean definition中设置了自动注入类型为AUTOWIRE_BY_TYPE,在初始化的时候,会走到autowireByType方法中对其PropertyValues进行一次填充。
autowireByType方法中主要做了两件事情:
1)使用java的内省机制(基于反射),也就是java.beans包下的Introspector类的getBeanInfo方法,获取到MapperFactoryBean的 BeanInfo。
关键方法调用链:
unsatisfiedNonSimpleProperties -> getPropertyDescriptors -> new CachedIntrospectionResults(beanClass) -> getBeanInfo(beanClass) -> Introspector.getBeanInfo
BeanInfo包含了java bean的基本信息,包括PropertyDescriptor(属性描述符)、BeanDescriptor(bean描述符)、MethodDescriptor(方法描述符)、EventSetDescriptor(事件描述符)等。
其中我们进行属性填充,需要拿到的是PropertyDescriptor,具体逻辑是:
- 如果有父类,先拿到父类BeanInfo中的PropertyDescriptor,放进结果列表中;
- 遍历MapperFactoryBean类的所有public方法(包括继承的和自己定义的),判断方法名是否是 getXXX、isXXX 或 setXXX,然后将XXX作为属性,组装PropertyDescriptor,放进结果列表中;
- 处理结果列表,进行合并。
利用Introspector内省器我们拿到了初步结果:
这里拿到MapperFactoryBean类的PropertyDescriptor之后,为了保险起见,比如接口的default方法也可能用到setter/getter等名称,又对MapperFactoryBean类所实现的接口、以及其所有父类实现的接口都进行了内省。
可以看到现在拿到的所有PropertyDescriptor,有些明显不是我们属性注入所需要的,这里spring对其进行了过滤,最终只剩下两个:sqlSessionFactory 和 sqlSessionTemplate。
2)遍历PropertyDescriptor,调用bean factory的resolveDependency方法,解决setter方法的参数依赖。
这里其实就是实例化了sqlSessionFactory 和 sqlSessionTemplate,并将他们添加到MapperFactoryBean的PropertyValues中。
初始化的第二个重要方法,initializeBean,完成初始化,得到bean对象。
这里包含四个步骤:
invokeAwareMethods、applyBeanPostProcessorsBeforeInitialization、invokeInitMethods、applyBeanPostProcessorsAfterInitialization
其中我们本次要关注的是invokeInitMethods,它里面检测了当前bean是否实现了InitializingBean接口,如果是则调用其afterPropertiesSet方法。
显然,MapperFactoryBean实现了该接口,并且在它的afterPropertiesSet方法中,又调用checkDaoConfig,将mapperInterface成员变量,也就是我们业务中使用的mapper接口添加到了sqlSessionFactory.Configuration.MapperRegistry.knownMappers属性中,knownMappers的结构为 Map<Class<?>, MapperProxyFactory<?>>。
代码如下:
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
//拿到内部sqlSessionFactory的Configuration对象
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
//把当前接口放到configuration的mapperRegistry属性中
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
到这里,MapperFactoryBean这个工厂bean的实例化和初始化就完成了,而且现在工厂bean、mapper接口、sqlSessionFactory、
sqlSessionTemplate之间已经建立了联系,最后就是通过MapperFactoryBean对象的getObject()方法拿到mapper接口的实现类对象了。
3、生成mapper bean实例
到现在为止,我们要创建的mapper bean对象,实际是创建了它对应的工厂bean实例,但是工厂bean并不是给我们最终需要的,因为它的作用是为我们生成各种不同mapper接口的实现类对象,这个生成的对象才是我们最终使用的。
所以在createBean创建完工厂bean实例后,会调用getObjectForBeanInstance方法,最终调用的是工厂bean的getObject()方法,拿到mapper bean对象,如下图:
getObject():
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
该方法的调用链拆解如下:
1)MapperFactoryBean.getSqlSessionTemplate.getSqlSessionFactory.getConfiguration.getMapperRegistry.getMapperProxyFactory(MapperFactoryBean.mapperInterface)
2)MapperProxyFactory.newInstance(MapperFactoryBean.sqlSessionTemplate)
简单来说就是在MapperFactoryBean的内部拿到当前mapper接口对应的MapperProxyFactory类对象,然后调用newInstance方法创建mapper接口的代理类对象。
接着看newInstance方法:
protected T newInstance(MapperProxy<T> mapperProxy) {
//参数:代理类的类加载器、要实现的接口、调用处理器。
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这里首先创建了一个MapperProxy<T>调用处理器类对象,这个类实现了InvocationHandler接口,重写了invoke方法实现代理功能;
然后使用Proxy.newProxyInstance方法,创建了最终的代理对象实例。
由于涉及到的类比较多,这里统一梳理一下各个类的功能:
SqlSession,接口,可以理解为与数据库的连接,定义了select、insert等各种执行sql语句的方法,以及getConfiguration、getMapper等用于内部协作的get方法。
DefaultSqlSession,类,实现SqlSession接口,内部属性包含Configuration(配置)、Executor(执行器,执行具体sql)。
SqlSessionFactory,接口,用来创建SqlSession。
DefaultSqlSessionFactory,类,实现SqlSessionFactory接口,内部属性包含Configuration类对象。
SqlSessionTemplate,类,实现SqlSession接口,同时封装SqlSessionFactory及其他关联对象。
MapperFactoryBean,类,工厂bean,重写getObject()方法创建mapper代理对象;内部属性包含SqlSessionTemplate类对象、表示对应mapper接口的Class对象。
MapperProxyFactory,mapper代理对象工厂类,承接具体的mapper代理对象创建工作(使用Proxy类的动态代理机制)。
MapperProxy,调用处理器类,实现InvocationHandler接口,重写invoke方法,完成mapper功能的具体实现,作为Proxy.newProxyInstance方法入参。
到这里,mapper bean的实例化就完成了,实际是创建了一个代理类对象。
当业务代码走到类似 bookMapper.selectByExample(bookExample); 的语句时,就进入了代理对象的invoke方法,然后由sqlSessionFactory创建一个sqlSession,通过sqlSession中的executor来执行sql语句(最终还是构建了JDBC的PreparedStatement,调用了execute()方法)。
@MapperScan注解
除了在mapper接口上添加@Mapper注解,还可以在启动类(主配置类)上添加@MapperScan(value = “org.example.dao”)注解,让程序在指定路径下自动扫描。
点开@MapperScan注解,可以看到内部包含了@Import(MapperScannerRegistrar.class)注解,它引入了一个MapperScanner注册器。
再看下MapperScannerRegistrar这个类,它实现了ImportBeanDefinitionRegistrar接口,也就意味着解析到MapperScannerRegistrar时,会将这个注册器绑定到启动类,后面处理启动类时再通过该注册器的registerBeanDefinitions方法注册一个bean definition。
再看下registerBeanDefinitions方法,注册的bean类型正是MapperScannerConfigurer,和之前处理@Mapper注解时注册的一样,只不过这里少了像annotationClass = Mapper.class 这样的PropertyValue。
后面和@Mapper注解的处理类似,调用MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry方法,创建ClassPathMapperScanner,扫描、解析路径下的接口,注册mapper bean definition。
参考文档:http://www.mybatis.cn/2182.html(Mybatis中文官网)