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

news2024/11/24 4:33:33

文章目录

  • Pre
  • Pre
  • MyBatis-Spring 组件
  • 扩展点org.mybatis.spring.SqlSessionFactoryBean
    • InitializingBean扩展接口 afterPropertiesSet
    • FactoryBean 扩展接口 getObject
    • ApplicationListener扩展接口 onApplicationEvent
  • 扩展点org.mybatis.spring.mapper.MapperFactoryBean
    • SqlSessionTemplate 解决线程安全问题

在这里插入图片描述


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

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

显然,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);
}

继续build

 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

首先会加载 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;
  }

如果还没有创建目标 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();
    }
  }

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

执行流程如下:

ContextRefreshedEvent ------>   onApplicationEvent  -------------> getMappedStatementNames

继续看下 getMappedStatementNames

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

继续跟下去就已经到了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;
  ...
}

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

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

通过 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;
    }
    ...
  }

看到这里定义了一个 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());
   }
   ...
}

这里创建了一个名为 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);
        }
      }
    }
  }

核心逻辑其实是在 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;
  }

使用了 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 {
       //......
    }

  }

重点是:


holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));

继续

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

synchronizations变量

TransactionSynchronizationManager 中存储 SqlSessionSynchronization 用的是 synchronizations 变量


private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
  new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

ThreadLocal 确保了线程的安全性。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/66570.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux基本命令归纳整理】

Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff0c;是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。严格来讲&#xff0c;Linux 这个词本身只表示 Linux 内核&#xff0c;但实际上人们已经习惯了用 Linux 来形容整个基于 Linux 内核&…

Day40——Dp专题

文章目录三、01背包8.分割等和子集9.最后一块石头的重量 II10.目标和11. 一和零三、01背包 8.分割等和子集 题目链接&#xff1a;416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;我们构造两个子集使得两个子集的和相等&#xff0c;其实就是让我…

JavaScript:初始JS 以及 基础语法

前端三件套&#xff1a; HTML: 生成网页控件 例如&#xff1a;生成 文本框 多选框 下拉列表 等 (人的身体) CSS: 修饰网页上的控件 例如&#xff1a;修饰文本框为圆形 &#xff08;人的衣服&#xff09; JavaSript: 在这些控件上添加逻辑 例如&#xff1a;获取文本框的值 然…

哈工大体系结构lab3 —— 流水线处理器的verilog实现

流水线处理器的verilog实现 是的我刚刚验收完最后一个实验&#xff0c;所以怀着激动的心情&#xff0c;把当时其中一个留档的代码发出来&#xff0c;还算较为清晰&#xff0c;仅供没有思路的同学参考。造完cache&#xff0c;我的生活终于可以恢复正轨了&#xff0c;这几天折磨的…

web安全之SQL盲注的靶场练习和分析

目录 SQL盲注-报错回显盲注 SQL盲注-时间盲注 SQL盲注-布尔盲注 SQL盲注-报错回显盲注 在burp里面进行动态抓包&#xff0c;判断符号闭环&#xff0c;如图明显为闭环 列数3时报错&#xff0c;判断当前列数为2 强行报错注入 &#xff0c;如图获取到版本号 uname1212 unio…

h5视频落地页知识点整理

一、视频在苹果中自动播放&#xff08;借助微信SDK&#xff09; 1.引入微信SDK <script src"http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> 2. document.addEventListener(WeixinJSBridgeReady, function() { const timer setInte…

如何签署exe或Windows应用程序?

本文您将了解为什么要签署Windows应用程序以及如何签署EXE或Windows应用程序的步骤指南。 代码签名是一种确保软件来自经过验证的正版软件发行商的方法。使用代码签名证书唱WindowsEXE文件可确保可执行文件或Windows应用程序不会被恶意行为者更改或修改。 Windows应用程序签名…

2022年NPDP新版教材知识集锦--【第五章节】(2)

《产品经理认证(NPDP)知识体系指南(第2版)》已于2022年4月正式上架发行&#xff0c;新版教材自2022年11月NPDP考试起使用。将新版NPDP教材中的相关知识点进行了整理汇总&#xff0c;包括详细设计与规格阶段相关内容&#xff0c;快来看看吧。 【市场研究工具】(全部内容获取文末…

华为机试 - 无向图染色

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 给一个无向图染色&#xff0c;可以填红黑两种颜色&#xff0c;必须保证相邻两个节点不能同时为红色&#xff0c;输出有多少种不同的染色方案&#xff1f; 输入描述 第一行输入M(图中节点数) N(边数) …

使用reshape2 R包进行在线长数据和宽数据相互转化

数据是数据分析的基础。我们常见的数据一般存储在excel表格&#xff0c;或者txt文档中。今天我们来看看长数据和宽数据&#xff0c;以及如何进行两者之间的相互转换。 1&#xff0e;宽数据和长数据 宽数据 如图1所示&#xff0c;宽数据是我们最常见的数据存储形式&#xff0c…

[附源码]Python计算机毕业设计Django校园订餐管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

nginx配置文件组成

1.配置文件的组成 ​ 配置文件由全局块events块http块组成 1.1 全局块 ​ 从配置文件开始到events之间的内容&#xff0c;主要会设置一些影响Nginx服务器整体运行的配置指令&#xff0c;主要包括配置运行Nginx服务器的用户(组)、允许生成的worker process数&#xff0c;进程pid…

Java数据结构与Java算法学习Day06---堆(简略笔记记录)

目录 一、堆 96 1.1堆的定义 96 1.2堆的API设计 97 1.3堆---堆的插入方法 98 1.4堆---堆的删除最大元素方法 99 1.5堆---堆的测试 100 二、堆排序 101 2.1堆排序 101 一、堆 96 1.1堆的定义 96 堆实际上也是利用数据结构实现的&#xff0c;用树实现的特殊结构&…

(mac M1)Flutter环境搭建

下载Flutter SDK&#xff0c;需要科学上网。 将Flutter永久添加到PATH中 1 sudo vim ~/.bash_profile 打开文件 2 export PATHpwd/flutter/bin:$PATH 将这个添加到前几行环境变量设置中 3 :wq 退出vim 4 source ~/.bash_profile 配置马上生效命令 运行 flutter doctor 命令&a…

【Linux】yum的介绍和使用

本期主题&#xff1a;yum介绍和使用博客主页&#xff1a;小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限&#xff0c;出现错误希望大家不吝赐作为程序员&#xff0c;不会有人还没女朋友吧。 目录 &#x1f341;1.软件包是什么&#xff1f; &#x1f341;…

[附源码]Python计算机毕业设计Django小型银行管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Qwt开发笔记(二):Qwt基础框架介绍、折线图介绍、折线图Demo以及代码详解

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/128194710 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

单片机硬件和软件延时、RTOS相对延时和绝对延时

已剪辑自: https://mp.weixin.qq.com/s/-RPLQn4KO9Aqu1fpfZeOKA 前不久有个读者在问关于延时的问题&#xff0c;大概就是问&#xff1a;软件延时和硬件延时是啥意思&#xff1f;做项目时他俩有什么区别&#xff1f; 今天就来讲讲关于硬件延时和软件延时的内容&#xff0c;以及…