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的整合全流程,我们已经彻底分析完毕了。
是不是让人感叹,学好源码真不容易,看大佬写的框架真实太精彩了!
写在后面
如果本文对你有帮助,请点赞收藏关注一下吧 ~
相关文章:
MyBatis源码分析(七)MyBatis与Spring的整合原理与源码分析
文章目录写在前面一、SqlSessionFactoryBean配置SqlSessionFactory1、初识SqlSessionFactoryBean2、实现ApplicationListener3、实现InitializingBean接口4、实现FactoryBean接口5、构建SqlSessionFactory二、SqlSessionTemplate1、初始SqlSessionTemplate2、SqlSessionTemplat…...
基于声网 Flutter SDK 实现多人视频通话
前言 本文是由声网社区的开发者“小猿”撰写的Flutter基础教程系列中的第一篇。本文除了讲述实现多人视频通话的过程,还有一些 Flutter 开发方面的知识点。该系列将基于声网 Fluttter SDK 实现视频通话、互动直播,并尝试虚拟背景等更多功能的实现。 如果…...
IT服务管理(ITSM) 中的大数据
当我们谈论IT服务管理(ITSM)领域的大数据时,我们谈论的是关于两件不同的事情: IT 为业务提供的大数据工具/服务 - 对业务运营数据进行数字处理。IT 运营中的大数据 – 处理和利用复杂的 IT 运营数据。 面向业务运营的大数据服务…...
Validator校验之ValidatorUtils
注意:hibernate-validator 与 持久层框架 hibernate 没有什么关系,hibernate-validator 是 hibernate 组织下的一个开源项目 。 hibernate-validator 是 JSR 380(Bean Validation 2.0)、JSR 303(Bean Validation 1.0&…...
C++---背包模型---采药(每日一道算法2023.3.7)
注意事项: 本题是"动态规划—01背包"的扩展题,dp和优化思路不多赘述。 题目: 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。 为此,他想拜附近最有威望的医师为师。 医师为了判断他的资质&…...
Java各种锁
目录 一、读写锁(ReentrantReadWriteLock) 二、非公平锁(synchronized/ReentrantLock) 三、可重入锁/递归锁(synchronized/ReentrantLock) 四、自旋锁(spinlock) 五、乐观锁/悲观锁 六、死锁 1、死锁代码 2、死锁的检测(jps -l 与 jstack 进程号) 七、sychronized-wait…...
TryHackMe-Tardigrade(应急响应)
Tardigrade 您能否在此 Linux 端点中找到所有基本的持久性机制? 服务器已遭到入侵,安全团队已决定隔离计算机,直到对其进行彻底清理。事件响应团队的初步检查显示,有五个不同的后门。你的工作是在发出信号以使服务器恢复生产之前…...
导出GIS | 将EXCEL表格中坐标导出成GIS格式文件
一 前言 EXCEL是我们日常工作学习数据处理的办公软件,操作易上手,几乎人人都会用。EXCEL表格能够处理各种数据,包括经纬度坐标数据,地址数据等等。 有时因工作需要需将表格中地址数据处理为GIS格式的文件,以便能够将数…...
new set数组对象去重失败
我们知道Set是JS的一个种新的数据结构,和数组类似,和数组不同的是它可以去重,比如存入两个1或两个"123",只有1条数据会存入成功,但有个特殊情况,如果添加到set的值是引用类型,比如数组…...
Acwing: 一道关于线段树的好题(有助于全面理解线段树)
题目链接🔗:2643. 序列操作 - AcWing题库 前驱知识:需要理解线段树的结构和程序基本框架、以及懒标记的操作。 题目描述 题目分析 对区间在线进行修改和查询,一般就是用线段树来解决,观察到题目一共有五个操作&…...
DD-1/40 10-40mA型【接地继电器】
系列型号: DD-1/40接地继电器 DD-1/50接地继电器 DD-1/60接地继电器 一、 用途及工作原理 DD-1型接地继电器为瞬时动作的过电流继电器,用作小电流接地电力系统高电压三相交流发电机和电动机的接地零序过电流保护。继电器线圈接零序电流互感器(电缆式、母…...
【女神节】简单使用C/C++和Python嵌套for循环生成一个小爱心
目录 前言实现分析代码实现代码如下效果如下优化效果代码如下效果如下总结尾叙前言 女神节马上到了,有女朋友的小伙伴是不是已经精心准好礼物了呢!对于已婚男士,是不是整愁今天又该送什么礼物呢!说真的,我也整愁着,有什么要推荐么,评论留言下! 实现分析 可以先在纸上或…...
Biome-BGC生态系统模型与Python融合技术实践应用
查看原文>>> Biome-BGC生态系统模型与Python融合技术实践应用 Biome-BGC是利用站点描述数据、气象数据和植被生理生态参数,模拟日尺度碳、水和氮通量的有效模型,其研究的空间尺度可以从点尺度扩展到陆地生态系统。 在Biome-BGC模型中…...
ESP32 GPIO使用
ESP32 GPIO使用 #define GPIO_OUT_PIN 2 //定义引脚号 #define GPIO_OUTPUT_PIN_SEL (1<<GPIO_OUT_PIN) //定义输出引脚的宏,用来将输出引脚号转换为位掩码void bsp_gpio_init(){gpio_config_t io_conf;io_conf.pin_bit_mask GPIO_OUTPUT_PIN_SE…...
JavaScript 高级4 :正则表达式
JavaScript 高级4 :正则表达式 Date: January 19, 2023 Text: 正则表达式、正则表达式特殊字符、正则表达式中的替换 目标: 能够说出正则表达式的作用 能够写出简单的正则表达式 能够使用正则表达式对表单进行验证 能够使用正则表达式替换内容 正则…...
如何让AI帮你干活-娱乐(3)
背景今天的话题会偏代码技巧一些,对于以前没有接触过代码的朋友或者接触代码开发经验较少的朋友会有些吃力。上篇文章介绍了如何广视角的生成相对稳定的视频。昨天的实现相对简单,主要用的是UI界面来做生成。但是生成的效果其实也显而易见,不…...
webview的工作、内存泄漏、漏洞以及缓存机制原理原理+方案解决
分析一段appium的日志来分析webview的工作原理,文章尾部附有自动化脚本及完整日志: 解析: 获取上下文列表 服务端发送命令adb shell cat /proc/net/unix获取域套接字列表。那什么是域套接字呢? 域套接字:是unix系统里…...
BFD协议原理
BFD协议原理引入背景不使用BFD带来的问题OSPF感知慢VRRP产生次优路径BFD技术简介BFD会话建立方式和检测机制BFD会话建立过程BFD工作流程BFD的单臂回声BFD默认参数以及调整方法总结引入背景 随着网络应用的广泛部署,网络发生中断可能影响业务正常运行并造成重大损失…...
你把骑行当什么? 它就是你需要的
1.骑行是一种有活力的运动,尝试一下你一定会喜欢上它的!2.把骑行当作一种娱乐,让自己快乐地体验自然的美!3.骑行可以帮助你改变心态,让你的心情变得更加愉悦!4.让骑行成为你每天的计划,看看骑行…...
python基础系列 —— 迭代器与内置高阶函数
目录 一、迭代器 1、基本概念 2、如何定义一个迭代器 3、如果判断对象是否是迭代器 4、如何重置迭代器 5、如何调用迭代器 二、高阶函数 1、map函数 2、filter函数 3、reduce函数 4、sorted函数 一、迭代器 1、基本概念 迭代:是一个重复的过程,每次重复…...
MySQL面试题-日志
目录 1.MySQL 中常见的日志有哪些? 2.慢查询日志有什么用? 3.binlog 主要记录了什么? 4.Mysql的binlog有几种录入格式?分别有什么区别? 5.redo log 如何保证事务的持久性? 6.页修改之后为什么不直接刷…...
Android 10.0 去掉Launcher3默认给 icon增加的APK图标白边
1.概述 在10.0的系统产品开发中,Launcher3定制化开发中,发现在给第三方app的icon绘制图标的时候,会有白边第三方app的图标没有完全绘制出来,而系统app不存在这个问题,是完全绘制出来的,所以需要分析图标绘制类来解决这个问题 2.去掉Launcher3默认给 icon增加的APK图标白…...
E900V21C(S905L-armbian)安装armbian-Ubuntu(WiFi)
基本上是s905L芯片的刷机都是如此,包括Q7等 在网上寻找好多的教程关于e900v21c的刷机包和教程都少的可怜,唯一的就是这个:山东联通版创维E900V21C盒子刷入Armbiam并安装宝塔和Docker,但他是不能用WiFi和蓝牙的然后就是寻找s90l的…...
tpc协议的3次握手和4次挥手
建立连接的3次握手过程: A: 我想和你建立连接,你收到我的请求吗?(我想娶你) B: 好的,我收到了你的请求,我们可以建立连接,我同意。(好的,我愿意嫁给你) A: 好的,我收到了你的回应,我…...
YOLOv5害虫识别项目代码打包完整上传Gitee仓库(已开源)以及git上传速率限制踩坑记录
YOLOv5害虫识别项目代码打包完整上传Gitee仓库(已开源)以及git上传速率限制踩坑记录 ps: 最近很多小伙伴需要这个害虫识别项目的源码,由于文件过大,所以将代码完整上传至gitee,所有文件、教程、论文、以及代码模型…...
从零开始学习c语言|21、动态内存管理
一、malloc函数 1、什么是malloc函数 malloc是memery(内存)和allocate(分配)的缩写,顾名思义,malloc函数为动态分配内存的意思 2、malloc函数语句 int *p(int *)malloc(sizeof(int))malloc函数的形参为申请的内存空间大小,上述申请了一个i…...
swagger关闭/v2/api-docs仍然可以访问漏洞
今天接到安全团队的说swagger有未授权访问漏洞,即使在swagger关闭的情况下http://127.0.0.1:8086/agcloud/v2/api-docs?group%E7%94%A8%E6%88%B7%E5%85%B3%E8%81%94%E4%BF%A1%E6%81%AF%E6%A8%A1%E5%9D%97仍然还能访问。 看了下原来是有写一个拦截器 registry.addI…...
k8s pod调度总结
在Kubernetes平台上,我们很少会直接创建一个Pod,在大多数情况下会通过控制器完成对一组Pod副本的创建、调度 及全生命周期的自动控制任务,如:RC、Deployment、DaemonSet、Job 等。本文主要举例常见的Pod调度。1全自动调度功能&…...
28个案例问题分析---10---对生产环境的敬畏--生产环境
一:背景介绍 1:上午9:23,老师没有进行上课,但是却又很多的在线人员,并且在线人员的时间也不正确。 2:开发人员及时练习用户,查看用户上课情况。 3:10点整,询问项目组长发…...
视觉SLAM十四讲ch7-1视觉里程计笔记
视觉SLAM十四讲ch7-1 视觉里程计笔记本讲目标从本讲开始,开始介绍SLAM系统的重要算法特征点法ORB特征BRIEF实践特征提取与匹配2D-2D:对极几何八点法求E八点法的讨论从单应矩阵恢复R,t小结三角化![在这里插入图片描述](https://img-blog.csdni…...
家具做网站/免费网站收录入口
Golang与C的关系非常密切,下面主要介绍在Golang中使用C。 一. Golang中嵌入C代码 1 package main 2 3 //#include <stdio.h>…...
360网站优化/网络推广运营推广
观察es数据目录内会发现大量文件如下图所示: 其中重要的有如下几个,存储三大头: 文件后缀文件含义.fdt文档存储的字段值.fdx文档索引指针,需载入内存.fnm存储fields信息.dvddocValues值.dvmdocValues原信息.doc包含每个term词频…...
wordpress全屏滚轴/给公司做网站要多少钱
Miracles sometimes occur, but one has to work terribly hard for them. 奇迹有时候是会发生的,但是你得为之拼命地努力。(Chaim Weizmann, First president of Israel) 原文载于:爱词霸英语学习网-每日一句 Chaim Weizmann 人物简介: h…...
微网站怎么自己做/国际国内新闻最新消息今天
/*测试版的实现 开始*/介绍在用GridView自带的排序功能排序时,无法直观的知道当前是通过哪个字段排序?是升序还是降序?所以扩展一下,用图片或文字的形式来提示一下当前是根据哪个字段排序,是升序还是降序。控件开发1、…...
文具网站建设策划书/关键词完整版免费听
1、计算机的存储单位有哪些呢?git计算机存储单位通常用B,KB,MB,GB,TB,PB,EB,ZB,YB,BB来表示,spa2、他们换算关系是怎样的呢?blog一、计…...
jsp动态网站开发课程/seo优化网站教程
写在前面 在自己准备写一些简单的verilog教程之前,参考了许多资料----asic-world网站的verilog教程即是其一。这套教程写得极好,奈何没有中文,在下只好斗胆翻译过来(加了自己的理解)分享给大家。 这是网站原文…...