learn.lianglianglee.com/文章/Spring MyBatis和Spring整合的奥秘.md.html
2022-05-11 19:04:14 +08:00

1667 lines
50 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>Spring MyBatis和Spring整合的奥秘.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制.md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>Spring MyBatis和Spring整合的奥秘</h1>
<p><em>本篇博客源码分析基于Spring 5.1.16.RELEASEmybatis-spring 2.0.0较高版本的mybatis-spring源码有较大区别。</em></p>
<p>Spring之所以是目前Java最受欢迎的框架几乎所有的Java项目都在使用就是因为它良好的生态很多技术可以与之整合为什么其他技术可以和Spring相整合就是因为Spring拥有很多扩展点阅读Spring源码有一部分原因就是有必要清楚的知道Spring提供了哪些扩展点而怎么合理的利用这些扩展点就需要了解其他技术是如何利用这些扩展点的。</p>
<p>今天我就来带着大家看下国内最流行的数据库框架MyBatis是如何利用Spring的扩展点的从而双剑合璧让Spring+MyBatis成为国内最流行的技术搭配。</p>
<h3>前置知识</h3>
<p>为了后面的故事可以顺利展开很有必要先给大家介绍下阅读mybatis-spring源码的前置知识没有这些前置知识阅读mybatis-spring源码是寸步难行。</p>
<h4>mybatis-spring使用</h4>
<p>因为现在有了SpringBoot所以Mybatis和Spring的整合变得非常简单但是如果没有SpringBoot该怎么整合呢我翻阅了百度的前几页不知道是不是搜索关键词问题几乎全是用XML的方式去整合Mybatis和Spring的零XML配置它不香吗</p>
<p>代码结构:
<img src="assets/15100432-c2a982e6c0145a65.png" alt="image.png" /></p>
<p>具体实现:</p>
<pre><code> &lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.mybatis&lt;/groupId&gt;
&lt;artifactId&gt;mybatis-spring&lt;/artifactId&gt;
&lt;version&gt;2.0.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- https://mvnrepository.com/artifact/org.springframework/spring-context --&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework&lt;/groupId&gt;
&lt;artifactId&gt;spring-context&lt;/artifactId&gt;
&lt;version&gt;5.1.16.RELEASE&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.mybatis&lt;/groupId&gt;
&lt;artifactId&gt;mybatis&lt;/artifactId&gt;
&lt;version&gt;3.4.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework&lt;/groupId&gt;
&lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt;
&lt;version&gt;5.1.10.RELEASE&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --&gt;
&lt;dependency&gt;
&lt;groupId&gt;mysql&lt;/groupId&gt;
&lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
&lt;version&gt;6.0.5&lt;/version&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;
@MapperScan(&quot;com.codebear.mapper&quot;)
@ComponentScan
public class AppConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(&quot;com.mysql.jdbc.Driver&quot;);
dataSource.setUrl(&quot;jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC&quot;);
dataSource.setUsername(&quot;root&quot;);
dataSource.setPassword(&quot;123456&quot;);
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean.getObject();
}
}
@Repository
public interface StudentMapper {
@Select(&quot;select * from student&quot;)
List&lt;Student&gt; getList();
}
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(applicationContext.getBean(StudentMapper.class).getList());
}
}
</code></pre>
<p>运行结果:</p>
<pre><code>[Student{id=1, name='疫苗王', age=20}, Student{id=2, name='阿修罗独角仙', age=18}, Student{id=3, name='地底王', age=18}]
</code></pre>
<h4>Import注解</h4>
<p>如果我们想把一个类注册到Spring容器中可以采用的方法有很多其中一种是利用Import注解Import注解有三种用法mybatis-spring利用的是其中一种用法Import了ImportBeanDefinitionRegistrar类所以我们这里只看Import ImportBeanDefinitionRegistrar。</p>
<h5>如何使用</h5>
<pre><code>public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
System.out.println(annotationMetadata.getAnnotationTypes());
}
}
</code></pre>
<p>写一个类实现ImportBeanDefinitionRegistrar 重写其中的registerBeanDefinitions方法。</p>
<pre><code>@Import(MyBeanDefinitionRegistrar.class)
@ComponentScan
@MapperScan(&quot;com.codebear.mapper&quot;)
public class AppConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(&quot;com.mysql.jdbc.Driver&quot;);
dataSource.setUrl(&quot;jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC&quot;);
dataSource.setUsername(&quot;root&quot;);
dataSource.setPassword(&quot;123456&quot;);
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean.getObject();
}
}
</code></pre>
<p>在配置上加上@Import注解写上刚才写的MyBeanDefinitionRegistrar类。</p>
<p>运行结果:</p>
<pre><code>[org.springframework.context.annotation.Import, org.springframework.context.annotation.ComponentScan, org.mybatis.spring.annotation.MapperScan]
</code></pre>
<p>从registerBeanDefinitions两个入参的命名来看第一个参数Spring把注解元数据给你了而第二个参数Spring是直接把beanDefinition的注册器给你了。</p>
<h5>追本溯源</h5>
<p>下面我们来看看Spring在什么时候处理@Import注解的又是什么时候调用registerBeanDefinitions方法的当然这里不是Spring源码分析我不会详细一行行翻译而是简单的找到源头。</p>
<pre><code>//AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class&lt;?&gt;... componentClasses)
public AnnotationConfigApplicationContext(Class&lt;?&gt;... componentClasses) {
this();
register(componentClasses);
refresh();
}
</code></pre>
<p>进入第三行的refresh()方法。</p>
<p>refresh方法做了很多事情我们只需要关心invokeBeanFactoryPostProcessors方法</p>
<pre><code>//AbstractApplicationContext#refresh
invokeBeanFactoryPostProcessors(beanFactory);
</code></pre>
<p>执行invokeBeanFactoryPostProcessors方法顾名思义这个方法是执行BeanFactoryPostProcessor的。什么你不知道什么是BeanFactoryPostProcessor你可以简单的理解为Spring遵循插件化式的开发其中有一个插件叫ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor同时BeanDefinitionRegistryPostProcessor又实现了BeanFactoryPostProcessor通过ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry的方法Spring完成了扫描。</p>
<pre><code>//PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
</code></pre>
<p>这一步传入了BeanDefinitionRegistryPostProcessor的集合要执行BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法集合有一个元素是我们关心的就是上面提到的ConfigurationClassPostProcessor。</p>
<pre><code>//PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
</code></pre>
<p>循环传入的BeanDefinitionRegistryPostProcessor集合调用postProcessBeanDefinitionRegistry方法我们直接进入到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法找到关键解析代码</p>
<pre><code>//ConfigurationClassPostProcessor#processConfigBeanDefinitions
parser.parse(candidates);
//ConfigurationClassParser#parse
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
//ConfigurationClassParser#processConfigurationClass
doProcessConfigurationClass(configClass, sourceClass);
//ConfigurationClassParser#doProcessConfigurationClass
processImports(configClass, sourceClass, getImports(sourceClass), true);
</code></pre>
<p>重点来了,终于找到了我们的目标:处理@Import注解。</p>
<pre><code>//ConfigurationClassParser#processImports
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
//ConfigurationClass#addImportBeanDefinitionRegistrar
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
</code></pre>
<p>这个importBeanDefinitionRegistrars就是一个Map</p>
<pre><code>//ConfigurationClass
private final Map&lt;ImportBeanDefinitionRegistrar, AnnotationMetadata&gt; importBeanDefinitionRegistrars = new LinkedHashMap&lt;&gt;();
</code></pre>
<p>让我们就监视下configClass
<img src="assets/15100432-df700f54b54cb88d.png" alt="image.png" />
可以看到我们写的MyBeanDefinitionRegistrar被放入了importBeanDefinitionRegistrars ,我们需要记住这个集合,至于还有一个什么,这里不用关心,当然,聪明的小伙伴肯定知道这是什么了。</p>
<p>我们写的MyBeanDefinitionRegistrar只是被放入了一个Map并没有执行下面我们要找找它是在哪里执行的。</p>
<p>我们需要回到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法</p>
<pre><code>//ConfigurationClassPostProcessor#processConfigBeanDefinitions
this.reader.loadBeanDefinitions(configClasses);
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
</code></pre>
<p>这个集合是不是有点眼熟就是我在上面让大家记住的集合这个集合就存放着我们的写的MyBeanDefinitionRegistrar类让我们继续点进去</p>
<pre><code>//ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
private void loadBeanDefinitionsFromRegistrars(Map&lt;ImportBeanDefinitionRegistrar, AnnotationMetadata&gt; registrars) {
registrars.forEach((registrar, metadata) -&gt;
registrar.registerBeanDefinitions(metadata, this.registry));
}
</code></pre>
<p>循环传入的ImportBeanDefinitionRegistrar集合调用registerBeanDefinitions方法我的天终于找到执行方法了。</p>
<h4>FactoryBean</h4>
<p>Spring就像是一个魔术师的袋子而FactoryBean就是被魔术师装进袋子的香蕉当魔术师打开袋子发现香蕉变成鸽子了。</p>
<h5>如何使用</h5>
<pre><code>@Component
public class MyFactoryBean implements FactoryBean&lt;Teacher&gt; {
public Teacher getObject() {
Teacher teacher = new Teacher();
teacher.setName(&quot;琦玉老师&quot;);
return teacher;
}
public Class&lt;?&gt; getObjectType() {
return Teacher.class;
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(applicationContext.getBean(MyFactoryBean.class));
System.out.println(applicationContext.getBean(Teacher.class));
System.out.println(applicationContext.getBean(&quot;&amp;myFactoryBean&quot;));
System.out.println(applicationContext.getBean(&quot;myFactoryBean&quot;));
System.out.println(applicationContext.getBean(&quot;myFactoryBean&quot;).hashCode());
System.out.println(applicationContext.getBean(&quot;myFactoryBean&quot;).hashCode());
}
}
</code></pre>
<p>运行结果:</p>
<pre><code><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="85e6eae8abe6eae1e0e7e0e4f7abc8fcc3e4e6f1eaf7fcc7e0e4ebc5b1e0e0b7b5b6e0e7">[email&#160;protected]</a>
Teacher{name='琦玉老师'}
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c6a5a9abe8a5a9a2a3a4a3a7b4e88bbf80a7a5b2a9b4bf84a3a7a886f2a3a3f4f6f5a3a4">[email&#160;protected]</a>
Teacher{name='琦玉老师'}
442125849
442125849
</code></pre>
<p>可以很清楚的看到从FactoryBean里面又生产出了一个Bean生产出来的Bean就是FactoryBean中getObject方法返回的。</p>
<h5>追本溯源</h5>
<p>和上面一样我们也要看看FactoryBean中的getObject是在哪里执行的我们先来做个试验</p>
<p>我们在getObject里面加上一句打印的代码</p>
<pre><code>@Component
public class MyFactoryBean implements FactoryBean&lt;Teacher&gt; {
public Teacher getObject() {
System.out.println(&quot;getObject&quot;);
Teacher teacher = new Teacher();
teacher.setName(&quot;琦玉老师&quot;);
return teacher;
}
public Class&lt;?&gt; getObjectType() {
return Teacher.class;
}
}
</code></pre>
<p>然后只保留main方法中的创建ApplicationContext方法</p>
<pre><code>public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
}
}
</code></pre>
<p>运行后你会发现控制台没有任何输出我们大胆的猜想FactoryBean生产出来的Bean并不是预先加载的而是采用懒加载的机制也就是只有需要才会去加载。</p>
<p>我们继续改下main方法</p>
<pre><code> public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(applicationContext.getBean(Teacher.class));
}
</code></pre>
<p>运行结果:</p>
<pre><code>getObject
Teacher{name='琦玉老师'}
</code></pre>
<p>所以我们的猜想是正确的这次入口是getBean。</p>
<p>下面还是枯燥无味的寻找,这次的寻找之旅更复杂:</p>
<pre><code>//org.springframework.beans.factory.support.DefaultListableBeanFactory#getBean(java.lang.Class&lt;T&gt;)
public &lt;T&gt; T getBean(Class&lt;T&gt; requiredType) throws BeansException {
return getBean(requiredType, (Object[]) null);
}
// org.springframework.beans.factory.support.DefaultListableBeanFactory#getBean(java.lang.Class&lt;T&gt;, java.lang.Object...)
Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
//org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveBean
NamedBeanHolder&lt;T&gt; namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
//org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveNamedBean(org.springframework.core.ResolvableType, java.lang.Object[], boolean)
String[] candidateNames = getBeanNamesForType(requiredType);
//org.springframework.beans.factory.support.DefaultListableBeanFactory#getBeanNamesForType(org.springframework.core.ResolvableType)
return getBeanNamesForType(resolved, true, true);
//org.springframework.beans.factory.support.DefaultListableBeanFactory#getBeanNamesForType(java.lang.Class&lt;?&gt;, boolean, boolean)
resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
</code></pre>
<p>这个方法里面有一步是循环beanDefinitionNames当循环到myFactoryBean的时候判断这是一个FactoryBean</p>
<pre><code>boolean isFactoryBean = isFactoryBean(beanName, mbd);
</code></pre>
<p>随后执行isTypeMatch(beanName, type)方法:</p>
<pre><code>//org.springframework.beans.factory.support.AbstractBeanFactory#isTypeMatch(java.lang.String, org.springframework.core.ResolvableType)
Class&lt;?&gt; type = getTypeForFactoryBean((FactoryBean&lt;?&gt;) beanInstance);
//org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getTypeForFactoryBean
return factoryBean.getObjectType();
</code></pre>
<p>当执行到这里我们写的MyFactoryBean的getObjectType方法被调用了返回Teacher.class而我们现在要找的也是Teacher.class所以匹配。</p>
<p>随后回到DefaultListableBeanFactory#doGetBeanNamesForType把beanName放入一个集合中</p>
<pre><code>if (matchFound) {
result.add(beanName);
}
</code></pre>
<p>随后返回集合。</p>
<p>再回到DefaultListableBeanFactory#resolveNamedBean会判断返回出来的集合的元素的个数显然只返回一个执行</p>
<pre><code>//org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveNamedBean
if (candidateNames.length == 1) {
String beanName = candidateNames[0];
return new NamedBeanHolder&lt;&gt;(beanName, (T) getBean(beanName, requiredType.toClass(), args));
}
</code></pre>
<p>继续点开getBean方法</p>
<pre><code>//org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class&lt;T&gt;, java.lang.Object...)
return doGetBean(name, requiredType, args, false);
//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
//org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
//org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean
doGetObjectFromFactoryBean(factory, beanName);
//org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
object = factory.getObject();
</code></pre>
<p>直到这里才执行了我们写的MyFactoryBean的getObject方法拿到了我们返回的Teacher对象后。</p>
<p>因为有缓存机制如果我们再去拿就不会再次调用getObject方法了这个缓存机制就不再继续分析了比较复杂就算不了解也不影响我们今天的主题。</p>
<h4>JDK动态代理</h4>
<p>我以前写过JDK动态代理的博客大家可以找来看一看 ,这里就不阐述了。</p>
<h3>mybatis-spring源码分析</h3>
<p>前置知识介绍完成有了上面的前置知识我们就可以一探MyBatis和Spring整合的奥秘。</p>
<p>Mybatis和Spring整合的入口很好找就是我们再配置上添加的@MapperScan注解当我们点开@MapperScan</p>
<pre><code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
</code></pre>
<p>你会发现一个很熟悉的注解就是我们上面讲的Import注解Import了MapperScannerRegistrar。</p>
<p>通过上面的源码分析明白Spring会执行到registerBeanDefinitions方法</p>
<pre><code>@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 拿到我们写的MapperScan注解上带的东西我们写的只有一个Value字段。
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
</code></pre>
<p>继续深入registerBeanDefinitions方法</p>
<pre><code>// 创建了一个扫描器这个扫描器继承了Spring定义的扫描器ClassPathBeanDefinitionScanner
// 扫描的主要是主要作用就是扫描把bean放到map中去
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//省略
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray(&quot;value&quot;))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
//省略
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
</code></pre>
<p>这里主要是创建了一个扫描器,传入了一些规则。</p>
<p>scanner.registerFilters()中有一行代码,比较重要:</p>
<pre><code>// mybatis定义的扫描器最终的扫描任务是交给Spring的扫描器执行的
// Spring的扫描器中定义了includeFilters只有符合规则的最终才可以被扫描出来
// 这里意味着mybatis告诉spring任何东西你都要给我扫描出来。
addIncludeFilter((metadataReader, metadataReaderFactory) -&gt; true);
</code></pre>
<p>让我们看下 scanner.doScan(StringUtils.toStringArray(basePackages))方法:</p>
<pre><code> @Override
public Set&lt;BeanDefinitionHolder&gt; doScan(String... basePackages) {
//交给Spring执行扫描任务返回beanDefinition、beanName的包装对象这里就把我们
//@MapperScan注解中给定的com.codebear.mapper包中所有的内容都扫描
//并且返回出来了。
Set&lt;BeanDefinitionHolder&gt; beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -&gt; &quot;No MyBatis mapper was found in '&quot; + Arrays.toString(basePackages) + &quot;' package. Please check your configuration.&quot;);
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
</code></pre>
<p>继续看processBeanDefinitions方法</p>
<pre><code>// 省略
for (BeanDefinitionHolder holder : beanDefinitions) {
definition.setBeanClass(this.mapperFactoryBean.getClass());
}
//省略
</code></pre>
<p>这个循环中有一行代码很是重要把扫描出来的bean的BeanClass都设置成了mapperFactoryBean这个mapperFactoryBean是何方神圣呢没错它就是我们上面分析过的FactoryBean通过实验和分析我们知道了最终产生的bean对象是FactoryBean中的getObject返回的对象。</p>
<pre><code> public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
//org.mybatis.spring.SqlSessionTemplate#getMapper
public &lt;T&gt; T getMapper(Class&lt;T&gt; type) {
return getConfiguration().getMapper(type, this);
}
//org.apache.ibatis.binding.MapperRegistry#getMapper
public &lt;T&gt; T getMapper(Class&lt;T&gt; type, SqlSession sqlSession) {
MapperProxyFactory&lt;T&gt; mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException(&quot;Type &quot; + type + &quot; is not known to the MapperRegistry.&quot;);
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException(&quot;Error getting mapper instance. Cause: &quot; + var5, var5);
}
}
}
//org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
//sqlSession是SqlSessionTemplate
public T newInstance(SqlSession sqlSession) {
// 这里需要用到JDK动态代理的知识传入了SqlSessionTemplateMapper类接口
MapperProxy&lt;T&gt; mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
// 生成了代理对象
protected T newInstance(MapperProxy&lt;T&gt; mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
</code></pre>
<p>最终我们调用代理对象的方法会执行到MapperProxy的invoke方法</p>
<pre><code> public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
</code></pre>
<p>当我们点开mapperMethod.execute方法你会觉得一切是那么的熟悉。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/Spring MVC 原理.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709980217f758b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>