learn.lianglianglee.com/文章/Spring 帮助你更好的理解Spring循环依赖.md.html
2022-05-11 19:04:14 +08:00

895 lines
46 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 帮助你更好的理解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 href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a class="current-tab" 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 帮助你更好的理解Spring循环依赖</h1>
<p>网上关于Spring循环依赖的博客太多了有很多都分析的很深入写的很用心甚至还画了时序图、流程图帮助读者理解我看了后感觉自己是懂了但是闭上眼睛总觉得还没有完全理解总觉得还有一两个坎过不去对我这种有点笨的人来说真的好难。当时我就在想如果哪一天我理解了Spring循环依赖一定要用自己的方式写篇博客帮助大家更好的理解等我理解后一直在构思到底怎么应该写才能更通俗易懂就在前几天我想通了这么写应该更通俗易懂。在写本篇博客之前我翻阅了好多关于Spring循环依赖的博客网上应该还没有像我这样讲解的现在就让我们开始把。</p>
<h3>什么是循环依赖</h3>
<p>一言以蔽之:两者相互依赖。</p>
<p>在开发中可能经常出现这种情况只是我们平时并没有注意到原来我们写的两个类、甚至多个类相互依赖了为什么注意不到呢当然是因为没有报错而且一点问题都木有如果报错了或者产生了问题我们还会注意不到吗这一切都是Spring的功劳它在后面默默的为我们解决了循环依赖的问题。</p>
<p>如下所示:</p>
<pre><code>@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class AuthorService {
@Autowired
BookService bookService;
}
@Service
public class BookService {
@Autowired
AuthorService authorService;
}
public class Main {
public static void main(String[] args) {
ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
BookService bookService = (BookService) annotationConfigApplicationContext.getBean(&quot;bookService&quot;);
System.out.println(bookService.authorService);
AuthorService authorService = (AuthorService) annotationConfigApplicationContext.getBean(&quot;authorService&quot;);
System.out.println(authorService.bookService);
}
}
</code></pre>
<p>运行结果:</p>
<pre><code><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0e6d6163206d616a6b6c6b6f7c207d7e7c6760696d776d626b204f7b7a66617c5d6b7c78676d6b4e383d3d39386c6b6a">[email&#160;protected]</a>
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ddbeb2b0f3beb2b9b8bfb8bcaff3aeadafb4b3babea4beb1b8f39fb2b2b68eb8afabb4beb89de9ece9e8bfbcb9e5">[email&#160;protected]</a>
</code></pre>
<p>可以看到BookService中需要AuthorServiceAuthorService中需要BookService类似于这样的就叫循环依赖但是神奇的是竟然一点问题没有。</p>
<p>当然有些小伙伴可能get不到它的神奇之处至于它的神奇之处在哪里我们放到后面再说。</p>
<h3>任何循环依赖Spring都能解决吗</h3>
<p>不行。</p>
<p>如果是原型 bean的循环依赖Spring无法解决</p>
<pre><code>@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BookService {
@Autowired
AuthorService authorService;
}
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AuthorService {
@Autowired
BookService bookService;
}
</code></pre>
<p>启动后,令人恐惧的红色字体在控制台出现了:
<img src="assets/15100432-9dd42da442010f30.png" alt="image.png" /></p>
<p>如果是构造参数注入的循环依赖Spring无法解决</p>
<pre><code>@Service
public class AuthorService {
BookService bookService;
public AuthorService(BookService bookService) {
this.bookService = bookService;
}
}
@Service
public class BookService {
AuthorService authorService;
public BookService(AuthorService authorService) {
this.authorService = authorService;
}
}
</code></pre>
<p>还是讨厌的红色字体:
<img src="assets/15100432-d0b71854edfbcd9f.png" alt="image.png" /></p>
<h3>循环依赖可以关闭吗</h3>
<p>可以Spring提供了这个功能我们需要这么写</p>
<pre><code>public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setAllowCircularReferences(false);
applicationContext.register(AppConfig.class);
applicationContext.refresh();
}
}
</code></pre>
<p>再次运行,就报错了:
<img src="assets/15100432-2ecaf84436018d26.png" alt="image.png" /></p>
<p>需要注意的是,我们不能这么写:</p>
<pre><code> AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
applicationContext.setAllowCircularReferences(false);
</code></pre>
<p>如果你这么写程序执行完第一行代码整个Spring容器已经初始化完成了你再设置不允许循环依赖也于事无补了。</p>
<h3>可以循环依赖的神奇之处在哪</h3>
<p>有很多小伙伴可能并不觉得可以循环依赖有多么神奇,那是因为不知道矛盾点在哪,接下来就来说说这个问题:
当beanAbeanB循环依赖</p>
<ol>
<li>创建beanA发现依赖beanB</li>
<li>创建beanB发现依赖beanA</li>
<li>创建beanA发现依赖beanB</li>
<li>创建beanB发现依赖beanA。
...
好了,死循环了。</li>
</ol>
<p>循环依赖的矛盾点就在于要创建beanA它需要beanB而创建beanB又需要beanA然后两个bean都创建不出来。</p>
<h3>如何简单的解决循环依赖</h3>
<p>如果你曾经看过Spring解决循环依赖的博客应该知道它其中有好几个Map一个Map放的是最完整的对象称为singletonObjects一个Map放的是提前暴露出来的对象称为earlySingletonObjects。</p>
<p>在这里,先要解释下这两个东西:</p>
<ul>
<li>singletonObjects单例池其中存放的是经历了Spring完整生命周期的bean这里面的bean的依赖都已经填充完毕了。</li>
<li>earlySingletonObjects提前暴露出来的对象的map其中存放的是刚刚创建出来的对象没有经历Spring完整生命周期的bean这里面的bean的依赖还未填充完毕。</li>
</ul>
<p>我们可以这么做:</p>
<ol>
<li>当我们创建完beanA就把自己放到earlySingletonObjects发现自己需要beanB然后就去屁颠屁颠创建beanB</li>
<li>当我们创建完beanB就把自己放到earlySingletonObjects发现自己需要beanA然后就去屁颠屁颠创建beanA</li>
<li>创建beanA前先去earlySingletonObjects看一下发现自己已经被创建出来了把自己返回出去</li>
<li>beanB拿到了beanAbeanB创建完毕把自己放入singletonObjects</li>
<li>beanA可以去singletonObjects拿到beanB了beanA也创建完毕把自己放到singletonObjects。
整个过程结束。</li>
</ol>
<p>下面让我们来实现这个功能:
首先自定义一个注解字段上打上这个注解的说明需要被Autowired</p>
<pre><code>@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearAutowired {
}
</code></pre>
<p>再创建两个循环依赖的类:</p>
<pre><code>public class OrderService {
@CodeBearAutowired
public UserService userService;
}
public class UserService {
@CodeBearAutowired
public OrderService orderService;
}
</code></pre>
<p>然后就是核心创建对象填充属性并解决Spring循环依赖的问题</p>
<pre><code>public class Cycle {
// 单例池里面放的是完整的bean已完成填充属性
private final Map&lt;String, Object&gt; singletonObjects = new ConcurrentHashMap&lt;&gt;();
// 存放的是提前暴露出来的bean没有经历过spring完整的生命周期没有填充属性
private final Map&lt;String, Object&gt; earlySingletonObjects = new HashMap&lt;&gt;();
// 在Spring中这个map存放的是beanNam和beanDefinition的映射关系
static Map&lt;String, Class&lt;?&gt;&gt; map = new HashMap&lt;&gt;();
static {
map.put(&quot;orderService&quot;, OrderService.class);
map.put(&quot;userService&quot;, UserService.class);
}
// 如果先调用init方法就是预加载如果直接调用getBean就是懒加载两者的循环依赖问题都解决了
public void init() {
for (Map.Entry&lt;String, Class&lt;?&gt;&gt; stringClassEntry : map.entrySet()) {
createBean(stringClassEntry.getKey());
}
}
public Object getBean(String beanName) {
// 尝试从singletonObjects中取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 尝试从earlySingletonObjects取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
return createBean(beanName);
}
private Object createBean(String beanName) {
Object singletonObject;
try {
// 创建对象
singletonObject = map.get(beanName).getConstructor().newInstance();
// 把没有完成填充属性的半成品 bean 放入earlySingletonObjects
earlySingletonObjects.put(beanName, singletonObject);
// 填充属性
populateBean(singletonObject);
// bean创建成功放入singletonObjects
this.singletonObjects.put(beanName, singletonObject);
return singletonObject;
} catch (Exception ignore) {
}
return null;
}
private void populateBean(Object object) {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(CodeBearAutowired.class) != null) {
Object value = getBean(field.getName());
try {
field.setAccessible(true);
field.set(object, value);
} catch (IllegalAccessException ignored) {
}
}
}
}
}
</code></pre>
<p>预加载调用:</p>
<pre><code>public class Main {
public static void main(String[] args) {
Cycle cycle = new Cycle();
cycle.init();
UserService userService = (UserService) cycle.getBean(&quot;userService&quot;);
OrderService orderService = (OrderService) cycle.getBean(&quot;orderService&quot;);
System.out.println(userService.orderService);
System.out.println(orderService.userService);
}
}
</code></pre>
<p>运行结果:</p>
<pre><code><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f794989ad99498939295929685d9948e949b929296848ed9b885939285a49285819e9492b7c1c6959696cfcec3">[email&#160;protected]</a>
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7f1c1012511c101b1a1d1a1e0d511c061c131a1a1e0c06512a0c1a0d2c1a0d09161c1a3f1d4f494a1c494c">[email&#160;protected]</a>
</code></pre>
<p>懒加载调用:</p>
<pre><code>public class Main {
public static void main(String[] args) {
Cycle cycle = new Cycle();
UserService userService = (UserService) cycle.getBean(&quot;userService&quot;);
OrderService orderService = (OrderService) cycle.getBean(&quot;orderService&quot;);
System.out.println(userService.orderService);
System.out.println(orderService.userService);
}
}
</code></pre>
<p>运行结果:</p>
<pre><code><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="492a2624672a262d2c2b2c283b672a302a252c2c283a3067063b2d2c3b1a2c3b3f202a2c097f782b282871707d">[email&#160;protected]</a>
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="9af9f5f7b4f9f5fefff8fffbe8b4f9e3f9f6fffffbe9e3b4cfe9ffe8c9ffe8ecf3f9ffdaf8aaacaff9aca9">[email&#160;protected]</a>
</code></pre>
<h3>为什么无法解决原型、构造方法注入的循环依赖</h3>
<p>在上面我们自己手写了解决循环依赖的代码可以看到核心是利用一个map来解决这个问题的这个map就相当于缓存。</p>
<p>为什么可以这么做因为我们的bean是单例的而且是字段注入setter注入单例意味着只需要创建一次对象后面就可以从缓存中取出来字段注入意味着我们无需调用构造方法进行注入。</p>
<ul>
<li>如果是原型bean那么就意味着每次都要去创建对象无法利用缓存</li>
<li>如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。</li>
</ul>
<h3>需要aop怎么办</h3>
<p>我们上面的方案看起来很美好但是还有一个问题如果我们的bean创建出来还要做一点加工怎么办也许你没有理解这句话的意思再说的明白点如果beanA和【beanB的代理对象】循环依赖或者【beanA的代理对象】和beanB循环依赖再或者【beanA的代理对象】和【beanB的代理对象】循环依赖怎么办</p>
<p><em>这里说的创建代理对象仅仅是“加工”的其中一种可能。</em></p>
<p>遇到这种情况我们总不能把创建完的对象直接扔到缓存把我们这么做的话如果【beanA的代理对象】和【beanB的代理对象】循环依赖我们最终获取的beanA中的beanB还是beanB并非是beanB的代理对象。</p>
<p>聪明的你,一定在想,这还不简单吗:
我们创建完对象后判断这个对象是否需要代理如果需要代理创建代理对象然后把代理对象放到earlySingletonObjects不就OJ8K了
就像这样:</p>
<pre><code> private Object createBean(String beanName) {
Object singletonObject;
try {
// 创建对象
singletonObject = map.get(beanName).getConstructor().newInstance();
// 创建bean的代理对象
/**
* if( 需要代理){
* singletonObject=创建代理对象;
*
* }
*/
// 把没有完成填充属性的半成品 bean 放入earlySingletonObjects
earlySingletonObjects.put(beanName, singletonObject);
// 填充属性
populateBean(singletonObject);
// bean创建成功放入singletonObjects
this.singletonObjects.put(beanName, singletonObject);
return singletonObject;
} catch (Exception ignore) {
}
return null;
}
</code></pre>
<p>这确实可以但是这违反了Spring的初衷Spring的初衷是希望在bean生命周期的最后几步才去aop如果像上面说的这么做就意味着一旦创建完对象Spring就会去aop了这就违反了Spring的初衷所以Spring并没有这么做。</p>
<p>但是如果真的出现了aop bean循环依赖就没办法了只能先去aop但是如果没有出现循环依赖Spring并不希望在这里就进行aop所以Spring引入了Map&lt;String, ObjectFactory&lt;?&gt;&gt;ObjectFactory是一个函数式接口可以理解为工厂方法当创建完对象后把【获得这个对象的工厂方法】放入这个map等真的发生循环依赖就去执行这个【获得这个对象的工厂方法】获取加工完成的对象。</p>
<p>下面直接放出代码:</p>
<pre><code>public class Cycle {
// 单例池里面放的是完整的bean已完成填充属性
private final Map&lt;String, Object&gt; singletonObjects = new ConcurrentHashMap&lt;&gt;();
// 存放的是 加工bean的工厂方法
private final Map&lt;String, ObjectFactory&lt;?&gt;&gt; singletonFactories = new HashMap&lt;&gt;();
// 存放的是提前暴露出来的bean没有经历过spring完整的生命周期没有填充属性
private final Map&lt;String, Object&gt; earlySingletonObjects = new HashMap&lt;&gt;();
private final Set&lt;String&gt; singletonsCurrentlyInCreation = new HashSet&lt;&gt;();
static Map&lt;String, Class&lt;?&gt;&gt; map = new HashMap&lt;&gt;();
static {
map.put(&quot;orderService&quot;, OrderService.class);
map.put(&quot;userService&quot;, UserService.class);
}
public void init() {
for (Map.Entry&lt;String, Class&lt;?&gt;&gt; stringClassEntry : map.entrySet()) {
createBean(stringClassEntry.getKey());
}
}
private Object createBean(String beanName) {
Object instance = null;
try {
instance = map.get(beanName).getConstructor().newInstance();
} catch (Exception ex) {
}
Object finalInstance = instance;
this.singletonFactories.put(beanName, () -&gt; {
// 创建代理对象
return finalInstance;
});
populateBean(instance);
this.singletonObjects.put(beanName, instance);
return instance;
}
public Object getBean(String beanName) {
// 尝试从singletonObjects中取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 尝试从earlySingletonObjects取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 尝试从singletonFactories取出工厂方法
ObjectFactory&lt;?&gt; objectFactory = this.singletonFactories.get(beanName);
if (objectFactory != null) {
singletonObject = objectFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
return singletonObject;
}
return createBean(beanName);
}
private void populateBean(Object object) {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(CodeBearAutowired.class) != null) {
Object value = getBean(field.getName());
try {
field.setAccessible(true);
field.set(object, value);
} catch (IllegalAccessException ignored) {
}
}
}
}
}
</code></pre>
<p>调用方法:</p>
<pre><code> public static void main(String[] args) {
Cycle cycle = new Cycle();
cycle.init();
System.out.println(((UserService) cycle.getBean(&quot;userService&quot;)).orderService);
System.out.println(((OrderService) cycle.getBean(&quot;orderService&quot;)).userService);
}
</code></pre>
<p>运行结果:</p>
<pre><code><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="02616d6f2c616d6667606763702c617b616e67712c4d70666770516770746b616742363b673661603a37">[email&#160;protected]</a>
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="96f5f9fbb8f5f9f2f3f4f3f7e4b8f5eff5faf3e5b8c3e5f3e4c5f3e4e0fff5f3d6a4a7a5a5f5aef0ae">[email&#160;protected]</a>
</code></pre>
<h3>二级缓存能不能解决循环依赖,三级循环到底有什么用?</h3>
<p>我的观点可能和网上的主流观点有很大的出入,至于我的观点是对是错,请各位自行判断。</p>
<p>二级缓存可以解决循环依赖哪怕aop bean循环依赖上面我们已经提到了我们可以创建完对象直接创建代理对象把代理对象放入二级缓存这样我们从二级缓存获得的一定是aop bean并非是bean本身。</p>
<p>三级缓存有什么用?网上的主流观点是为了解决循环依赖,还有就是为了效率,为了解决循环依赖,我们上面已经讨论过了,我的观点是二级缓存已经可以解决循环依赖了,下面就让我们想想,和效率是否有关系?</p>
<p>我的观点是没有关系,理由如下:
我们把【获得对象的工厂方法】放入了map</p>
<ul>
<li>如果没有循环依赖这个map根本没有用到和效率没有关系</li>
<li>如果是普通bean循环依赖三级缓存直接返回了bean和效率还是没有关系</li>
<li>如果是aop bean循环依赖如果没有三级缓存直接创建代理对象放入二级缓存如果有三级缓存还是需要创建代理对象只是两者的时机不同和效率还是没有关系。</li>
</ul>
<p>有了这篇博客的基础当你再看其他关于Spring循环依赖的博客应该会轻松的多因为我们毕竟自己解决了循环依赖Spring的循环依赖只是在我们之上做了进一步的封装与改进。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/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":"70998023dc938b66","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>