disgare 的博客
首页
博客
分类
标签
首页
博客
分类
标签
  • 网络

    • 计算机网络学习笔记
    • 网络安全相关
    • 域名和子网掩码
    • CORS 跨域资源共享
    • DNS、HTTP 与 HTTPS
    • Server-Sent Events (SSE)
    • WebSocket 长连接
  • 计算机基础

    • 操作系统 IO 相关知识
    • 操作系统学习笔记
    • 程序的机器级表示
    • 音频文件基础
    • 正则表达式相关概念
    • ffmpeg 的安装以及实现音频切分功能
    • Hex 和 Base64 编码
    • XML 的使用
  • 数据结构与算法

    • 动态规划算法学习笔记
    • 基于比较的排序算法的最坏情况下的最优下界为什么是O(nlogn)
    • 集合与数据结构学习笔记
    • 面试常见算法总结
    • 算法导论第二部分排序学习笔记
    • 算法导论第一部分学习笔记
  • Java

    • 对象之间的映射与转换
    • 反射学习笔记
    • 泛型相关概念
    • 关于 boolean 类型的坑
    • 如何使用 lambda 表达式实现排序
    • CompletableFuture 相关用法
    • CompletableFuture 源码浅要阅读
    • FutureTask 源码阅读
    • Guava 常用 API
    • Guava 源码阅读:Multimap 相关
    • Jackson 的各种使用
    • Java 的 Excel 相关操作
    • java 的常见性能问题分析以及出现场景
    • java 基础知识
    • JAVA 枚举的基础和原理
    • Java 图片文件上传下载处理
    • Java 序列化
    • Java 异常
    • Java 语法糖
    • Java 中关于字符串处理的常用方法
    • Java 中强、软、弱、虚引用
    • JAVA 注解小结
    • Java Http 访问框架
    • Java Stream 的使用
    • Java8 新特性
    • netty 学习笔记
    • Scanner 的各种用法
    • Servlet 学习笔记
    • String、StringBuffer、StringBuilder 学习笔记
  • JVM

    • 虚拟机执行子系统
    • JVM 自动内存管理
    • Linux 中 JVM 常用工具以及常见问题解决思路
  • Linux

    • crontab 表达式
    • Linux 常见命令
    • Linux 文件系统
  • 中间件

    • 关于定时任务原理
    • 详解 kafka
    • ES 搜索引擎
    • flink 提交流程
    • Grape-RAG
    • Hadoop 基础原理
  • 多线程

    • 多线程基础学习笔记
    • 简单了解并发集合
    • 如何手写单例
    • 深入理解 java 多线程安全
    • 生产者消费者问题
    • 线程池作用、用法以及原理
    • AQS 组件
    • ThreadLocal 原理以及使用
  • 非关系型数据库

    • Redis 集群
    • Redis 数据结构、对象与数据库
    • Redis 学习笔记
  • 关系型数据库

    • B+ 树的插入、删除和数据页分裂机制
    • MySQL 的 binglog、redolog、undolog
    • MySQL 的记录存储结构、存储引擎与 Buffer Pool
    • MySQL 基本的特性
    • MySQL 开发规范
    • MySQL 事务与锁与 MVCC
    • MySQL 数据类型、字符集相关内容
    • MySQL 索引与索引优化
    • PostgreSQL 更新数据时 HOT优化
    • PostgreSQL 相关用法
  • Python

    • Python 基础语法
    • Python 学习
  • Spring 项目

    • Lombok 的常用注解
    • maven 小结
    • MyBatis 框架的使用
    • MyBatis 重要知识点总结
    • MybatisPlus 的使用
    • Spring 框架基础使用
    • Spring 事务相关
    • Spring IOC 的原理及源码
      • IOC 前置知识
        • 为什么要设计模式
        • 依赖倒置原则
        • IOC(控制反转)与 DI(依赖注入)
        • IOC 容器
        • Spring IOC 的大体思路
        • 构造器、注解、方法注入
      • 源码
        • 容器创建
        • refresh
        • BeanDefinition
        • invokeBeanFactoryPostProcessors
        • initApplicationEventMulticaster
        • bean 创建
        • getBean
        • 循环依赖
        • 工厂 bean
        • bean 的生命周期
      • 使用 IOC
        • bean 的作用域
        • 如何定义一个 bean
        • 如何使用 bean
        • @Autowired 与 @Resource 的区别
    • Spring AOP 的使用和原理
    • SpringBoot 的原理
    • SpringBoot 基础使用
    • SpringWeb 重要知识点
  • 分布式

    • 初步了解 docker
    • 从 ACID 到 BASE 事务处理的实现
    • 访问远程服务
    • 分布式 id
    • 分布式缓存相关问题
    • 分布式集群理论和分布式事务协议
    • 分布式架构的观测
    • 分布式一致性算法
    • 负载均衡 Load Balancing
    • 关于分布式系统 RPC 中高可用功能的实现
    • 集群间数据同步的目的
    • 三高问题下的系统优化
    • 数据库分库分表
    • 详解 Spring Cloud
    • Dubbo 基础概念
    • Gossip 协议
    • nginx 学习笔记
    • Protobuf 通信协议
    • Zookeeper 基础学习
  • 架构设计

    • 参数校验与异常处理
    • 抽象方法与设计模式
    • 代码整洁之道
    • 权限系统设计
    • 用低内存处理大量数据
    • 设计模式——策略模式
    • 设计模式——过滤器模式在 Spring 中的实践
    • 状态模式
    • 统一结果返回
    • 为什么要打日志?怎么打日志?打什么日志?
    • 运维监控常见指标含义
    • 资深研发进阶
    • DDD 架构学习笔记
    • Java 常用的规则引擎
    • MVC 架构学习笔记
  • AI

    • 如何编写 Prompt
    • Agent 工程架构
    • LLM 相关内容
    • NLP 相关知识
    • vibe coding 最佳实践
    • windows 下 ollama 迁移到 D 盘
  • 开发工具

    • 如何画时序图、流程图、状态流转图
    • excel 关于 =vlookup 的用法
    • git 的学习以及使用
    • IDEA 插件推荐
    • IDEA 常用快捷键以及调试
    • Shell 脚本
    • swagger 的使用
  • 前端

    • 简单了解前端页面开发
    • 伪静态是什么
    • GitHub Pages 部署教程
    • Vercel 部署教程
    • vue-admin-template 简单使用
    • VuePress 博客搭建指南
  • 项目

    • 面试刷题网——技术方案
    • 影视资源聚合站——技术方案
  • 问题记录

    • 定时任务单线程消费 redis 中数据导致消费能力不足
    • 提供可传递的易受攻击的依赖项
    • Liteflow 在 SpringBoot 启动时无法注入组件问题 couldn‘t find chain with the id[THEN(NodeComponent)]
  • 金融

    • 股票分析——关于电力
    • 股票技术面——量价关系
    • 股票技术面——盘口
    • 股票技术面——基础
    • 基础的金融知识
    • 基金与股票
    • 韭菜的自我总结
    • 聊聊价值投资
  • 其他

    • 程序员职场工作需要注意什么
    • 创业全链路SOP:从灵光一现到系统化增长的实战指南
    • 观罗翔讲刑法随笔
    • 价格和价值
    • 立直麻将牌效益理论
    • 梅花易数学习笔记
    • 压力管理
