Spring - FactoryBean扩展实战_MyBatis-Spring 启动过程源码解读

简介: 在理解 MyBatis-Spring 的启动过程时,需要重点把握的是 `SqlSessionTemplate` 核心类的设计理念及其实现过程,使用了JDK动态代理机制。

@[toc]

在这里插入图片描述


Pre

Spring Boot - 扩展接口一览

在这里插入图片描述


Pre

Spring - FactoryBean扩展接口

Spring-Spring整合MyBatis详解

在这里插入图片描述


MyBatis-Spring 组件

MyBatis的启动过程包含了一系列核心对象的创建,而这个过程涉及到对配置文件的读取和处理 。

MyBatis 也专门提供了一个 MyBatis-Spring 组件来完成与 Spring 框架的集成。

在这里插入图片描述

在这里插入图片描述

对于 MyBatis-Spring 而言,它的启动过程构建在 MyBatis 的启动过程基础之上,融合了 Spring 框架的功能特性。

因此了解Spring的扩展点是非常重要的。

在这里插入图片描述


扩展点org.mybatis.spring.SqlSessionFactoryBean

基于这些启动扩展点,其他框架实现与 Spring 框架之间的整合变得非常简单。

MyBatis 就是利用了这些扩展点实现与 Spring 框架的整合。扩展点-------------> SqlSessionFactoryBean 类。


public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    ...
    private Configuration configuration;
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    private SqlSessionFactory sqlSessionFactory;
    private String environment = SqlSessionFactoryBean.class.getSimpleName();
    ...
}
AI 代码解读

SqlSessionFactoryBean 实现了 FactoryBeanInitializingBeanApplicationListener 这三个扩展点,部分重要的变量如上。

InitializingBean扩展接口 afterPropertiesSet

结合Spring扩展点的执行顺序,我们先看看 InitializingBean,找到 afterPropertiesSet

  /**
   * {@inheritDoc}
   */
  @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();
  }
AI 代码解读

显然,SqlSessionFactoryBean 的主要职责就是完成 SqlSessionFactory 的构建,这也是这个类的类名的由来。

而完成这个操作的最合适阶段就是生命周期中的 InitializingBean 阶段。

buildSqlSessionFactory 方法的具体实现过程,这个方法非常长,但代码结构比较简单。

抛开大量的代码细节,使用如下所示的代码框架来展示这个方法的结构:


protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 
    Configuration configuration;
 
    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      //如果当前的configuration不为空,这直接使用该对象
      configuration = this.configuration;
      ...
    } else if (this.configLocation != null) {
      //如果配置文件地址configLocation不为空,则通过XMLConfigBuilder进行解析并创建configuration
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      //如果以上两种情况都不满足,则创建一个新的configuration对象并进行参数赋值
      configuration = new Configuration();
      ...
    }
 
  //设置objectFactory等各种MyBatis运行时所需的配置信息
  ...
 
  //基于configuration,通过SqlSessionFactoryBuilder构建SqlSessionFactory
  return this.sqlSessionFactoryBuilder.build(configuration);
}
AI 代码解读

继续build

 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
AI 代码解读

首先会加载 XML 配置文件,然后基于这个配置文件构建一个 Configuration 配置类,再通过 SqlSessionFactoryBuilderbuild 方法来创建 SqlSessionFactory

这里创建的是一个 DefaultSqlSessionFactory,我们可以通过 DefaultSqlSessionFactory 进而获取 SqlSession 对象的实例。


FactoryBean 扩展接口 getObject

继续看 SqlSessionFactoryBean 实现的 FactoryBean接口, 从接口的泛型定义上,我们明白它的 getObject 方法返回的应该是一个 SqlSessionFactory 对象。

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

    return this.sqlSessionFactory;
  }
AI 代码解读

如果还没有创建目标 SqlSessionFactory,就直接调 afterPropertiesSet 方法完成该对象的创建并返回。


ApplicationListener扩展接口 onApplicationEvent

  /**
   * {@inheritDoc}
   */
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    if (failFast && event instanceof ContextRefreshedEvent) {
      // fail-fast -> check all statements are completed
      this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
  }
AI 代码解读

在接收到代表容器刷新的 ContextRefreshedEvent 事件时,重新获取各种 MappedStatement

执行流程如下:

ContextRefreshedEvent ------>   onApplicationEvent  -------------> getMappedStatementNames
AI 代码解读

继续看下 getMappedStatementNames

 @Override
    public Collection<String> getMappedStatementNames() {
       //构建所有的Statement
        buildAllStatements();
        return mappedStatements.keySet();
    }
AI 代码解读

继续跟下去就已经到了Mybatis了,就这么巧妙的集合起来了。

