> 文章列表 > Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

1 Spring框架整合Mybatis示例

1.1 创建演示项目

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

1.2 项目目录结构

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

1.3 依赖配置pom.xml文件

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kkarma</groupId><artifactId>spring-mybatis-app</artifactId><version>1.0.0</version><name>spring-mybatis-app</name><!-- FIXME change it to the project's website --><url>http://www.example.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.7</maven.compiler.source><maven.compiler.target>1.7</maven.compiler.target></properties><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-context --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.26</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-orm --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.26</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-test --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.26</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.26</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.15</version></dependency><!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.1.0</version></dependency><!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11-SNAPSHOT</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency></dependencies><build><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --><plugins><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-jar-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin><!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --><plugin><artifactId>maven-site-plugin</artifactId><version>3.7.1</version></plugin></plugins></pluginManagement></build>
</project>

1.4 项目配置文件

1.4.1 数据库连接配置文件

druid.driver=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://10.10.3.95:3306/sys-library?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone\\=Asia/Shanghai
druid.userName=adsion
druid.password=MTIzNDU2
# 初始化连接数
druid.pool.init=3
# 高峰期过后,保留连接吃的个数
druid.pool.minIdle=5
# 高峰期,最大能创建连接的个数
druid.pool.MaxActive=20
# 等待的时间
durid.pool.timeout=60

1.4.2 Mybatis框架全局配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><!--  二级缓存开启, 默认为true --><setting name="cacheEnabled" value="true" /><setting name="localCacheScope" value="STATEMENT"/></settings></configuration>

1.4.3 Spring框架配置文件

<?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:Context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 通过配置文件方式注册bean --><bean id="myBook01" class="com.kkarma.pojo.LibBook" ><property name="bookId" value="6"></property><property name="bookIndexNo" value="xxooooxx"></property><property name="bookName" value="java高级程序设计"></property><property name="bookAuthor" value="张三"></property><property name="bookPublisher" value="清华大学出版社"></property><property name="bookCateId" value="1"></property><property name="bookStock" value="2"></property></bean><!--声明使用注解配置--><Context:annotation-config /><!--声明Spring工厂注解的扫描范围--><Context:component-scan base-package="com.kkarma"/><!--引用外部文件--><Context:property-placeholder location="db.properties"/><!--配置DruidDataSources--><bean id="DruidDataSources" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${druid.driver}"/><property name="url" value="${druid.url}"/><property name="username" value="${druid.userName}"/><property name="password" value="${druid.password}"/><property name="initialSize" value="${druid.pool.init}"/><property name="minIdle" value="${druid.pool.minIdle}"/><property name="maxActive" value="${druid.pool.MaxActive}"/><property name="maxWait" value="${durid.pool.timeout}"/></bean><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--配置数据源--><property name="dataSource" ref="DruidDataSources"/><!--配置mapper的路径--><property name="mapperLocations" value="classpath*:mappers//*Mapper.xml"></property><!--配置需要定义别名的实体类的包--><property name="typeAliasesPackage" value="com.kkarma.pojo"/><!--配置需要mybatis的主配置文件--><property name="configLocation" value="mybatis-config.xml"/></bean><!-- 配置MapperScannerConfigurer --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<!--        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>--><property name="basePackage" value="com.kkarma.mapper"/></bean><!-- 将spring事务管理配置给spring --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="DruidDataSources"/></bean><!-- 通过Spring jdbc提供的<tx>标签声明事物的管理策略,并给事务设置隔离级别以及传播机制 --><tx:advice id="MyAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="Insert*" isolation="REPEATABLE_READ" propagation="REQUIRED"/><tx:method name="Update*" isolation="REPEATABLE_READ" propagation="REQUIRED"/><tx:method name="Delete*" isolation="REPEATABLE_READ" propagation="REQUIRED"/><tx:method name="Query*" isolation="REPEATABLE_READ" propagation="SUPPORTS"/></tx:attributes></tx:advice><!--将事务管理以Aop配置,应用于ServiceI方法(ServiceImp)--><aop:config><aop:pointcut id="MyManager" expression="execution(* com.kkarma.service.*.*(..))"/><aop:advisor advice-ref="MyAdvice" pointcut-ref="MyManager" /></aop:config>
</beans>

