文章目录
前言
原生MyBatis的调用流程如下
@Test
public void TestExample() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee employee = mapper.selectByPrimaryKey(1)
System.out.println(employee);
} finally {
session.close();
}
}
那么 当MyBatis被Spring托管之后,对于MyBatis的使用上会不会发生变化呢?
本篇文章主要围绕SqlSessionFactory、SqlSession以及Mapper的创建和使用来分析。
SqlSessionFactory是怎么被创建的?
SqlSessionFactoryBean
这个类是由Spring提供的,类的描述信息大意是讲 这个类可以创建MyBatis SqlSessionFactory对象。
先看下这个类的声明
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>{}
发现其实现了FactoryBean接口,Bean初始化接口、以及事件监听接口
FactoryBean 是一个工厂Bean,可以生成Bean,其逻辑在getObject()
InitializingBean Bean生命周期接口,afterPropertiesSet()会被重新 并在Bean初始化时 被调用
ApplicationListener事件监听接口,可以监听感兴趣的事件 做处理
因此我们首先可以推断 无论是采用xml方式或基于Java Config方式 该类在使用时 都要被加载到IOC容器,成为被Spring管理的Bean。
为了方便理解 我们给出xml和java config两种配置
<!-- 在Spring启动时创建 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<property name="dataSource" ref="dataSource"/>
</bean>
java config配置
@Bean
@Autowired
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setConfigLocation("classpath:mybatis-config.xml")
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/*.xml"));
return sessionFactory.getObject();
}
SqlSessionFactory的创建在getObject(),先看下这个方法
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
// 实现InitializingBean接口,重写该方法
@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");
this.sqlSessionFactory = buildSqlSessionFactory();
}
buildSqlSessionFactory方法
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
final Configuration targetConfiguration;
XMLConfigBuilder 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)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Stream.of(typeAliasPackageArray).forEach(packageToScan -> {
targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
});
}
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)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Stream.of(typeHandlersPackageArray).forEach(packageToScan -> {
targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
});
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
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 (!isEmpty(this.mapperLocations)) {
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 or no matching resources found");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
buildSqlSessionFactory
方法主要做以下几件事:
1、如果mybatis全局配置文件存在,则解析它。并且如果SqlSessionFactoryBean配置了typeAlias、typeHandler、插件plugins等 则解析它
2、如果mapper文件存在 则解析mapper文件
3、将解析后的数据放到Configuration对象中
4、根据SqlSessionFactoryBuilder创建SqlSessionFactory
谁替代了SqlSession?
我们知道 SqlSessionFactory有了,离调用mapper方法 就只差SqlSession了,我们沿着SqlSession这个接口开始找,发现在mybatis-spring包下 有一个新的实现
SqlSessionTemplate
,看到这个类马上想到了 就是它。为什么呢? 因为它实现了SqlSession接口,以前DefaultSqlSession可以做的事情,它都可以做。
我们结合类图再看下
通过类描述 我们发现 SqlSessionTemplate
是线程安全的,可以被所有的DAO共享。没错,这很好的弥补了DefaultSqlSession 线程不安全的弊端。
那我们来使用下SqlSessionTemplate
(注释中 给出说明 创建SqlSessionTemplate 须传入sqlSessionFactory)
xml方式
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"></constructor-arg>
<constructor-arg value="SIMPLE"></constructor-arg>
</bean>
或者
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory,ExecutorType.SIMPLE);
}
单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class DaoSupportTest {
@Autowired
private SqlSessionTemplate sqlSession;
@Test
public void EmployeeDaoSupportTest() {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
System.out.println(mapper.selectByPrimaryKey(1));
System.out.println((Employee)sqlSession.selectOne("com.wojiushiwo.EmployeeMapper.selectByPrimaryKey", 1));
}
}
附上applicationContext.xml完整配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
<bean id="jdbc" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath*:jdbc.properties"/>
</bean>
<!-- Spring的连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!-- 在Spring启动时创建 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置一个可以执行批量的sqlSession,全局唯一,单例 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"></constructor-arg>
<constructor-arg value="SIMPLE"></constructor-arg>
</bean>
</beans>
说到SqlSessionTemplate
, 这里顺便提一下SqlSessionDaoSupport
,它是个抽象类,它的一个成员变量就是SqlSessionTemplate
,也提供了获取SqlSession
的方法
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
...
}
可以看到,只要我们提供了SqlSessionFactory
,这个类也可以自动生成SqlSessionTemplate
,那么我们也可以基于此 来间接地使用SqlSessionTemplate
(前提是IOC容器中已存在SqlSessionFactory)
public class BaseDao extends SqlSessionDaoSupport implements EmployeeMapper{
//使用sqlSessionFactory
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public Employee selectByPrimaryKey(String statement,Integer empId) {
return (Employee)getSqlSession().selectOne(statement,empId);
}
我可以直接使用Mapper接口吗?
看到这里,不免又有些疑惑了,实际生产环境中 不是这样去访问mybatis的呀,生产环境中是直接注入的Mapper接口,形如
public class EmployeeService{
@Autowired
private EmployeeMapper employeeMapper;
...
}
这又是怎么实现的呢? 别急,继续往下看(这里先以xml配置方式介绍下)
有一个类MapperFactoryBean
映入了我们的眼帘,从类名上可以看出 它跟Mapper对象的生成密不可分,同时它也是SqlSessionDaoSupport
的子类,自然也就具备了访问Mybatis必备的那些属性和类了
先看下它的类图
既然是FactoryBean,那么我们就从getObject
作为切入点
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;
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
//父类getSqlSession的定义为了方便 就贴这里了
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
...
}
看到这,是不是恍然大悟了。getSqlSession().getMapper(this.mapperInterface)
这不正是跟我们前面手动调用时 一样嘛!getSqlSession().getMapper(this.mapperInterface)
这个方法的主要内容是 生成this.mapperInterface
的代理对象,这里不再赘述,可至MyBatis中Mapper是如何产生的?查看原理
我们以MapperFactoryBean
来优化下我们前面对EmployeeMapper的调用写法(xml方式 展示 更直观)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
<bean id="jdbc" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath*:jdbc.properties"/>
</bean>
<!-- Spring的连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!-- 在Spring启动时创建 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 接口的普通注册方式1 -->
<bean id="employeeMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" parent="baseMapper">
<property name="mapperInterface" value="com.gupaoedu.crud.dao.EmployeeMapper"/>
</bean>
<!-- 接口的普通注册方式2 -->
<bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="employeeMapper2" parent="baseMapper">
<property name="mapperInterface" value="com.gupaoedu.crud.dao.EmployeeMapper"/>
</bean>
</beans>
调用举例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class DaoSupportTest {
@Autowired
private EmployeeMapper employeeMapper2;
@Test
public void EmployeeDaoSupportTest() {
System.out.println(employeeMapper2.selectByPrimaryKey(1));
}
}
还有更简便地方式使用Mapper接口吗?
虽然上面这种方式 相较于SqlSessionTemplate 便捷了一些,可是 真正的开发场景下 会有很多的Mapper,难道都需要手动将其注册到IOC容器吗?
当然不是了,请继续往下看,mybatis-spring包提供了一个扫描器ClassPathMapperScanner
,可以扫描指定包下的Mapper接口 然后将其注入IOC容器中。
这里我们大致看一下
ClassPathMapperScanner#doScan
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
...
//很重要的一步 将BeanClass修改为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.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;
}
...
}
}
ClassPathMapperScanner#doScan 主要做两件事:
1、调用父类doScan方法,扫描指定包 获取候选对象 将其注入到IOC容器
2、处理BeanDefinition,最重要的一步是将BeanClass替换为MapperFactoryBean.class
将Mapper作为属性注入到Service等IOC容器,当Service被初始化的过程中,Mapper也会从IOC容器中拿到代理对象,然后就是使用。具体这部分逻辑在FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
,这也算是Bean初始化的范畴了,这里不再赘述。
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
throws BeanCreationException {
Object object;
try {
...
object = factory.getObject();
...
}
catch (FactoryBeanNotInitializedException ex) {
throw new BeanCurrentlyInCreationException(beanName, ex.toString());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
}
...
return object;
}
factory.getObject()
这里又与前面介绍的MapperFactoryBean#getObject
调用链吻合了。
至于MyBatis扫描包的配置 也有两种,这里全部给出
基于xml
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wojiushiwo.dao"/>
</bean>
或者
基于@MapperScan,在启动类上指定@MapperScan(value=“com.wojiushiwo.dao”)即可,这里顺便分析下基于@MapperScan注入是如何实现的
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
...
}
请注意@Import(MapperScannerRegistrar.class) 注解引入了一个配置类,我们猜测@MapperScan相关的实现全在MapperScannerRegistrar
里
MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,这是一种动态加载Bean的方式,子类会重写registerBeanDefinitions
来处理动态加载Bean的逻辑,与之类似的还有ImportSelector等
registerBeanDefinitions
方法中 ClassPathMapperScanner
很是眼熟,它不就是我们前面介绍的基于xml方式实现的扫描器吗? 之后的逻辑与前面类似了,不再赘述。
好了,收工。至此 Spring整合MyBatis主要流程分析告一段落。