至此,SqlSessionFactoryBean 中与 Spring 整合的相关内容就梳理完了。通过 org.mybatis.spring.SqlSessionFactoryBean,我们就可以获取 SqlSessionFactory 对象,这是 MyBatis 框架启动过程的目标生成对象 。

在这里插入图片描述


扩展点org.mybatis.spring.mapper.MapperFactoryBean

继续来看另一个 FactoryBean, MapperFactoryBean 这个类用于生成 MapperFactoryMapperFactory 的作用显然就是获取 Mapper

在这里插入图片描述


public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;
  ...
}
AI 代码解读

MapperFactoryBean 实现了 FactoryBean 接口,那就看下 getObject方法

  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
AI 代码解读

通过 SqlSessiongetMapper 方法获取 Mapper 对象,这个是 MyBatis 自身所提供的核心功能。

那么这个 SqlSession 是怎么来的呢?

不难发现 MapperFactoryBean 在实现了 FactoryBean 接口的同时,还扩展了 SqlSessionDaoSupport 类。

SqlSessionDaoSupport 是一个抽象类,扩展了 Spring 中的 DaoSupport 抽象类,并提供了如下方法


  public abstract class SqlSessionDaoSupport extends DaoSupport {
   
    private SqlSession sqlSession;
   
    private boolean externalSqlSession;
   
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
        //注意,这个构建SqlSession是一个SqlSessionTemplate对象
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
      }
    }
   
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
      this.sqlSession = sqlSessionTemplate;
      this.externalSqlSession = true;
    }
    
    public SqlSession getSqlSession() {
      return this.sqlSession;
    }
    ...
  }
AI 代码解读

看到这里定义了一个 SqlSession 对象用于对外暴露访问入口。

注意,这个 SqlSession 并不是我们所认为的来自 MyBatis 的 DefaultSqlSession,而是构建了一个同样实现了 SqlSession 接口的 SqlSessionTemplate 类。


SqlSessionTemplate 解决线程安全问题

既然 MyBatis 已经提供了 DefaultSqlSession,为什么这里还要构建一个 SqlSessionTemplate 呢?那一起看看这个 SqlSessionTemplate,这是 MyBatis-Spring 中的核心类。

DefaultSqlSession 本身是线程不安全的,所以我们要使用 SqlSession 时,为了确保线程安全,常规做法就是通过 SqlSessionFactory 获取一个新的 SqlSession。但这种做法效率会很低,造成资源的浪费。

更好的实现方法应该是通过全局唯一的 SqlSession 实例来完成 DefaultSqlSession 的工作,而 SqlSessionTemplate 就是这个全局唯一的 SqlSession 实例。

在这里插入图片描述

当通过 Web 线程访问同一个 SqlSessionTemplate,也就是同一个 SqlSession 时,它是如何确保线程安全的呢?

分析一下 SqlSessionTemplate 类的构造函数。SqlSessionTemplate 实现了 SqlSession 接口,并提供了如下所示的构造函数


public class SqlSessionTemplate implements SqlSession, DisposableBean { 
  
  public SqlSessionTemplate(...) {
        
  //通过动态代理创建一个SqlSession的代理对象
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
   }
   ...
}
AI 代码解读

这里创建了一个名为 sqlSessionProxySqlSession,但创建过程使用了 JDK 中经典的动态代理实现方案,就是通过 Proxy.newProxyInstance 静态方法注入一个 InvocationHandler 的实例。

这个实例就是 SqlSessionInterceptor 类。SqlSessionInterceptor 类是 SqlSessionTemplate 中的一个内部类,实现了 InvocationHandler 接口,其 invoke 方法实现如下:

 
private class SqlSessionInterceptor implements InvocationHandler {
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
      //获取SqlSession实例,该实例是线程不安全的
      SqlSession sqlSession =  getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
          
      try {
          //调用真实SqlSession的方法
        Object result = method.invoke(sqlSession, args);
        
        //判断当前的sqlSession是否被Spring托管,如果未被Spring托管则自动commit
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        //省略异常处理
      } finally {
        if (sqlSession != null) {
          //关闭sqlSession
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
AI 代码解读

核心逻辑其实是在 getSqlSession 方法中,这个方法完成了线程安全性的处理


  public static SqlSession getSqlSession(...) {
 
    //根据sqlSessionFactory从当前线程对应的资源Map中获取SqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 
    //从SqlSessionHolder中获取sqlSession实例
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
 
    //如果获取不到sqlSession实例,则根据executorType创建一个新的sqlSession 
    session = sessionFactory.openSession(executorType);
 
    //将sessionFactory和session注册到线程安全的资源Map
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
 
    return session;
  }
AI 代码解读

使用了 Spring 中的一个工具类——TransactionSynchronizationManager,这个类用于存储传入的 SessionHolderregisterSessionHolder 方法的核心代码如下所示:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
       //......
    } else {
       //......
    }

  }
AI 代码解读

重点是:

    holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
    TransactionSynchronizationManager.bindResource(sessionFactory, holder);
    TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
AI 代码解读

继续

     public static void registerSynchronization(TransactionSynchronization synchronization)
        throws IllegalStateException {

    Assert.notNull(synchronization, "TransactionSynchronization must not be null");
    Set<TransactionSynchronization> synchs = synchronizations.get();
    if (synchs == null) {
        throw new IllegalStateException("Transaction synchronization is not active");
    }
    synchs.add(synchronization);
}
AI 代码解读

synchronizations变量

TransactionSynchronizationManager 中存储 SqlSessionSynchronization 用的是 synchronizations 变量

private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
  new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
AI 代码解读

ThreadLocal 确保了线程的安全性。


总结

在这里插入图片描述

在理解 MyBatis-Spring 的启动过程时,需要重点把握的是 SqlSessionTemplate 核心类的设计理念及其实现过程,使用了JDK动态代理机制。

在这里插入图片描述

相关文章
Spring AI与DeepSeek实战三:打造企业知识库
本文基于Spring AI与RAG技术结合,通过构建实时知识库增强大语言模型能力,实现企业级智能搜索场景与个性化推荐,攻克LLM知识滞后与生成幻觉两大核心痛点。
249 7
Spring Security 实战指南:从入门到精通
本文详细介绍了Spring Security在Java Web项目中的应用,涵盖登录、权限控制与安全防护等功能。通过Filter Chain过滤器链实现请求拦截与认证授权,核心组件包括AuthenticationProvider和UserDetailsService,负责用户信息加载与密码验证。文章还解析了项目结构,如SecurityConfig配置类、User实体类及自定义登录逻辑,并探讨了Method-Level Security、CSRF防护、Remember-Me等进阶功能。最后总结了Spring Security的核心机制与常见配置,帮助开发者构建健壮的安全系统。
105 0
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
65 0
Spring AI与DeepSeek实战四:系统API调用
在AI应用开发中,工具调用是增强大模型能力的核心技术,通过让模型与外部API或工具交互,可实现实时信息检索(如天气查询、新闻获取)、系统操作(如创建任务、发送邮件)等功能;本文结合Spring AI与大模型,演示如何通过Tool Calling实现系统API调用,同时处理多轮对话中的会话记忆。
254 57
|
4天前
|
Mybatis源码解析:详述初始化过程
以上就是MyBatis的初始化过程,这个过程主要包括SqlSessionFactory的创建、配置文件的解析和加载、映射文件的加载、SqlSession的创建、SQL的执行和SqlSession的关闭。这个过程涉及到了MyBatis的核心类和接口,包括SqlSessionFactory、SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、Configuration、SqlSession和Executor等。通过这个过程,我们可以看出MyBatis的灵活性和强大性,它可以很好地支持定制化SQL、存储过程以及高级映射,同时也避免了几
38 20
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
89 5
Spring AI与DeepSeek实战二:打造企业级智能体
本文介绍如何基于Spring AI与DeepSeek模型构建企业级多语言翻译智能体。通过明确的Prompt设计,该智能体能自主执行复杂任务,如精准翻译32种ISO标准语言,并严格遵循输入格式和行为限制。代码示例展示了如何通过API实现动态Prompt生成和翻译功能,确保服务的安全性和可控性。项目已开源,提供更多细节和完整代码。 [GitHub](https://github.com/zlt2000/zlt-spring-ai-app) | [Gitee](https://gitee.com/zlt2000/zlt-spring-ai-app)
223 11
Spring MVC 扩展和SSM框架整合
通过以上步骤,我们可以将Spring MVC扩展并整合到SSM框架中。这个过程包括配置Spring MVC和Spring的核心配置文件,创建控制器、服务层和MyBatis的Mapper接口及映射文件。在实际开发中,可以根据具体业务需求进行进一步的扩展和优化,以构建更加灵活和高效的企业级应用程序。
54 5
Spring AI与DeepSeek实战一:快速打造智能对话应用
在 AI 技术蓬勃发展的今天,国产大模型DeepSeek凭借其低成本高性能的特点,成为企业智能化转型的热门选择。而Spring AI作为 Java 生态的 AI 集成框架,通过统一API、简化配置等特性,让开发者无需深入底层即可快速调用各类 AI 服务。本文将手把手教你通过spring-ai集成DeepSeek接口实现普通对话与流式对话功能,助力你的Java应用轻松接入 AI 能力!虽然通过Spring AI能够快速完成DeepSeek大模型与。
544 11
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
184 29