MyBatis源码分析(七)MyBatis与Spring的整合原理与源码分析
文章目录
- 写在前面
- 一、SqlSessionFactoryBean配置SqlSessionFactory
- 1、初识SqlSessionFactoryBean
- 2、实现ApplicationListener
- 3、实现InitializingBean接口
- 4、实现FactoryBean接口
- 5、构建SqlSessionFactory
- 二、SqlSessionTemplate
- 1、初始SqlSessionTemplate
- 2、SqlSessionTemplate的构造方法
- 3、SqlSessionInterceptor执行核心方法
- (1)获取SqlSession
- (2)关闭SqlSession
- 4、总结
- 三、@MapperScan扫描注册的Mapper
- 1、@MapperScan
- 2、MapperScannerRegistrar
- 3、MapperScannerConfigurer
- (1)processBeanDefinitions
- 4、MapperFactoryBean
- 四、总结
- 写在后面
写在前面
MyBatis与Spring的整合,依赖于mybatis-spring包。
其中的设计非常的巧妙,并且与Spring完美结合,我们一起来完整分析一下MyBatis是怎么与Spring整合的。
一、SqlSessionFactoryBean配置SqlSessionFactory
1、初识SqlSessionFactoryBean
SqlSessionFactoryBean是MyBatis与Spring整合的关键,它用于创建MyBatis的SqlSessionFactory。
SqlSessionFactoryBean实现了FactoryBean、InitializingBean和ApplicationListener,这都是Spring的核心接口。
public class SqlSessionFactoryBeanimplements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
并且,SqlSessionFactoryBean中包含了所有的MyBatis的配置项,包括但不限于数据源、事务管理工厂、环境、拦截器、TypeHandler、别名、缓存等等。并且提供了这些属性的get和set方法,开发人员可以随心所欲地对这些属性进行配置。
private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();private Resource configLocation;private Configuration configuration;private Resource[] mapperLocations;private DataSource dataSource;private TransactionFactory transactionFactory;private Properties configurationProperties;private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();private SqlSessionFactory sqlSessionFactory;// EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName();private boolean failFast;private Interceptor[] plugins;private TypeHandler<?>[] typeHandlers;private String typeHandlersPackage;private Class<?>[] typeAliases;private String typeAliasesPackage;private Class<?> typeAliasesSuperType;private LanguageDriver[] scriptingLanguageDrivers;private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;// issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;private Class<? extends VFS> vfs;private Cache cache;private ObjectFactory objectFactory;private ObjectWrapperFactory objectWrapperFactory;
2、实现ApplicationListener
Spring的事件监听器,关于Spring的事件监听器请移步:
Spring事件详解,Spring-Event源码详解,一文搞透Spring事件管理
重写了onApplicationEvent方法:
// org.mybatis.spring.SqlSessionFactoryBean#onApplicationEvent
@Override
public void onApplicationEvent(ApplicationEvent event) {if (failFast && event instanceof ContextRefreshedEvent) {// fail-fast -> check all statements are completedthis.sqlSessionFactory.getConfiguration().getMappedStatementNames();}
}
主要是做了快速失败语句验证。
3、实现InitializingBean接口
在Spring的Bean生命周期中,一个Bean实现了InitializingBean接口,会在Bean的初始化过程中调用其afterPropertiesSet方法:
// org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
@Override
public void afterPropertiesSet() throws Exception {// 校验notNull(dataSource, "Property 'dataSource' is required");notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),"Property 'configuration' and 'configLocation' can not specified with together");// 关键方法!构造SqlSessionFactorythis.sqlSessionFactory = buildSqlSessionFactory();
}
4、实现FactoryBean接口
FactoryBean接口是Spring-bean的创建工厂,SpringIOC的第三级缓存中存储的就是FactoryBean,其getObject方法默认就会公开单例实例或创建一个新的原型实例,以下是Spring中FactoryBean的默认实现:
// org.springframework.beans.factory.config.AbstractFactoryBean#getObject
@Override
public final T getObject() throws Exception {if (isSingleton()) {return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());}else {return createInstance();}
}
而此处的SqlSessionFactoryBean重写了getObject方法,直接返回一个SqlSessionFactory(若没有,走创建逻辑)。相当于getObject逻辑不需要Spring来掌控,交给自己来做,生成一个SqlSessionFactory的实例。
// org.mybatis.spring.SqlSessionFactoryBean#getObject
@Override
public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();}return this.sqlSessionFactory;
}
5、构建SqlSessionFactory
在buildSqlSessionFactory方法中,包含着对SqlSessionFactory的创建逻辑:
//
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {final Configuration targetConfiguration;// 创建一个MyBatis的ConfigurationXMLConfigBuilder xmlConfigBuilder = null;if (this.configuration != null) {targetConfiguration = this.configuration;if (targetConfiguration.getVariables() == null) {targetConfiguration.setVariables(this.configurationProperties);} else if (this.configurationProperties != null) {targetConfiguration.getVariables().putAll(this.configurationProperties);}} else if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);targetConfiguration = xmlConfigBuilder.getConfiguration();} else {LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");targetConfiguration = new Configuration();Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);}Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);if (hasLength(this.typeAliasesPackage)) {scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream().filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()).filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);}if (!isEmpty(this.typeAliases)) {Stream.of(this.typeAliases).forEach(typeAlias -> {targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");});}if (!isEmpty(this.plugins)) {Stream.of(this.plugins).forEach(plugin -> {targetConfiguration.addInterceptor(plugin);LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");});}if (hasLength(this.typeHandlersPackage)) {scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())).forEach(targetConfiguration.getTypeHandlerRegistry()::register);}if (!isEmpty(this.typeHandlers)) {Stream.of(this.typeHandlers).forEach(typeHandler -> {targetConfiguration.getTypeHandlerRegistry().register(typeHandler);LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");});}if (!isEmpty(this.scriptingLanguageDrivers)) {Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {targetConfiguration.getLanguageRegistry().register(languageDriver);LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");});}Optional.ofNullable(this.defaultScriptingLanguageDriver).ifPresent(targetConfiguration::setDefaultScriptingLanguage);if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmlstry {targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException e) {throw new NestedIOException("Failed getting a databaseId", e);}}Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);if (xmlConfigBuilder != null) {try {xmlConfigBuilder.parse();LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");} catch (Exception ex) {throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);} finally {ErrorContext.instance().reset();}}targetConfiguration.setEnvironment(new Environment(this.environment,this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,this.dataSource));if (this.mapperLocations != null) {if (this.mapperLocations.length == 0) {LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");} else {for (Resource mapperLocation : this.mapperLocations) {if (mapperLocation == null) {continue;}try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());xmlMapperBuilder.parse();} catch (Exception e) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);} finally {ErrorContext.instance().reset();}LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");}}} else {LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");}return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
我们可以看出,会将当前SqlSessionFactoryBean中的所有用户加载的配置,都在这里加载到MyBatis的Configuration中,并且调用sqlSessionFactoryBuilder的build方法进行构建SqlSessionFactory,而这个操作也正是MyBatis的标准构建SqlSessionFactoryAPI。
二、SqlSessionTemplate
1、初始SqlSessionTemplate
MyBatis为了与Spring整合,提供了一个SqlSessionTemplate来管理SqlSession,通过代理SqlSession的方式将其与SqlSession进行关联:
public class SqlSessionTemplate implements SqlSession, DisposableBean {private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;private final SqlSession sqlSessionProxy;private final PersistenceExceptionTranslator exceptionTranslator;
SqlSessionTemplate 实现了SqlSession,并重写了SqlSession的接口,而SqlSession的这些接口,全都是通过sqlSessionProxy属性进行代理:
@Override
public <E> List<E> selectList(String statement) {return this.sqlSessionProxy.selectList(statement);
}/*** {@inheritDoc}*/
@Override
public <E> List<E> selectList(String statement, Object parameter) {return this.sqlSessionProxy.selectList(statement, parameter);
}/*** {@inheritDoc}*/
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
}// 等等都是这样处理的
2、SqlSessionTemplate的构造方法
在创建SqlSessionTemplate时,会执行该构造方法:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");notNull(executorType, "Property 'executorType' is required");this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;this.exceptionTranslator = exceptionTranslator;this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
最终通过JDK动态代理的方式,创建SqlSession的代理,并且创建一个SqlSessionInterceptor处理器。
3、SqlSessionInterceptor执行核心方法
SqlSessionInterceptor 实现了InvocationHandler,也就是说调用SqlSession的方法时,会执行SqlSessionInterceptor 的invoke方法:
private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 获取SqlSessionSqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);try {// 执行核心方法(select、update、delete等)Object result = method.invoke(sqlSession, args);if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// force commit even on non-dirty sessions because some databases require// a commit/rollback before calling close()sqlSession.commit(true);}return result;} catch (Throwable t) {Throwable unwrapped = unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {// release the connection to avoid a deadlock if the translator is no loaded. See issue #22closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);sqlSession = null;Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);if (translated != null) {unwrapped = translated;}}throw unwrapped;} finally {if (sqlSession != null) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}
}
(1)获取SqlSession
获取SqlSession的主逻辑和MyBatis的API没什么不同,只不过此处会通过TransactionSynchronizationManager来缓存SqlSession。
// org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);// ThreadLocal的线程持有者,从事务管理器中获取SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);SqlSession session = sessionHolder(executorType, holder);if (session != null) {return session;}// 如果没有,创建一个新的SqlSessionLOGGER.debug(() -> "Creating a new SqlSession");session = sessionFactory.openSession(executorType);// 存储SqlSession到ThreadLocalregisterSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;
}
TransactionSynchronizationManager的getResource方法,最终的resource是一个ThreadLocal,我们可以得出结论,同一个线程获取的SqlSession是同一个,不同线程获取的SqlSession是不同的:
private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");// org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource
@Nullable
private static Object doGetResource(Object actualKey) {Map<Object, Object> map = resources.get();if (map == null) {return null;}Object value = map.get(actualKey);// Transparently remove ResourceHolder that was marked as void...if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {map.remove(actualKey);// Remove entire ThreadLocal if empty...if (map.isEmpty()) {resources.remove();}value = null;}return value;
}
在registerSessionHolder逻辑中,只有开启了事务、并且事务工厂是SpringManagedTransactionFactory,才会缓存SqlSession:
// org.mybatis.spring.SqlSessionUtils#registerSessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {SqlSessionHolder holder;// 如果当前线程的事务同步是活动的,则返回。if (TransactionSynchronizationManager.isSynchronizationActive()) {Environment environment = sessionFactory.getConfiguration().getEnvironment();if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");holder = new SqlSessionHolder(session, executorType, exceptionTranslator);TransactionSynchronizationManager.bindResource(sessionFactory, holder);TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));holder.setSynchronizedWithTransaction(true);holder.requested();} else {if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {LOGGER.debug(() -> "SqlSession [" + session+ "] was not registered for synchronization because DataSource is not transactional");} else {throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");}}} else {LOGGER.debug(() -> "SqlSession [" + session+ "] was not registered for synchronization because synchronization is not active");}}
(2)关闭SqlSession
关闭SqlSession同样会先从ThreadLocal中释放,然后调用SqlSession的close方法
// org.mybatis.spring.SqlSessionUtils#closeSqlSession
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {notNull(session, NO_SQL_SESSION_SPECIFIED);notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);if ((holder != null) && (holder.getSqlSession() == session)) {LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");holder.released();} else {LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");session.close();}
}
4、总结
也就是说,通过代理SqlSession,每次执行SqlSession的select、update等方法时,会先执行获取SqlSession的方法,然后再调用select方法。
也就是说,我们做的sqlSessionFactory.openSession这个操作,交给框架的代理做了。
三、@MapperScan扫描注册的Mapper
1、@MapperScan
@MapperScan用于扫描Mapper所在的包,注册了一个MapperScannerRegistrar
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
2、MapperScannerRegistrar
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,在registerBeanDefinitions方法中,进行了MapperScannerConfigurer的注册:
// org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,generateBaseBeanName(importingClassMetadata, 0));}
}void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,BeanDefinitionRegistry registry, String beanName) {// 定义一个MapperScannerConfigurer的bean,并且设置许多参数BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {builder.addPropertyValue("annotationClass", annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {builder.addPropertyValue("markerInterface", markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);}String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");if (StringUtils.hasText(sqlSessionTemplateRef)) {builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));}String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");if (StringUtils.hasText(sqlSessionFactoryRef)) {builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));}List<String> basePackages = new ArrayList<>();basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));if (basePackages.isEmpty()) {basePackages.add(getDefaultBasePackage(annoMeta));}String lazyInitialization = annoAttrs.getString("lazyInitialization");if (StringUtils.hasText(lazyInitialization)) {builder.addPropertyValue("lazyInitialization", lazyInitialization);}builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}
3、MapperScannerConfigurer
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在其postProcessBeanDefinitionRegistry方法中,创建了ClassPathMapperScanner 进行了扫描和注册:
// org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
@Override
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));
}
// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
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);
}
// org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {// Spring的扫描+Bean的注册Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)+ "' package. Please check your configuration.");} else {// Mapper的最终属性调整过程processBeanDefinitions(beanDefinitions);}return beanDefinitions;
}
(1)processBeanDefinitions
在processBeanDefinitions方法中,对BeanDefinition进行属性调整,关键逻辑-设置Bean的Class为MapperFactoryBean,因为Mapper都是接口无法实例化,所以只能通过MapperFactoryBean来进行实例化。
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;// org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName+ "' mapperInterface");// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBeandefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59definition.setBeanClass(this.mapperFactoryBeanClass); // 设置Bean的Classdefinition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory",new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate",new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}definition.setLazyInit(lazyInitialization);}
}
4、MapperFactoryBean
MapperFactoryBean实现了FactoryBean,同样重写了getObject方法:
// org.mybatis.spring.mapper.MapperFactoryBean#getObject
@Override
public T getObject() throws Exception {// return getSqlSession().getMapper(this.mapperInterface);
}
看到这,我们是不是眼前一亮!
正是这里,获取了SqlSession,并且调用了getMapper方法获取了这个接口!
这个Mapper接口注册到Spring容器中,并通过这种方式获取。
四、总结
到此,MyBatis与Spring的整合全流程,我们已经彻底分析完毕了。
是不是让人感叹,学好源码真不容易,看大佬写的框架真实太精彩了!
写在后面
如果本文对你有帮助,请点赞收藏关注一下吧 ~