1.4.4 Mapper.xml文件

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

1.4.5 Mapper接口

package com.kkarma.mapper;import com.kkarma.pojo.LibBook;import java.util.List;/* @Author: karma* @Date: 2023/3/17 0017 - 03 - 17 - 10:18* @Description: com.kkarma.mapper* @version: 1.0*/
public interface LibBookMapper {List<LibBook> selectAllBook();
}

1.4.6 实体类

package com.kkarma.pojo;import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;import java.io.Serializable;/* @Author: karma* @Date: 2023/3/17 0017 - 03 - 17 - 10:09* @Description: com.kkarma.pojo* @version: 1.0*/
@Data
@NoArgsConstructor
@ToString
public class LibBook implements Serializable {private Long bookId;private String bookIndexNo;private String bookName;private String bookAuthor;private String bookDescription;private String bookPublisher;private Integer bookCateId;private Integer bookStock;
}

1.5 测试

package com.kkarma;import static org.junit.Assert.assertTrue;import com.kkarma.mapper.LibBookMapper;
import com.kkarma.pojo.LibBook;
import com.kkarma.service.ILibBookService;
import lombok.AllArgsConstructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;/* Unit test for simple App.*/
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class AppTest 
{@Autowired// private ILibBookService bookService;private LibBookMapper bookMapper;/* Rigorous Test :-)*/@Testpublic void queryBooksTest(){List<LibBook> libBooks = bookMapper.selectAllBook();libBooks.forEach( System.out::println);}
}

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

2 Spring框架整合Mybatis原理分析

看看, 之前如果我们单独只用Mybatis框架的时候, 测试代码是怎么写的

1) 创建一个SqlSessionFactoryBuilder对象SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();	2) 创建一个SqlSessionFactory对象:SqlSessionFactory factory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); 3) 创建一个SqlSession对象:SqlSession sqlSession = factory.openSession();4) 获取Mapper层指定接口的动态代理对象:LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);5) 调用接口方法获取返回结果即可:List<LibBook> list = mapper.selectAllBook();

对比一下spring框架集成Mybatis框架之后

public class AppTest 
{@Autowiredprivate LibBookMapper bookMapper;@Testpublic void queryBooksTest(){List<LibBook> libBooks = bookMapper.selectAllBook();}
}

对比一下,发现是不是很多操作就被简化了, 整个SqlSession对象的创建过程Mapper动态代理对象的创建过程都没有了, 那么mybatis-spring到底是怎么实现这个操作的简化的呢, 下面我们一起来分析一下这个过程。

2.1 SqlSessionFactoryBean

首先我们看看Spring的配置文件applicationContext.xml, 其中定义了sqlSessionFactory的bean声明, Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析
创建的对象是SqlSessionFactoryBean对象,前面已经详细说过Mybatis框架的核心内容了,我们应该都知道在Mybatis中我们都是通过SqlSessionFactoryBuilder创建SqlSessionFactory对象,然后再通过SqlSessionFactory对象创建SqlSession对象,从而获取数据库连接等等操作。

见名知意,SqlSessionFactoryBean对象很定也是跟创建SqlSessionFactory紧密相关的。到源码里面去看看