2023-03-03
Spring 项目
目录

Spring IOC 的原理及源码

# IOC 前置知识

# 为什么要设计模式

首先我想讲一个比较抽象的概念,没有在实际项目上写过代码的小伙伴可能不知道设计模式有什么用。以下通过一个例子描述一下为什么需要设计模式

首先,我有一只笔

public class Pen {

    public void draw() {
        System.out.println("draw");
    }
}
1
2
3
4
5
6

然后有一个画家用了这只笔

public class Painter {
    
    public void usePen() {
        Pen pen = new Pen();
        pen.paint();
    }
}
1
2
3
4
5
6
7

这段代码在正常情况下没有什么问题,但是,在企业生产环境中往往有很多画家要用这只笔。现在我有10000个类,Painter1,Painter2...Painter10000,他们都调用了这个笔的 paint 方法

这时候,这支笔突然换路径了。10000个画家都找不到这只笔了,如果想要正常使用这只笔,必须将10000个画家的调用路径都修改一遍。如果这么做的话程序员也不用写代码了,天天在这为画家换笔

再比如,这支笔的 paint 方法换成了 write 方法,等等等等,像这种问题我们称之为强依赖(强耦合),10000个画家都依赖与这只笔

我们需要一定的方式去解决强耦合的问题,这种方式被称之为设计模式,设计模式的目的是让程序中的某个类发生改变是其他的类不用发生巨大修改,比如下面的工厂模式

public class PenFactory {
    
    public Pen getPen() {
        return new Pen();
    }
}
1
2
3
4
5
6

我现在有一个工厂去造这支笔,所有的画家都从这个工厂的 getPen 中获得笔,现在笔的路径改了,对我们10000个画家都不会产生什么影响,我们只要在工厂中修改一次笔的路径就行了。这样画家和笔产生了解耦

下面的 IOC 中无时无刻不体现了这种思想

# 依赖倒置原则

IOC 根据依赖倒置原则演变而来,那依赖倒置原则解决了什么问题呢?

如果是面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本

底层是上层的组件,依赖倒置原则是面向接口编程的一种思路。一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化

依赖倒置原则是设计模式的六大原则之一,简单来说就是:针对接口编程,依赖于抽象而不依赖于具体

