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

1 Spring框架整合Mybatis示例

1.1 创建演示项目

在这里插入图片描述

1.2 项目目录结构

在这里插入图片描述

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 configuration
        PUBLIC "-//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/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://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文件

在这里插入图片描述

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 :-)
     */
    @Test
    public void queryBooksTest()
    {
    
    
        List<LibBook> libBooks = bookMapper.selectAllBook();
        libBooks.forEach( System.out::println);
    }
}

在这里插入图片描述

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 
{
    
    
    @Autowired
    private LibBookMapper bookMapper;

    @Test
    public void queryBooksTest()
    {
    
    
        List<LibBook> libBooks = bookMapper.selectAllBook();
    }
}

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

2.1 SqlSessionFactoryBean

首先我们看看Spring的配置文件applicationContext.xml, 其中定义了sqlSessionFactory的bean声明, 在这里插入图片描述
创建的对象是SqlSessionFactoryBean对象,前面已经详细说过Mybatis框架的核心内容了,我们应该都知道在Mybatis中我们都是通过SqlSessionFactoryBuilder创建SqlSessionFactory对象,然后再通过SqlSessionFactory对象创建SqlSession对象,从而获取数据库连接等等操作。

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

public class SqlSessionFactoryBean
    implements 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.1
  private 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;
  }


  @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();
  }

  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 xmls
      try {
    
    
        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);
  }

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

    return this.sqlSessionFactory;
  }

  @Override
  public Class<? extends SqlSessionFactory> getObjectType() {
    
    
    return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isSingleton() {
    
    
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    
    
    if (failFast && event instanceof ContextRefreshedEvent) {
    
    
      // fail-fast -> check all statements are completed
      this.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类的成员属性在对象创建的时候会进行创建。
在这里插入图片描述

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

SqlSessionFactoryBean类实现InitializingBean接口,实现了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");
	// 对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 xmls
      try {
    
    
        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 SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    
    
    } 

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

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

afterPropertiesSet()方法中完成了SqlSessionFactory对象的创建。
在这里插入图片描述
这样就完成了SqlSessionFactory注入Spring容器。

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

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

还是从配置文件applicationContext.xml文件为入口,在applicationContext.xml文件中我们配置了一个Bean为MapperScannerConfigurer
这里我们配置了mapper层的基础扫描路径basePackage
在这里插入图片描述
在这里插入图片描述

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类图
在这里插入图片描述
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方法
在这里插入图片描述

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
   */
  @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;
  }

父类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,然后再判断拿出符合条件的bd
        for(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
				// 没有的话,就以类名小写为beanName
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

				// 如果这个类是AbstractBeanDefinition类型
				// 则为他设置默认值,比如lazy/init/destroy
				// 通过扫描出来的bd是ScannedGenericBeanDefinition,实现了AbstractBeanDefinition
                if (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信息。
在这里插入图片描述

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 #59
      try {
    
    
        // for spring-native
        definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
      } catch (ClassNotFoundException ignore) {
    
    
        // ignore
      }
	  // 设置BeanDefinition的beanClass为MapperFactoryBean.class
	  // 这里一修改, 就相当于在创建Bean对象的时候都是通过MapperFactoryBean来创建的
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // 添加属性addToConfig为true, 因为ClassPathMapperScanner类的addToConfig属性默认为true
      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      // Attribute for MockitoPostProcessor
      // https://github.com/mybatis/spring-boot-starter/issues/475
      definition.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());
      }

    }
  }

在这里插入图片描述

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

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

首先看下 MapperFactoryBean的类图
在这里插入图片描述
发现这个类即实现了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方法

  @Override
  protected 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注册器中了。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.6.1 MapperFactoryBeangetObject()方法
  @Override
  public T getObject() throws Exception {
    
    
    return getSqlSession().getMapper(this.mapperInterface);
  }

在Spring框架扫描所有实现FactoryBean接口的类进行Bean对象的注册时, 这个getObject()方法会被执行,会调用Mybatis框架的通过Mybatis框架的动态代理模式创建mapper接口的代理对象注入到Spring容器中进行托管。
在这里插入图片描述
到此,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的属性初始化后都会执行该方法。

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

猜你喜欢

转载自blog.csdn.net/qq_41865652/article/details/129932614