public class SqlSessionFactoryBeanimplements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);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;/ 在创建SqlSessionFactoryBean的时候,已经创建了sqlSessionFactoryBuilder对象 */private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();private SqlSessionFactory sqlSessionFactory;// EnvironmentAware requires spring 3.1private String environment = SqlSessionFactoryBean.class.getSimpleName();private boolean failFast;private Interceptor[] plugins;private TypeHandler<?>[] typeHandlers;private String typeHandlersPackage;@SuppressWarnings("rawtypes")private Class<? extends TypeHandler> defaultEnumTypeHandler;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;public void setObjectFactory(ObjectFactory objectFactory) {this.objectFactory = objectFactory;}public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {this.objectWrapperFactory = objectWrapperFactory;}public DatabaseIdProvider getDatabaseIdProvider() {return databaseIdProvider;}public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {this.databaseIdProvider = databaseIdProvider;}public Class<? extends VFS> getVfs() {return this.vfs;}public void setVfs(Class<? extends VFS> vfs) {this.vfs = vfs;}public Cache getCache() {return this.cache;}public void setCache(Cache cache) {this.cache = cache;}public void setPlugins(Interceptor... plugins) {this.plugins = plugins;}public void setTypeAliasesPackage(String typeAliasesPackage) {this.typeAliasesPackage = typeAliasesPackage;}public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {this.typeAliasesSuperType = typeAliasesSuperType;}public void setTypeHandlersPackage(String typeHandlersPackage) {this.typeHandlersPackage = typeHandlersPackage;}public void setTypeHandlers(TypeHandler<?>... typeHandlers) {this.typeHandlers = typeHandlers;}public void setDefaultEnumTypeHandler(@SuppressWarnings("rawtypes") Class<? extends TypeHandler> defaultEnumTypeHandler) {this.defaultEnumTypeHandler = defaultEnumTypeHandler;}public void setTypeAliases(Class<?>... typeAliases) {this.typeAliases = typeAliases;}public void setFailFast(boolean failFast) {this.failFast = failFast;}public void setConfigLocation(Resource configLocation) {this.configLocation = configLocation;}public void setConfiguration(Configuration configuration) {this.configuration = configuration;}public void setMapperLocations(Resource... mapperLocations) {this.mapperLocations = mapperLocations;}public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {this.configurationProperties = sqlSessionFactoryProperties;}public void setDataSource(DataSource dataSource) {if (dataSource instanceof TransactionAwareDataSourceProxy) {this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();} else {this.dataSource = dataSource;}}public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;}public void setTransactionFactory(TransactionFactory transactionFactory) {this.transactionFactory = transactionFactory;}public void setEnvironment(String environment) {this.environment = environment;}public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {this.scriptingLanguageDrivers = scriptingLanguageDrivers;}public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;}@Overridepublic 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();}protected SqlSessionFactory buildSqlSessionFactory() throws Exception {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)) {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 + "'");});}targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);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 IOException("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 IOException("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 IOException("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);}@Overridepublic SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();}return this.sqlSessionFactory;}@Overridepublic Class<? extends SqlSessionFactory> getObjectType() {return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();}/* {@inheritDoc}*/@Overridepublic boolean isSingleton() {return true;}/* {@inheritDoc}*/@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (failFast && event instanceof ContextRefreshedEvent) {// fail-fast -> check all statements are completedthis.sqlSessionFactory.getConfiguration().getMappedStatementNames();}}private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {Set<Class<?>> classes = new HashSet<>();String[] packagePatternArray = tokenizeToStringArray(packagePatterns,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);for (String packagePattern : packagePatternArray) {Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX+ ClassUtils.convertClassNameToResourcePath(packagePattern) + "//*.class");for (Resource resource : resources) {try {ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();Class<?> clazz = Resources.classForName(classMetadata.getClassName());if (assignableType == null || assignableType.isAssignableFrom(clazz)) {classes.add(clazz);}} catch (Throwable e) {LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());}}}return classes;}}

还是带着以下问题来看源码:

  • 什么时候完成的SqlSessionFactoryBuilder对象的创建?
  • 什么时候完成的SqlSessionFactory对象的创建?
  • Mapper层的动态代理对象是何时创建并注入到Spring容器进行管理的?

2.2 什么时候完成的SqlSessionFactoryBuilder对象的创建?

通过SqlSessionFactoryBean类源码我们可以看到,SqlSessionFactoryBuilder作为SqlSessionFactoryBean类的成员属性在对象创建的时候会进行创建。
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

2.3 什么时候完成的SqlSessionFactory对象的创建并注入Spring容器的?

SqlSessionFactoryBean类实现InitializingBean接口,实现了afterPropertiesSet方法,那么这个类肯定在该方法中实现了某些初始化的逻辑代码, 一块儿看看这个方法

  @Overridepublic 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");// 对SqlSessionFactory对象进行赋值,治理是通过buildSqlSessionFactory实现了SqlSessionFactory对象的创建this.sqlSessionFactory = buildSqlSessionFactory();}