# IOC(控制反转)与 DI(依赖注入)

为什么要有 IOC 呢?

试想一下,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,也就是对各个类的控制权在我们手中,这样的话我们需要手动的去创建各个类并且维持他们的依赖关系,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合

举个例子:现有类 A 依赖于类 B,如果没有 IOC 的话,可能需要在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来。如果 B 还依赖 C 的话,C 可能也需要 new 一个 B,并且这个 B 里面还要 new 一个 A

而解决问题的方案就是 IOC。既然我们手动去操作非常麻烦,我们可以写一段代码去操作这些依赖关系啊。IOC 核心概念就是将你设计好的对象以及他们的依赖关系交给第三方容器控制。IOC 容器和 DI 让上层从被动接受一个底层组件,变成主动选择一个底层组件,这样我们就不需要去思考这些对象的依赖关系,使系统耦合度降低,所以我们得出以下结论:

  • 控制:指的是对象创建(实例化、管理)的权力
  • 反转:控制权交给外部环境(IOC 容器)

而所谓的依赖注入,就是把底层类作为参数传入上层类、将依赖项注入到被依赖项中,实现上层类对下层类的控制。此时就体现出依赖抽象的好处了:我们可以注入任何抽象类的子类,让程序变的更加多样性

# IOC 容器

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。这句话在设计模式中体现的淋漓尽致,任何强耦合的对象在他们之间加一次中间件就能解耦,IOC 容器就是负责解耦的容器,你可以把它看成是一个装对象的集合

IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖

IOC 容器有两个好处:一是容器管理所有的对象,我们不需要去手动创建对象;二是容器管理所有的依赖项,在创建实例的时候不需要了解其中的细节

而 Spring 对 IOC 容器的具体实现就是 BeanFactory

啥是 BeanFactory 呢,BeanFactory 是 Spring 框架中最基础的容器接口,提供了以下核心功能:

  • 依赖注入:管理 bean 的创建和依赖关系
  • 配置机制:支持多种配置方式(XML、注解等)
  • 生命周期管理:控制 bean 的初始化和销毁
  • 延迟加载:默认情况下,bean 是按需加载的(懒加载模式)

BeanFactory 会读取项目整体的配置,和所有被扫描到的类,生成一个 map 进行管理。而 ApplicationContext 是 BeanFactory 的子接口,提供了更多企业级功能:

  • AOP 支持:提供声明式 AOP 功能
  • 国际化:消息资源处理(i18n)
  • 自动注册:自动注册 BeanPostProcessor 等特殊 bean

# Spring IOC 的大体思路

首先 IOC 容器需要知道这个类的具体位置才能找到对应的对象,而我们就用 xml 或者注解的方式标记这个类的位置

然后 Spring 容器通过反射以获得该类的对象(反射的作用是可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性)

IOC 容器中存放了程序需要的所有的对象,这些对象只会被创建一次(单例),容器将这些对象包装成一个个 bean(带有对象信息以及该对象的结构体),IOC 容器简单来说不过是一个以该对象名称为 key、对应 bean 为 value 的 map。当然,只是简单来说

IOC 容器完成创建这一步之后会根据我们所需要的方式来管理 bean 的依赖

# 构造器、注解、方法注入

Spring 官方推荐使用构造器注入,理由如下:

// 1. 构造器注入
@Service
public class OrderService {
    private final OrderRepository repo;

    public OrderService(OrderRepository repo) {
        this.repo = repo;
    }
}

// 2. Setter 注入
@Service
public class OrderService {
    private OrderRepository repo;

    @Autowired
    public void setOrderRepository(OrderRepository repo) {
        this.repo = repo;
    }
}

// 3. 字段注入
@Service
public class OrderService {
    @Autowired
    private OrderRepository repo;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • 1,构造器注入可以将依赖设置为 final,确保依赖的不可变性。而其他两种 java 对象创建后,依赖有可能是 null,因此 spring 内部可能在容器创建的时候做一些非空检查
  • 2,构造器注入可以使依赖关系更加清晰,方便测试,可读性更好
  • 3,构造器注入的话,类只是一个普通 POJO,没有任何 Spring 注解也能正常使用(自己 new)
  • 4,构造器注入解决不了循环依赖,Spring 会直接抛 BeanCurrentlyInCreationException。鼓励工程师优先重构设计,消除循环依赖。实在不行可以使用 @Lazy 延迟初始化

# 源码

来看看 IOC 容器是如何实现上面的过程的

最常用的 IOC 容器是 ClassPathXmlApplicationContext,我们就从它开始,它的类继承图如下 在这里插入图片描述 不要害怕,比较重要的接口也只有几个

BeanFactory 接口定义了容器的最基本功能,它可以读取类的配置文档,管理类的加载,实例化,维护类之间的依赖关系。实现了此接口的类才能叫容器(实现它的容器实例化时并不会初始化配置文件中定义的类,初始化动作发生在第一次调用时)

ApplicationContext 接口除了提供容器的基本功能外还提供了很多的扩展功能(实现它的容器实例化时就会将配置文件中定义的类初始化)

其他的比较重要的接口:

ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,大家看源码会发现,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的

ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系

AutowireCapableBeanFactory 就是用来自动装配 Bean 用的,上图并没有显示它。但是不继承不代表不可以使用组合

# 容器创建

从创建 bean 工厂开始看看它的过程。需要重点关注的点是:refresh 重建工厂,bean 定义,别名处理,bean 覆盖

# refresh

步骤1:读取配置创建 bean 工厂

