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)
1. 配置依赖信息
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'
2. 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()); } }
3. 主配置文件
@Configuration @Import(MyBatisConfig.class) @MapperScan(value = "cn.edu.xxx.mapper", annotationClass = Mapper.class) public class AppConfig { }
4.@MapperScan
注解的定义
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { //... }
5.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)); }
6.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 一致。