再来看看那这个buildSqlSessionFactory方法

这个方法其实就干了下面两件事

  • 完成Mybatis全局配置文件的解析, 得到Configuration对象
  • 调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;
  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {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)) {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 + "'");});}targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);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 IOException("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 IOException("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 IOException("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.");}// 当前方法之前的代码其实就是在完成Mybatis全局配置文件的解析, 得到Configuration对象// 然后调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;return this.sqlSessionFactoryBuilder.build(targetConfiguration);}

2.4 SqlSessionFactory对象是怎么注入Spring容器的?

public class SqlSessionFactoryBeanimplements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {} 

我们可以看到SqlSessionFactoryBean实现了FactoryBean接口,
我们都知道有很多种方法可以将Bean对象注入到Spring容器中, 实现FactoryBean接口重写getObject方法就是其中一种, 这里就是利用该方法完成的SqlSessionFactory对象的注入。 看看SqlSessionFactoryBeangetObject`方法的实现

  @Overridepublic SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();}

afterPropertiesSet()方法中完成了SqlSessionFactory对象的创建。
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析
这样就完成了SqlSessionFactory注入Spring容器。

2.5 什么时候完成的Mapper接口对象的创建并注入Spring容器的?

2.5.1 spring配置文件中关于MapperScannerConfigurer的配置

还是从配置文件applicationContext.xml文件为入口,在applicationContext.xml文件中我们配置了一个Bean为MapperScannerConfigurer
这里我们配置了mapper层的基础扫描路径basePackage
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

2.5.2 MapperScannerConfigurer类

看看MapperScannerConfigurer类的注释信息, 我给大家翻译了一下, 勉强能说明是什么意思凑合看一下

/* BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and* registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;* concrete classes will be ignored.* BeanDefinitionRegistryPostProcessor,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean。* 请注意,将仅注册至少具有一个方法的接口;具体实现类将被忽略。* <p>* This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to* {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the* details.* 直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的,* 但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.* <p>* The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.* <p>* basePackage这个属性可包含多个基础包路径名称,使用逗号或分号分隔* This class supports filtering the mappers created by either specifying a marker interface or an annotation. The* {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property* specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that* match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given* {@code basePackage} are added as mappers.* 此类支持筛选通过指定标记接口或注释创建的映射器。属性annotationClass指定要搜索的注释。markerInterface属性指定要搜索的父接口。* 如果同时指定了这两个属性,则会为与任一条件匹配的接口添加映射器。* 默认情况下,这两个属性为 null,因此给定 basePackage中的所有接口都添加为Mapper映射器。* <p>* This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the* proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}* in the application, however, autowiring cannot be used. In this case you must explicitly specify either an* {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names* are used rather than actual objects because Spring does not initialize property placeholders until after this class* is processed.* 此配置器为其创建的所有 bean 启用自动装配,以便它们自动装配到正确的SqlSessionFactory或SqlSessionTemplate。* 但是,如果应用程序中有多个SqlSessionFactory,则无法使用自动装配。* 在这种情况下,您必须显式指定SqlSessionFactory或SqlSessionTemplate以通过 Bean Name属性使用。* 使用 Bean Name而不是实际对象,因为 Spring 在处理此类之前不会初始化属性占位符。* <p>* Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers* actual object creation until later in the startup process, after all placeholder substitution is completed. However,* note that this configurer does support property placeholders of its <em>own</em> properties. The* <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.* 传入可能需要占位符的实际对象(即数据库用户密码)会失败。* 使用 Bean Name会将实际的对象创建推迟到启动过程的稍后阶段,在所有占位符替换完成后。* 但是,请注意,此配置器确实支持其自己的属性的属性占位符。basePackage  Bean name 属性都支持 ${property} 样式替换。* <p>* Configuration sample:* 配置示例* 之前在applicationContext.xml我为什么之后可以这样配置, 就是通过这里的注释说明* <pre class="code">* {@code*   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">*       <property name="basePackage" value="org.mybatis.spring.sample.mapper" />*       <!-- optional unless there are multiple session factories defined -->*       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />*   </bean>* }* </pre>** @author Hunter Presnall* @author Eduardo Macarron** @see MapperFactoryBean* @see ClassPathMapperScanner*/

注意:

 直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的. 
但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.

我这里使用的是mybatis-spring2.1.0版本, 请注意版本问题。

看一下UML类图
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析
MapperScannerConfigurer类的注释信息明确告诉我们了,这个类实现了BeanDefinitionRegistryPostProcessor接口,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么他必然实现了postProcessBeanDefinitionRegistry

2.5.3 MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders();}// ClassPathBeanDefinitionScanner类的子类,就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中用的。ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);// 下面一堆set方法就是设置ClassPathMapperScanner或从父类ClassPathBeanDefinitionScanner继承的属性值的, 这没啥好说的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));}if (StringUtils.hasText(defaultScope)) {scanner.setDefaultScope(defaultScope);}scanner.registerFilters();// 核心代码就是这一行,扫描我们配置的包路径下的所有Mapper接口并注册成MapperFactoryBean对象// 这里我们配置的basePackage可能有多个,类似这样basePackage = "com.xxxx.mapper,com.xxxx.dao"// StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)的作用就是将// basePackage = "com.xxxx.mapper,com.xxxx.dao"分割成 String[]{"com.xxxx.mapper", "com.xxxx.dao"}数组scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}

2.5.4 ClassPathMapperScanner类

ClassPathMapperScanner类ClassPathBeanDefinitionScanner类的子类, 主要用途就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中。

源码中对于该类的注释信息如下:

一个 ClassPathBeanDefinitionScanner,它通过 basePackage、annotationClass 或
markerInterface 注册映射器。如果指定了 annotationClass 和/或
markerInterface,则仅搜索指定的类型(将禁用搜索所有接口)。 此功能以前是 MapperScannerConfigurer
的私有类,但在 1.2.0 版中被分解。

2.5.4.1 scan()方法

看看scanner.scan()方法

实际调用的是ClassPathBeanDefinitionScanner类的scan方法
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

2.5.4.2 doScan()方法

doScan()方法, 因为ClassPathMapperScanner类对该方法进行了重写, 这里调用的是ClassPathMapperScanner类的doScan()方法, 又在doScan()方法调用了父类的doScan()方法

  /* Calls the parent search that will search and register all the candidates. Then the registered objects are post* processed to set them as MapperFactoryBeans*/@Overridepublic 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;}

父类ClassPathBeanDefinitionScanner类的doScan()方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();String[] var3 = basePackages;int var4 = basePackages.length;// 扫描basePackage路径下的java类文件,先全部转为Resource,然后再判断拿出符合条件的bdfor(int var5 = 0; var5 < var4; ++var5) {String basePackage = var3[var5];Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);Iterator var8 = candidates.iterator();while(var8.hasNext()) {BeanDefinition candidate = (BeanDefinition)var8.next();// 解析scope属性ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());// 获取beanName// 先判断注解上有没有显示设置beanName// 没有的话,就以类名小写为beanNameString beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);// 如果这个类是AbstractBeanDefinition类型// 则为他设置默认值,比如lazy/init/destroy// 通过扫描出来的bd是ScannedGenericBeanDefinition,实现了AbstractBeanDefinitionif (candidate instanceof AbstractBeanDefinition) {this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);}// 如果这个类是AnnotatedBeanDefinition类型// 处理加了注解的类// 把常用注解设置到AnnotationBeanDefinition中if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);}if (this.checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);this.registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

通过父类doScan方法的调用获取到了所有的Mapper的接口的BeanDefinition信息。
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

2.5.4.3 processBeanDefinitions()方法

下面继续执行 processBeanDefinitions(beanDefinitions), 接着看看这个方法

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {AbstractBeanDefinition definition;BeanDefinitionRegistry registry = getRegistry();for (BeanDefinitionHolder holder : beanDefinitions) {definition = (AbstractBeanDefinition) holder.getBeanDefinition();boolean scopedProxy = false;if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {definition = (AbstractBeanDefinition) Optional.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()).map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException("The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));scopedProxy = true;}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 MapperFactoryBean// 设置BeanDefinition的构造函数参数值为当前BeanDefinition的类名,也就是*Mapper接口的类名definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59try {// for spring-nativedefinition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));} catch (ClassNotFoundException ignore) {// ignore}// 设置BeanDefinition的beanClass为MapperFactoryBean.class// 这里一修改, 就相当于在创建Bean对象的时候都是通过MapperFactoryBean来创建的definition.setBeanClass(this.mapperFactoryBeanClass);// 添加属性addToConfig为true, 因为ClassPathMapperScanner类的addToConfig属性默认为truedefinition.getPropertyValues().add("addToConfig", this.addToConfig);// Attribute for MockitoPostProcessor// https://github.com/mybatis/spring-boot-starter/issues/475definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);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);if (scopedProxy) {continue;}if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {definition.setScope(defaultScope);}if (!definition.isSingleton()) {BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {registry.removeBeanDefinition(proxyHolder.getBeanName());}registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());}}}

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

有上述方法我们可以知道,Mapper层接口的Bean对象都是通过MapperFactoryBean来创建的, mapper层的动态代理对象肯定也是借助MapperFactoryBean去完成了.

2.6 MapperFactoryBean是如何完成代理对象创建并注入Spring容器的?

首先看下 MapperFactoryBean的类图
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析
发现这个类即实现了FactoryBean接口, 也实现了InitializingBean接口, 那就一起来看看分别在
getObject()afterPropertiesSet()中都做了什么逻辑处理

2.6.1 MapperFactoryBeanSqlSessionFactoryBean是如何关联的

2.6.1.1 MapperFactoryBeanafterPropertiesSet方法

MapperFactoryBean类没有该方法, 肯定是在它的父类中实现的,在DaoSupport类中可以找到该方法

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {// Let abstract subclasses check their configuration.// 本类中的该方法是个抽象的方法,这里肯定调用的是子类的方法(使用了模板方法模式)checkDaoConfig();// Let concrete implementations initialize themselves.try {initDao();}catch (Exception ex) {throw new BeanInitializationException("Initialization of DAO failed", ex);}}

这个checkDaoConfig()方法在SqlSessionDaoSupport类中紧紧只是进行了sqlSessionTemplate是否注入完成的判断, 在SqlSessionDaoSupport类的子类MapperFactoryBean中又进行了重写

MapperFactoryBean类中的checkDaoConfig方法

  @Overrideprotected void checkDaoConfig() {// 调用了父类的该方法检测sqlSessionTemplate是否已经注入super.checkDaoConfig();// 检查mapperInterface属性是否已经填充notNull(this.mapperInterface, "Property 'mapperInterface' is required");// 这里就是调用父类的sqlSessionTemplate对象的getConfiguration方法获取Configuration对象Configuration configuration = getSqlSession().getConfiguration();if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {try {// 这里不就是Mybatis框架里面的addMapper方法么, 到这是不是就明白了。configuration.addMapper(this.mapperInterface);} catch (Exception e) {logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);throw new IllegalArgumentException(e);} finally {ErrorContext.instance().reset();}}}

在MapperFactoryBean注入到容器之后,MapperFactoryBean类中继承自父类的的afterPropertiesSet方法被自动执行, 当前MapperFactoryBean对象映射的接口就被注册到Mybatis框架的mapper注册器中了。
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

2.6.1 MapperFactoryBeangetObject()方法

  @Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}

在Spring框架扫描所有实现FactoryBean接口的类进行Bean对象的注册时, 这个getObject()方法会被执行,会调用Mybatis框架的通过Mybatis框架的动态代理模式创建mapper接口的代理对象注入到Spring容器中进行托管。
Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析
到此,Mybatis框架中的核心对象都注册到Spring容器中, 使用的时候是需要注入到调用的类中, 就可以使用对应的方法实现对数据库的增删改查了。

3 关于InitializingBean接口的拓展知识

Spring框架为bean提供了两种初始化bean的方式,实现InitializingBean接口或者通过在XML配置文件中添加init-method的方式,这两种方式可以同时使用。

实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。

如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

InitializingBean是Spring框架提供的拓展接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。

我们可以利用这个来完成很多功能, 比如在中小型系统业务开发中实现数据库的读写分离等业务操作。