	//这一行我们开始创建bean工厂
	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

	//最终都会调用这个方法来创建工厂
    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        //读取配置
        this.setConfigLocations(configLocations);
        if (refresh) {
        	//主要方法,调用这个方法创建容器,这个方法可以手动调用
            this.refresh();
        }
        
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

this.refresh() 其实调用了它父类 AbstractApplicationContext 的 refresh 方法

有没有想过这个方法为什么叫 refresh 而不叫 create?

因为容器建立以后,我们可以通过调用 refresh() 方法重建,refresh() 会将原来的容器销毁,然后再重新执行一次初始化操作,因此命名为 refresh 而不是 init 或者其他名字

bean 容器构建经过了13个重要的方法,这13个方法就是整个 Spring 核心的处理流程,在里面也可以看到 bean 的生命周期

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            /**
             * 1,做容器刷新前的准备工作,这里会设置容器的启动时间、设置活跃状态为true、设置关闭状态为false、获取Environment对象,并加载当前系统的属性值到Environment对象中去、准备监听器和事件的集合对象,默认为空的集合
             */
            this.prepareRefresh();
            // 2,创建容器对象:DefaultListableBeanFactory,加载xml配置文件的属性值到当前工厂中,最重要的就是BeanDefinition,即bean的各种定义
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            // 3,beanFactory的准备工作,对各种属性进行填充
            this.prepareBeanFactory(beanFactory);

            try {
                // 4,子类覆盖方法做额外的处理,此处我们自己一般不做任何扩展工作,但是可以查看web中子类的代码是有具体实现的
                this.postProcessBeanFactory(beanFactory);
                // 5,调用各种beanFactory前置处理器,这里比较核心,操作对象BeanDefinition
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 6,注册bean处理器,上一步注册的是bean工厂的处理器,这一步处理的是bean的处理器,两者要处理的内容不一样
                this.registerBeanPostProcessors(beanFactory);
                // 7,为上下文初始化message源,即不同语言的消息体
                this.initMessageSource();
                // 8,初始化事件监听器多路广播器
                this.initApplicationEventMulticaster();
                // 9,留给子类来初始化其他的bean
                this.onRefresh();
                // 10,在所有注册的bean中查找listener bean,注册到消息广播器中
                this.registerListeners();
                // 11,实例化剩下的非懒加载的单实例,著名的getBean方法在该方法中被循环调用
                this.finishBeanFactoryInitialization(beanFactory);
                // 12,完成属性过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
                // 为防止bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件bean
                this.destroyBeans();
                // 重置active标志
                this.cancelRefresh(var9);
                throw var9;
            } finally {
            	// 13,清空缓存,删除例如 BeanDefinition 的解析缓存、方法/字段的反射缓存、注解元数据缓存等
                this.resetCommonCaches();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

13个重要方法需要关注:

1,根据配置创建 beanFactory(bean 工厂),根据配置生成 bean 定义 2,获取并且注册 beanFactory 处理器、获取并且注册 bean 处理器 3,注册并且管理事件监听器 4,循环调用 getBean,实例化非懒加载的单实例 5,后置处理,清除缓存、发布事件等

# BeanDefinition

这个接口非常重要,配置文件被 Springxml 解析后,这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们配置的一个个类的信息都会存放在一个个BeanDefinition中

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    int ROLE_APPLICATION = 0;
    .....
    }
1
2
3
4
5
6

步骤2:注册 Bean

读取完配置之后,会以 beanname 为键,beanfefinition 为值,存放在一个 map 中,这个过程叫注册 bean

   //调用此函数开始注册
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
1
2

别名处理:

使用 map 保持别名,遇到别名时,先把别名变成 beanName

   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName, alias);
      }
   }
1
2
3
4
5
6

