概述
本文首先对 FactoryBean 接口做简单得介绍,详细描述如何通过 FactoryBean 来自定义 Spring Bean, 然后在对 Spring 和 MyBatis 进行一个整合。 最后再说明 mybatis-spring 中 2.0 和 1.3 的实现。
FactoryBean
FactoryBean 主要是解决自定义创建 Bean 的问题,通常 Spring IOC 是通过反射来创建对象的。但是对于特别复杂的对象我们可以通过自己实现 FactoryBean#getObject 来自定义创建实例,比如:mybatis-spring 中的 SqlSessionFactoryBean 这个接口主要是就是用来拓展三方框架,或者是说他是 Spring 整合其他框架的基础。
- FactoryBean 在创建 bean 过程中如何调用的在
DefaultListableBeanFactory#preInstantiateSingletons方法我们先来看看。
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 1. 判断是否是 FactoryBean, 判断规则: 是否是实现 FactoryBean 接口
if (isFactoryBean(beanName)) {
//如果是一个 FactoryBean 那么就去获取 xxxFactoryBeans 实例, 它的 beanName = "&" + beanName
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
// eager: 急切的意思, isEagerInit 是否需要立马初始化
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
} else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
//提前初始化 xxxFactoryBean
getBean(beanName);
}
}
} else {
// 2. 不是工厂Bean, 就通过 getBean 来创建 Bean 实例
getBean(beanName);
}
}
这里其实有两个逻辑如果是 FactoryBean 首先会去创建一个 FactoryBean Bean 他的 beanName 是 "&" + beanName, 创建完成之后再去创建 Bean。
- 我们再来看下
FactoryBean#getObject调用的时机他是在AbstractBeanFactory#doGetBean的时候会调用getObjectForBeanInstance再去调getObjectFromFactoryBean然后调用doGetObjectFromFactoryBean最后调用factory.getObject()下面我贴一下核心代码方便阅读。
// doGetBean
Object sharedInstance = getSingleton(beanName); //Map<beanName, Object>
if (sharedInstance != null && args == null) { // Bean 存在
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 判断 sharedInstance 是不是 FactoryBean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
// getObjectForBeanInstance
// 如果不是 FactoryBean 直接返回
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
Object object = null;
if (mbd == null) {
// 缓存中获取
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
// 执行创建
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
// getObjectFromFactoryBean
object = doGetObjectFromFactoryBean(factory, beanName);
// doGetObjectFromFactoryBean
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
// 调用 FactoryBean的 getObject 方法
object = factory.getObject();
}
Spring 和 MyBatis 的整合(2.0.5)
- 配置依赖信息
compile(project(":spring-jdbc"))
// Spring 整合 Mybatis
// compile 'org.mybatis:mybatis-spring:1.3.2'
compile 'org.mybatis:mybatis-spring:2.0.5'
compile 'org.mybatis:mybatis:3.5.3'
compile "commons-dbcp:commons-dbcp:1.4"
compile 'mysql:mysql-connector-java:8.0.16'
- Spring Bean 配置信息
@Configuration
public class MyBatisConfig {
/**
* Session 工厂配置
*
* @param dataSource 数据源
* @return Session 工厂
* @throws Exception 异常信息
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean.getObject();
}
/**
* 数据源配置
*
* @return 数据源
*/
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://ip:3306/db_name?useSSL=false&characterEncoding=UTF-8&useUnicode=true&serverTimezone=Asia/Shanghai");
dataSource.setUsername("root");
dataSource.setPassword("---");
dataSource.setInitialSize(5);
dataSource.setMaxActive(10);
return dataSource;
}
/**
* 事务管理器配置
*
* @return 事务管理器
*/
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
- 主配置文件
@Configuration
@Import(MyBatisConfig.class)
@MapperScan(value = "cn.edu.xxx.mapper", annotationClass = Mapper.class)
public class AppConfig {
}
@MapperScan注解的定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
//...
}
MapperScannerRegistrar由于它实现了,BeanDefinitionRegistryPostProcessor主要是生成一个ClassPathMapperScanner对象。我们就来看看postProcessBeanDefinitionRegistry
// postProcessBeanDefinitionRegistry
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
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));
}
scanner.registerFilters();
//执行扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
ClassPathMapperScanner#scan扫描指定路径得到 Mapper Bean, 也就是说是一个个的FactoryBean
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
- 然后就通过
MapperFactoryBean#getObject中会通过 getMapper 生成一个代理对象。
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
sqlSession是通过SqlSessionFactory来产生的, 由于MapperFactoryBean的字段注入模式为 byType 那么,Spring 会自动调用 set 方法,setSqlSessionTemplate或者setSqlSessionFactory, 所以我们要先定义SqlSessionTemplate或者SqlSessionFactory的 Bean。
// setSqlSessionFactory
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
//setSqlSessionTemplate
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
- 如果定义的是一个 SqlSessionFactory 类型的 bean,那么最终也会被包装为一个 SqlSessionTemplate 对象,并且赋值给 sqlSession 属性。
10.而在 SqlSessionTemplate 类中就存在一个 getMapper 方法,这个方法中就会利用 SqlSessionFactory 来生成一个代理对象。
两个版本的实现
最后我们再来完整的整理下 mybatis-spring 1.x 和 2.x 的实现处理过程
mybatis-spring-2.0.5
- 通过 @MapperScan 导入了 MapperScannerRegistrar 类。
- MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口,所以 Spring 在启动时会调用MapperScannerRegistrar 类中的 registerBeanDefinitions 方法。
- registerBeanDefinitions 中生成了一个 MapperScannerConfigurer 类型的 BeanDefinition 。
- 而 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,所以 Spring 在初始化的时候,会去调用 MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry() 方法。
- 在 postProcessBeanDefinitionRegistry() 中生成一个 ClassPathMapperScanner 对象,然后进行扫描
- 通过利用 Spring 的扫描后,会把接口扫描出来并且得到对应的 BeanDefinition。
- 接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean,把 AutowireMode 修改为 byType。
- 扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean 了,相当于每个 Mapper 对应一个 FactoryBean(单例的)
- 在 MapperFactoryBean 中的 getObject 方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象
- sqlSession 对象是 Mybatis 中的,一个 sqlSession 对象需要 SqlSessionFactory 来产生
- MapperFactoryBean 的 AutowireMode 为byType,所以 Spring 会自动调用 set 方法,有两个 set 方法,一个setSqlSessionFactory,一个 setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以 Spring 容器中要存在 SqlSessionFactory 类型的 bean 或者 SqlSessionTemplate 类型的 bean。
- 如果你定义的是一个 SqlSessionFactory 类型的 bean,那么最终也会被包装为一个 SqlSessionTemplate 对象,并且赋值给 sqlSession 属性
- 而在 SqlSessionTemplate 类中就存在一个 getMapper 方法,这个方法中就会利用 SqlSessionFactory 来生成一个代理对象。
mybatis-spring-1.3.4
- 通过利用 Spring 的扫描后,会把接口扫描出来并且得到对应的 BeanDefinition。
- 接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean,把 AutowireMode 修改为 byType。
- 扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean了,相当于每个 Mapper 对应一个 FactoryBean(单例的)
- 在 MapperFactoryBean 中的 getObject 方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象。
- 同时因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会认为接口是备选者Component
- 后续流程和 2.0.5 一致。