bean 覆盖:指多个 bean 的名字相同,默认情况下 spring 支持 bean 覆盖,在注册 bean 遇到 bean 覆盖时会这样处理

   BeanDefinition oldBeanDefinition;
   oldBeanDefinition = this.beanDefinitionMap.get(beanName);
   if (oldBeanDefinition != null) {
      if (!isAllowBeanDefinitionOverriding()) {
         // 如果不允许覆盖的话,抛异常
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription()...
      }
      else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
         // log...用框架定义的 Bean 覆盖用户自定义的 Bean 
      }
      else if (!beanDefinition.equals(oldBeanDefinition)) {
         // log...用新的 Bean 覆盖旧的 Bean
      }
      else {
         // log...用同等的 Bean 覆盖旧的 Bean,这里指的是 equals 方法返回 true 的 Bean
      }
      // 覆盖
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

完成注册的是这些语句,会有两个集合保存信息

         // 将 BeanDefinition 放到这个 map 中,这个 map 保存了所有的 BeanDefinition
         this.beanDefinitionMap.put(beanName, beanDefinition);
         // 这是个 ArrayList,所以会按照 bean 配置的顺序保存每一个注册的 Bean 的名字
         this.beanDefinitionNames.add(beanName);
1
2
3
4

# invokeBeanFactoryPostProcessors

spring 容器获取 bean 定义后,不会立刻初始化 bean,而是调用 invokeBeanFactoryPostProcessors 方法做一些增强处理

如果需要在所有 bean 加载之前做一些处理,需要继承 BeanFactoryPostProcessor 接口,典型应用场景如动态注册 Bean(如 @Configuration 类中的 @Bean 方法)、条件化加载 Bean(如 @Conditional 的实现)、AOP 代理增强(如 @EnableAspectJAutoProxy 的底层支持)等

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 修改或检查 BeanDefinition
        System.out.println("正在处理 BeanFactory...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# initApplicationEventMulticaster

此方法初始化和管理各种事件,ApplicationEventMulticaster 是 Spring 事件机制的核心组件,负责注册事件监听器(ApplicationListener):存储所有监听特定事件的监听器,并且广播事件(publishEvent):当事件发布时,通知所有匹配的监听器

事件需要用户自定义,当然 spring 容器也内置了一些自己的事件,比如:

  • ContextRefreshedEvent:容器初始化完成(refresh() 方法结束时)
  • ContextStartedEvent 调用:start() 方法时
  • ContextStoppedEvent:调用 stop() 方法时
  • ContextClosedEvent:容器关闭时

如果需要接受事件,可以实现以下方法(以接受容器初始化完成事件为例):

@Component
public class MyContextListener {
    @EventListener
    public void handleContextRefresh(ContextRefreshedEvent event) {
        System.out.println("Spring容器初始化完成");
    }

    @EventListener
    public void handleContextClose(ContextClosedEvent event) {
        System.out.println("Spring容器关闭");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

如果想在 spring 容器启动完毕后执行一段逻辑,除了可以监听 spring 初始化完成的事件,还可以实现 ApplicationRunner 接口,这个接口是 spring 对事件的封装

# bean 创建

需要关注的点是:两次循环依赖、作用域判断、对象创建、实例化时 CGlib 处理,并且有很多步骤都是认证这个 bean 是否合法

# getBean

在以上这些结束后,Spring 遍历 map 集合,将所有的 bean 初始化,初始化的过程封装到了 getBean 方法里

getBean 应该是我们获取 bean 使用的方法(你也可以使用这个方法来获取对象),所以这个方法有一层判断,如果在单例池中存在这个 bean,直接返回这个 bean

   final String beanName = transformedBeanName(name);
   Object bean; 
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
   	   ......
   	   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }
1
2
3
4
5
6
7

接下来是一大串 bean 认证环节(认证这个 bean 是否合法)

在认证的过程中有一步,需要先初始化依赖的 bean,此时初始化的是 depends-on 中定义的依赖,而不是它调用的依赖,因此必定不支持循环依赖

以下过程,判断该对象依赖是否为空,如果有依赖,使用 isDependent 方法判断是否循环依赖,如果是,抛异常

         String[] dependsOn = mbd.getDependsOn();
         if (dependsOn != null) {
            for (String dep : dependsOn) {
               if (isDependent(beanName, dep)) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
               }
               registerDependentBean(dep, beanName);
               getBean(dep);
            }
         }
1
2
3
4
5
6
7
8
9
10
11

然后进行 bean 作用域的判断,不管这个 bean 的作用域是什么(单例原型其他),总是要进入 createBean 方法中,createBean 进行一些认证又会转入 doCreateBean 中

实例化 bean

      instanceWrapper = createBeanInstance(beanName, mbd, args);
1

进入此方法,又是一段认证过程,然后实例化时分两种情况,存在方法覆写时使用 CGLIB 代理返回一个代理对象,没有就使用 java 反射直接创建一个 bean 对象实例(之间可能还会使用有参构造创建对象或者无参构造创建对象)

随后会进入 DI 的流程,在依赖注入的时候,如果发现需要注入的对象尚未初始化,还需要触发注入对象的初始化动作,同时在注入的时候也会分为按名称注入和按类型注入(除此之外还有构造器注入等方式)

# 循环依赖

循环依赖指的是当我们有两个类 A 和 B,其中 A 依赖 B,B 又依赖了 A,或者多个类也一样,只要形成了一个环状依赖那就属于循环依赖

如果换成我们来开发,我们会如何解决这个问题呢?其实方法也很简单,大家应该都能想到,那就是当我们把对象初始化之后,在没有注入属性之前,就先缓存起来,这样,就相当于缓存了一个半成品 Bean 来提前暴露出来供注入时使用,这也是 Spring 处理循环依赖的方法

但是以下三种情况无法解决循环依赖问题:

  • 只有 set 注入才能解循环依赖,构造器注入不行,因为 set 在对象被实例化之后才进行 DI,在实例化与 DI 之间有操作空间,如果在构造器是解单例的循环依赖可能就要在 JVM 上操作操作了,构造器注入代码如下:
@Controller
public class FooController {
  
  private final FooService fooService;
  
  @Autowired
  public FooController(FooService fooService) {
      this.fooService = fooService;
  }
}
1
2
3
4
5
6
7
8
9
10
  • 非单例模式 Bean,因为只有在单例模式下才会对 Bean 进行缓存。如果是原型模式,会走到 AbstractBeanFactory 类中下面的判断,抛出异常。原因很好理解,创建新的 A 时,发现要注入原型字段 B,又创建新的 B 发现要注入原型字段 A。这就套娃了,你猜是先 StackOverflow 还是 OutOfMemory?Spring 怕你不好猜,就先抛出了 BeanCurrentlyInCreationException
if (isPrototypeCurrentlyInCreation(beanName)) {
  throw new BeanCurrentlyInCreationException(beanName);
}
1
2
3
  • 手动设置了 allowCircularReferences=false,则表示不允许循环依赖

来看看 Spring 是怎么处理的,容器会创建三级缓存,表示此 bean 是否正在创建,这些缓存主要用来解决循环依赖。这就是面试中常常被提到的 Spring 如何处理循环依赖的问题

  • 一级缓存:Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例。这里面的 bean 已经完成了 DI,项目启动完成后获取 bean 实例时从此获取,因此这其实不是缓存而是 IOC 容器
  • 二级缓存:Map<String,Object> earlySingletonObjects,早期曝光对象,用于保存已经实例化、已经完成 DI 或者尚未完成 DI、一定没有执行初始化回调的 Bean
  • 三级缓存:Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,bean 创建工厂则是用于延迟获取 Bean,只有在真正调用 getObject() 时才会执行实际的 Bean 创建逻辑

刨除掉所有的异常情况,进入判断这个 bean 是否允许循环依赖的代码,如下:

   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   if (earlySingletonExposure) {
      if (logger.isDebugEnabled()) {
         logger.debug("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, new ObjectFactory<Object>() {
         @Override
         public Object getObject() throws BeansException {
            return getEarlyBeanReference(beanName, mbd, bean);
         }
      });
   }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

具体判断循环依赖的逻辑是这样的:

1,创建 A 实例,实例化的时候把 A 对象工厂放⼊三级缓存,表示 A 开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道。这时候曝光的对象仅仅是构造完成,还没注入属性和初始化,提前曝光的对象被放入 Map<String, ObjectFactory<?>> singletonFactories 缓存中,这里并不是直接将 Bean 放入缓存,而是包装成 ObjectFactory 对象再放入

2,A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B

3,同样,B 注⼊属性时发现依赖 A,它就会从缓存里找 A 对象。依次从⼀级到三级缓存查询 A,从三级缓存通过对象⼯⼚拿到 A,发现 A 虽然不太完善,但是存在,此时构建 A,把 A 放⼊⼆级缓存,同时删除三级缓存中的 A,B 已经实例化并且初始化完成,把 B 放入⼀级缓存

4,接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除⼆级缓存中的 A,同时把 A 放⼊⼀级缓存

那么问题来了,为什么要三级缓存,为什么需要包装?如果只实现这个功能的话二级缓存不是够了吗?二级缓存中存放的,就是未完成的 bean 啊。这么做的好处是什么?二级缓存绰绰有余不是吗

其实三级缓存中放的是生成具体对象的匿名内部类,在一个 bean 被 AOP 的时候,它才会⽣成代理对象,没有的话就会返回普通对象。Spring 不提前创建好对象,在出现循环依赖被其他对象注入时,才会根据三级缓存实时生成代理对象或者非代理对象

没有三级缓存的情况(二级缓存方案),会强制提前创建代理,在 Bean 实例化后,尚未初始化时,就必须立即创建代理对象放入二级缓存,而代理对象的创建是需要使用后置处理器 AnnotationAwareAspectJAutoProxyCreator 的,因为 bean 的生命周期中,依赖注入在 bean 初始化之前,如果采用二级缓存的方案,bean 初始化和依赖注入就一起完成了

但是用了三级缓存,就可以先把要生成的类记录下来,等到需要生成该类或者循环依赖发生时才实例化该类,用这种惰性加载的方式最大程度的不打破 bean 的生命周期,同时由于 bean 还没有完成属性注入,某些 AOP 逻辑(比如基于属性的切面匹配)可能无法确定,但是如果出现了循环依赖,还是会打破 bean 生命周期的

如此,处理完循环依赖后,Spring 继续设值,DI。DI 完成之后,这还不是一个完整的 bean,它还需要进行初始化 initializeBean,也就是 bean 生命周期的一部分。Spring 处理问题的思路,简单来说就是缓存,更简单一些,是使用 map,将创建过的对象都保存起来。这也就是为什么有人说,处理该问题的思路是一道简单的算法题

但是,循环依赖的问题还没有结束,就算 Spring 已经处理了循环依赖,在平时的代码中还是可能会出现循环依赖问题(如果设置了不允许支持循环依赖的话)

Error creating bean with name 'trackInfoServiceImpl': Injection of resource dependencies failed; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Errorcreating bean with name 
'salesClueService': Requested bean is currently in creation: Is there an unresolvable circular reference?
1
2
3

这种情况一般出现在系统设计的不好,Service 没有做分层,导致同级业务之间相互调用的问题,这种时候我们一般在方法中使用 getBean 在方法中强行获取某个 service 对象

# 工厂 bean

Spring 有两种 bean,一种是普通 bean,另外一种是工厂 bean,我们在项目中一般使用普通 bean 就绰绰有余了,但是工厂 bean 可以用来构建更加复杂的对象,比如你想在 bean 初始化的时候执行一段复杂的逻辑(如读取配置、设置缓存等),可以使用工厂 bean 来实现

普通 bean 调用 getBean 方法的时候会返回这个 bean 的对象,而这种工厂 bean 的特性就是在调用 getBean 时不一定返回该 bean 对象,甚至返回值都不一定是该类类型,其返回值是该 FactoryBean 的 getObject 方法所返回的对象。创建出来的对象是否属于单例由 isSingleton 中的返回决定

你可以实现它以重写 FactoryBean

public class FactoryBeanImpl implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        ......
    }
    @Override
    public Class<?> getObjectType() {
        ......
    }
}
1
2
3
4
5
6
7
8
9
10

那这种 bean 有什么好处呢,它可以基于工厂设计模式创建对象。在某些情况下,实例化 Bean 过程比较复杂,如果按照传统的方式,则需要在 bean 中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 org.springframework.bean.factory.FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 Bean 的逻辑

# bean 的生命周期

就是 bean 在它生成、使用、销毁过程中可能会发生什么事情:

1,实例化 (Instantiation):通过构造函数或工厂方法创建 Bean 实例。此时对象处于原始状态,属性未注入,这个 bean 还是一个对象,执行了对象的 init 方法、构造方法

2,属性赋值 (Population of Properties):设置 Bean 的属性值和依赖(通过 setter 或字段注入),此时处理 @Autowired、@Value 等注解发挥作用

3,Bean 初始化,这一步会做很多操作,主要是实现 spring 为 bean 提供的一些额外处理

  • Aware 接口回调(Aware Interface Callbacks):Aware 接口是一组标记接口,在 initializeBean 方法中会被调用
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
    
    @Override
    public void setBeanName(String name) {
        System.out.println("3.1 Aware回调 - BeanName: " + name);
    }
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        System.out.println("3.2 Aware回调 - BeanFactory");
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        System.out.println("3.3 Aware回调 - ApplicationContext");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 初始化前处理 (Pre-Initialization):调用 BeanPostProcessor.postProcessBeforeInitialization(),这就是上面的 bean 处理器需要注册的内容
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if(bean instanceof MyBean) {
            System.out.println("4,BeanPostProcessor前置处理 - BeforeInitialization: " + beanName);
        }
        return bean;
    }
}
1
2
3
4
5
6
7
8
9
10
  • 初始化 (Initialization):有多种初始化处理方式,比如调用自定义的 init-method ,用 XML 配置或 @Bean(initMethod="...")) 方式实现、执行 @PostConstruct 注解的方法等
public class MyBean implements InitializationBean {
    
    @PostConstruct
    public void initAnnotation() {
        System.out.println("5.1 初始化 - @PostConstruct方法");
    }
    
    @Override
    public void afterPropertiesSet() {
        System.out.println("5.2 初始化 - InitializingBean.afterPropertiesSet()");
    }
    
    public void initMethod() {
        System.out.println("5.3 初始化 - init-method");
    }
}

// 配置类中指定init-method,初始化时就会调用
@Configuration
public class AppConfig {
    @Bean(initMethod = "initMethod")
    public MyBean myBean() {
        return new MyBean();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 初始化后处理 (Post-Initialization):调用 BeanPostProcessor.postProcessAfterInitialization()
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if(bean instanceof MyBean) {
            System.out.println("6. BeanPostProcessor后置处理 - AfterInitialization: " + beanName);
        }
        return bean;
    }
}
1
2
3
4
5
6
7
8
9
10

4,销毁 (Destruction):和初始化一样,也有多种实现方式

实现 DisposableBean 接口的 destroy() 方法,调用自定义的 destroy-method、调用 @PreDestroy 注解的方法等

public class MyBean implements DisposableBean {
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("8.1 销毁 - @PreDestroy方法");
    }
    
    @Override
    public void destroy() {
        System.out.println("8.2 销毁 - DisposableBean.destroy()");
    }
    
    public void destroyMethod() {
        System.out.println("8.3 销毁 - destroy-method");
    }
}

// 配置类中指定destroy-method
@Configuration
public class AppConfig {
    @Bean(destroyMethod = "destroyMethod")
    public MyBean myBean() {
        return new MyBean();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 使用 IOC

# bean 的作用域

bean 的作用域用于确定哪种类型的 bean 实例应该从 Spring 容器中返回给调用者,从生成方式上来讲只有单例、原型和其他,因为源码是这么写的

1,singleton:单例 bean,获取的每一个 bean 地址都相同,spring 默认使用这个生成方式,当创建起容器时就同时自动创建了一个 bean 的对象,不管你是否使用,他都存在了

2,prototype:原型 bean,每一次使用都会生成一个新对象,创建容器的时候并没有实例化

3,request:每一次 http 请求都会生成一个新对象,这个 bean 只作用在 http 的 request 中

4,session:每一次 http 请求都会生成一个新对象,这个 bean 只作用在 http 的 session 中。会话和请求的区别是,会话指客户端与服务器之间的一系列交互组成的逻辑关联,包含多个请求和响应周期,服务器使用会话机制(如Session ID)来跟踪用户状态

你可以使用 @Scope 来声明作用域,scope 是范围的意思,比如

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
1

除了单例模式其他都不怎么会使用到

# 如何定义一个 bean

@Component:通用的 bean 定义,如果不知道是哪一层的 bean 就使用这个

@Repository:仓库的意思,对应持久层 dao 层的 bean

@Service:服务层,主要涉及一些复杂的逻辑

@Controller:对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面,这里除了定义为 bean 以外,还会将类中方法注册到默认 controller 中,处理 HTTP 请求,并返回视图或响应数据

@Bean:将一个方法的返回值标记为 bean,是对方法使用的注解(可以在没有源码的情况下将第三方代码中的类标记为组件,这种情况下使用 component 就不能解决这种需求了)

@Mapper:设置一个 bean,对应持久层 dao 的注解,同时该 bean 还可以被 mybatis 扫描到

# 如何使用 bean

使用 Autowired 或者 Resource 来进行 DI,Autowired 根据类型注入,是 spring 自带的注解,Resource 根据名称优先注入,来源于 jsr-250,如果找不到对应的名称会按照类型来注入。因此 resource 更优秀一些(如果根据类型来可能会返回多个 bean)

spring 可以注入 List 或者 Map,支持这种基于接口实现类的直接注入,比如如下示例

    @Autowired  
    private List<BeanInterface> list;  
        
    @Autowired  
    private Map<String, BeanInterface> map;  
1
2
3
4
5

接口的实现可以使用遍历来让目标数据执行每个实现类的方法

            hotelListProcessors.forEach(item -> item.process(context));
1

Spring 把 bean 放入了 List 中 那个这个顺序怎么控制呢?可以在实现类中加入 @Order(value) 注解即可 ,值越小越先被初始化越先被放入 List

# @Autowired 与 @Resource 的区别

Autowired 属于 Spring 内置的注解,默认的注入方式是根据类型进行匹配,通常被称为 byType。当它通过类型找到多个对象的时候,可以配合 Qualifier 注解来实现按照名称的依赖注入

@Autowired
@Qualifier("mainDataSource")
private DataSource dataSource;
1
2
3

还找不到的话,那就只能报错了

对于 @Autowired 声明的数组、集合类型,spring 并不是根据 beanName 去找容器中对应的 bean,而是把容器中所有类型与集合(数组)中元素类型相同的 bean 构造出一个对应集合,注入到目标 bean 中

Resource 属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为 byType。同时你也可以在该注解直接指定 bean 名称

#Spring
最后更新: 3/5/2026, 6:02:53 AM
Spring 事务相关
Spring AOP 的使用和原理

← Spring 事务相关 Spring AOP 的使用和原理→

最近更新
01
vibe coding 最佳实践
02-24
02
立直麻将牌效益理论
02-23
03
伪静态是什么
02-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式