技术

上下文记忆 agentic chat 图数据库的一些考量 LLM一些探索 Agent实践 LLM预训练 向量数据库的一些考量 fastapi+sqlalchemy进行项目开发 LLM微调实践 Python协程实现 Agent Functon Calling LLamaIndex入门 Multi-Agent探索 Python虚拟机 LLM工作流编排 Python实践 下一个平台Agent 激发LLM涌现——提示工程 LLM微调理论 大佬沉思 LLM外挂知识库 LLMOps 多模态LLM Python一些比较有意思的库 Transformers源码学习 LangChain源码学习 通用分布式计算引擎Ray Python并发 go依赖注入 go collection gc的基本原理 golang性能分析及优化 数据湖 高性能计算与存储 Linux2.1.13网络源代码学习 《大数据经典论文解读》 三驾马车学习 Spark 内存管理及调优 Yarn学习 从Spark部署模式开始讲源码分析 容器狂占内存资源怎么办? 多角度理解一致性 golang io使用及优化模式 Flink学习 c++学习 学习ebpf go设计哲学 ceph学习 学习mesh kvm虚拟化 学习MQ go编译器以及defer实现 学习go 为什么要有堆栈 汇编语言 计算机组成原理 运行时和库 Prometheus client mysql 事务 mysql 事务的隔离级别 mysql 索引 坏味道 学习分布式 学习网络 学习Linux go堆内存分配 golang 系统调用与阻塞处理 Goroutine 调度过程 重新认识cpu mosn有的没的 负载均衡泛谈 单元测试的新解读 《Redis核心技术与实现》笔记 《Prometheus监控实战》笔记 Prometheus 告警学习 calico源码分析 对容器云平台的理解 Prometheus 源码分析 并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go channel codereview gc分析 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes扩缩容 神经网络模型优化 直觉上理解深度学习 如何学习机器学习 TIDB源码分析 什么是云原生 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 共识算法 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全访问机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占资源怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes类型系统 源码分析体会 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 线程排队 jib源码分析之细节 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI——容器网络是如何打通的 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 精简代码的利器——lombok 学习 《深入剖析kubernetes》笔记 编程语言那些事儿 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 如何分发计算 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 calico学习 AQS——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记 log4j学习 为什么netty比较难懂? 递归、回溯、动态规划 apollo client源码分析及看待面向对象设计 学习并发 docker运行java项目的常见问题 OpenTSDB 入门 spring事务小结 分布式事务 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型和jvm内存布局 java exception Linux IO学习 netty内存管理 测试环境docker化实践 netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker namespace和cgroup zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 深度学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM执行 git maven/ant/gradle/make使用 再看tcp kv系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 Go基础 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

架构

bert rerank微调 大模型推理tips RAG向量检索与微调 dddfirework源码分析 RAG与知识图谱 大模型推理服务框架vLLM 大模型推理服务框架 模型服务化(未完成) 大模型Post-Training 大模型训练 大模型推理 从Attention到Transformer k8s设备管理 ddd从理念到代码 如何应用LLM 小鼠如何驾驭大象(LLM)? 多类型负载协调员Koordinator controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 kubevela源码分析 容器和CPU那些事儿 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群管理 AutoML和AutoDL 特征平台 实时训练 分布式链路追踪 K8S YAML 资源清单管理方案 tensorflow原理——python层分析 如何学习tensorflow 数据并行——allreduce 数据并行——ps 推荐系统embedding原理及实践 机器学习中的python调用c 机器学习训练框架概述 tensornet源码分析 大模型训练和推理 X的生成——特征工程 tvm tensorflow原理——core层分析 模型演变 《深度学习推荐系统实战》笔记 keras 和 Estimator tensorflow分布式训练 分布式训练的一些问题 基于Volcano的弹性训练 图神经网络 pytorch弹性分布式训练 从混部到统一调度 从RNN到Attention pytorch分布式训练 CNN 《动手学深度学习》笔记 pytorch与线性回归 多活 volcano特性源码分析 推理服务 kubebuilder 学习 mpi 学习pytorch client-go学习 提高gpu 利用率 GPU与容器的结合 GPU入门 AI云平台梳理 tensorflow学习 tf-operator源码分析 k8s批处理调度/Job调度 喜马拉雅容器化实践 Kubernetes 实践 学习rpc BFF openkruise学习 可观察性和监控系统 基于Kubernetes选主及应用 《许式伟的架构课》笔记 Admission Controller 与 Admission Webhook 发布平台系统设计 k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 深入controller openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go informer源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? 《软件设计之美》笔记 mecha 架构学习 Kubernetes events学习及应用 CRI——kubelet与容器引擎之间的接口 资源调度泛谈 业务系统设计原则 grpc学习 元编程 以应用为中心 istio学习 下一代微服务Service Mesh 《实现领域驱动设计》笔记 概率论 serverless 泛谈 《架构整洁之道》笔记 处理复杂性 那些年追过的并发 服务器端编程 网络通信协议 架构大杂烩 如何学习架构 《反应式设计模式》笔记 项目的演化特点 反应式架构摸索 函数式编程的设计模式 服务化 ddd反模式——CRUD的败笔 研发效能平台 重新看面向对象设计 业务系统设计的一些体会 函数式编程 《左耳听风》笔记 业务程序猿眼中的微服务管理 DDD实践——CQRS 项目隔离——案例研究 《编程的本质》笔记 系统故障排查汇总及教训 平台支持类系统的几个点 代码腾挪的艺术 abtest 系统设计汇总 《从0开始学架构》笔记 初级权限系统设计 领域驱动理念 现有上传协议分析 移动网络下的文件上传要注意的几个问题 推送系统的几个基本问题 做配置中心要想好的几个基本问题 不同层面的异步 分层那些事儿 性能问题分析 用户认证问题 资源的分配与回收——池 消息/任务队列

标签

k8s设备管理 多类型负载协调员Koordinator controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 kubevela源码分析 容器和CPU那些事儿 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群管理 K8S YAML 资源清单管理方案 从混部到统一调度 volcano特性源码分析 kubebuilder 学习 client-go学习 tf-operator源码分析 k8s批处理调度/Job调度 喜马拉雅容器化实践 Kubernetes 实践 openkruise学习 基于Kubernetes选主及应用 Admission Controller 与 Admission Webhook k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 深入controller openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go informer源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? Kubernetes events学习及应用 CRI——kubelet与容器引擎之间的接口 资源调度泛谈 如何学习Kubernetes 以应用为中心 kubernetes operator kubernetes扩缩容 serverless 泛谈 什么是云原生 自定义CNI IPAM docker和k8s安全访问机制 Kubernetes监控 Kubernetes 控制器模型 Kubernetes资源调度——scheduler Kubernetes类型系统 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 Kubernetes源码分析——从kubectl开始 kubernetes yaml配置 CNI——容器网络是如何打通的 当我在说PaaS时,我在说什么 《深入剖析kubernetes》笔记 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件
上下文记忆 agentic chat bert rerank微调 大模型推理tips LLM一些探索 Agent实践 LLM预训练 RAG向量检索与微调 LLM微调实践 RAG与知识图谱 大模型推理服务框架vLLM Agent Functon Calling LLamaIndex入门 Multi-Agent探索 LLM工作流编排 大模型推理服务框架 模型服务化(未完成) 大模型Post-Training 大模型训练 大模型推理 从Attention到Transformer 下一个平台Agent 激发LLM涌现——提示工程 LLM微调理论 大佬沉思 LLM外挂知识库 LLMOps 多模态LLM Transformers源码学习 LangChain源码学习 如何应用LLM 小鼠如何驾驭大象(LLM)? AutoML和AutoDL 特征平台 实时训练 tensorflow原理——python层分析 如何学习tensorflow 数据并行——allreduce 数据并行——ps 推荐系统embedding原理及实践 机器学习中的python调用c 机器学习训练框架概述 tensornet源码分析 大模型训练和推理 X的生成——特征工程 tvm tensorflow原理——core层分析 模型演变 《深度学习推荐系统实战》笔记 keras 和 Estimator tensorflow分布式训练 分布式训练的一些问题 基于Volcano的弹性训练 图神经网络 pytorch弹性分布式训练 从RNN到Attention pytorch分布式训练 CNN 《动手学深度学习》笔记 pytorch与线性回归 推理服务 mpi 学习pytorch 提高gpu 利用率 GPU与容器的结合 GPU入门 AI云平台梳理 tensorflow学习 kaggle泰坦尼克问题实践 神经网络模型优化 概率论 直觉上理解深度学习 如何学习机器学习 深度学习泛谈

回头看Spring IOC

2015年06月15日

前言

为什么依赖注入只在 Java 技术栈中流行,在 go 和 cpp 没有大量使用?

深入浅出依赖注入及其在抖音直播中的应用面向对象设计带来的最直接的问题,就是对象间的依赖。对象 A 依赖于对象 B,那么对象 A 在初始化或者运行到某一点的时候,自己必须主动去创建对象 B 或者使用已经创建的对象 B。这个直接依赖会导致什么问题?

  1. 过渡暴露细节,A 只关心 B 提供的接口服务,并不关心 B 的内部实现细节,A 因为依赖而引入 B 类,间接的关心了 B 的实现细节
  2. 对象间强耦合,B 发生任何变化都会影响到 A,开发 A 和开发 B 的人可能不是一个人,B 把一个 A 需要用到的方法参数改了,B 的修改能编译通过,能继续用,但是 A 就跑不起来了
  3. A 是服务使用者,B 是提供一个具体服务的,假如 C 也能提供类似服务,但是 A 已经严重依赖于 B 了,想换成 C 非常之困难

学过面向对象的同学马上会知道可以使用接口来解决上面几个问题。如果早期实现类 B 的时候就定义了一个接口,B 和 C 都实现这个接口里的方法,这样从 B 切换到 C 是不是就只需很小的改动就可以完成。A 对 B 或 C 的依赖变成对抽象接口的依赖了,上面说的几个问题都解决了。但是目前还是得实例化 B 或者 C,因为 new 只能 new 对象,不能 new 一个接口,还不能说 A 彻底只依赖于接口了。从 B 切换到 C 还是需要修改代码,能做到更少的依赖吗?能做到 A 在运行的时候想切换 B 就 B,想切换 C 就 C,不用改任何代码甚至还能支持以后切换成 D 吗?通过反射可以简单实现上面的诉求。例如常用的接口NSClassFromString,通过字符串可以转换成同名的类。通过读取本地的配置文件,或者服务端下发的数据,通过 OC 的提供的反射接口得到对应的类,就可以做到运行时动态控制依赖对象的引入。

对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。功能越复杂的应用,对象之间的依赖关系一般也越复杂,经常会出现对象之间的多重依赖性关系。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。Michael Mattson IOC 理论提出的观点大体为:借助于“第三方”实现具有依赖关系的对象之间的解耦。全部对象的控制权全部上缴给“第三方”IOC 容器,所以,IOC 容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把 IOC 容器比喻成“粘合剂”的由来。

控制反转(IoC )这里的控制是指:一个类除了自己的本职工作以外的逻辑。典型的如创建其依赖的对象的逻辑。将这些控制逻辑移出这个类中,就称为控制反转。那么这些逻辑由谁来实现呢?各种框架、工厂类、IoC (Inversion of Control)容器等等该上场了……依赖注入是控制反转( IoC)原则的一种具体实现方式,依赖注入的目的,是为了将「依赖对象的创建」与「依赖对象的使用」分离,通俗讲就是使用方不负责服务的创建。一个 DI 框架一般需要具备这些能力:

  1. 依赖关系的配置,被依赖的对象与其实现协议之间的映射关系
  2. 依赖对象生命周期的管理,注入对象的创建与销毁
  3. 依赖对象的获取,通过依赖对象绑定的协议,获取到对应的对象
  4. 依赖对象的注入,即被依赖的对象如何注入到使用者内

java的servlet就是一种组件,而负责运行servlet的比如tomcat这些,就是一种容器,tomcat和servlet就是一种控制反转,传统你要写一个http的服务器,你需要从main函数写起,你需要编写监听端口的代码,需要写处理逻辑的代码……,那经过ioc之后,你只需要写servlet,剩下的控制,交给tomcat等http服务器就好了,几乎所有的服务器端web服务器,都是这种理念的产物。di是ioc的一种,但是di并不是ioc的全部,spring是di的一种实现方式。在早些年,java没有static import,而且static方法,还放在一个不清不楚的方法区,而不是heap里面的时候用di可以让java代码更加易于维护的同时,也可以避开性能上的一些坑比如你把所有方法都放在方法区里,那方法区有多大啊?会不会爆掉啊?现在方法区已经被取消了,归入heap了,加上static import的引入,以及zgc等功能。其实你完全不需要spring和di,你的代码运行效率都不会低多少。

Spring 容器具象化一点就是 从xml、配置类、依赖jar 等处 通过 BeanDefinitionRegistry.registerBeanDefinition 向容器注入Bean信息,然后通过BeanFactory.getBean 应用在各个位置。

谈元编程与表达能力中,笔者收获了对runtime的一个理解:当相应的行为在当前对象上没有被找到时,运行时会提供一个改变当前对象行为的入口(比如synchronized关键字,jvm 会针对性的做一些处理)。从这个视角看,Spring 也可以认为是 java 的一个runtime,通过标注@Autowire等,ApplicationContext 获取的bean 拥有 bean代码本身看不到的能力

容器解决了什么问题?

DI 容器要解决的问题是什么呢?它解决的是组件创建和组装的问题,但是为什么这是一个需要解决的问题呢?软件设计需要有一个分解的过程,所以,它必然还要面对一个组装的过程,也就是把分解出来的各个组件组装到一起,完成所需要的功能。

假设我们有一个文章服务(ArticleService)提供根据标题查询文章的功能。当然,数据是需要持久化的,所以,这里还有一个 ArticleRepository,用来与持久化数据打交道。

class ArticleService {
  //提供根据标题查询文章的服务
  Article findByTitle(final String title) {
    ...
  }
}

在 ArticleService 处理业务的过程中,需要用到 ArticleRepository 辅助它完成功能,也就是说,ArticleService 要依赖于 ArticleRepository。这时你该怎么做呢?一个直接的做法就是在 ArticleService 中增加一个字段表示 ArticleRepository。

class ArticleService {
  private ArticleRepository repository;
  public Article findByTitle(final String title) {
    // 做参数校验
    return this.repository.findByTitle(title);
  }
}

这个字段怎么初始化呢?程序员一般最直接的反应就是直接创建这个对象。

class ArticleService {
  private ArticleRepository repository = new DBArticleRepository();
  public Article findByTitle(final String title) {
    // 做参数校验
    return this.repository.findByTitle(title);
  }
}

在真实的项目中,由于资源所限,我们一般不会在应用中任意打开数据库连接,而是会选择共享数据库连接。所以,DBArticleRepository 需要一个数据库连接(Connection)的参数。

class ArticlService {
  private ArticleRepository repository;
  public ArticlService(final Connection connection) {
    this.repository = new DBArticleRepository(connection);
  }
  public Article findByTitle(final String title) {
    // 做参数校验
    return this.repository.findByTitle(title);
  }
}

一旦开始准备测试,你就会发现,要让 ArticleService 跑起来,那就得让 ArticleRepository 也跑起来;要让 ArticleRepository 跑起来,那就得准备数据库连接。是不是觉得太麻烦?然后,真正开始写测试时,你才发现,要测试,你还要在数据库里准备各种数据。

问题出在哪儿呢?其实就在你创建对象的那一刻,问题就出现了。当我们创建一个对象时,就必须要有一个具体的实现类,对应到我们这里,就是那个 DBArticleRepository。虽然我们的 ArticleService 写得很干净,其他部分根本不依赖于 DBArticleRepository,只在构造函数里依赖了,但依赖就是依赖。与此同时,由于要构造 DBArticleRepository 的缘故,我们这里还引入了 Connection 这个类,这个类只与 DBArticleRepository 的构造有关系,与我们这个 ArticleService 的业务逻辑一点关系都没有。你看到了,只是因为引入了一个具体的实现,我们就需要把它周边配套的东西全部引入进来,而这一切与这个类本身的业务逻辑没有任何关系。这还只是最简单的场景,在真实的项目中,构建一个对象可能还会牵扯到更多的内容:

  1. 根据不同的参数,创建不同的实现类对象,你可能需要用到工厂模式。
  2. 为了了解方法的执行时间,需要给被依赖的对象加上监控。
  3. 依赖的对象来自于某个框架,你自己都不知道具体的实现类是什么。

既然直接构造存在这么多的问题,那么最简单的办法就是把创建的过程拿出去,只留下与字段关联的过程:

class ArticleService {
    private ArticleRepository repository;
    public ArticleService(final ArticleRepository repository) {
        this.repository = repository;
    }
    public Article findByTitle(final String title) {
        // 做参数校验
        return this.repository.findByTitle(title);
    }
}

现在,对象的创建已经分离了出去,但还是要要有一个地方完成这个工作,最简单的解决方案自然是,把所有的对象创建和组装在一个地方完成:

...
ArticleRepository repository = new DBArticleRepository(connection);
AriticleService service = new ArticleService(repository);
...

相比于业务逻辑,组装过程并没有什么复杂的部分。一般而言,纯粹是一个又一个对象的创建以及传参的过程,这部分的代码看上去会非常的无聊。虽然很无聊,但这一部分代码很重要,最好的解决方案就是有一个框架把它解决掉。在 Java 世界里,这种组装一堆对象的东西一般被称为“容器”,我们也用这个名字。

Container container = new Container();
container.bind(Connection.class).to(connection);
container.bind(ArticleReposistory.class).to(DBArticleRepository.class);
container.bind(ArticleService.class).to(ArticleService.class)
ArticleService service = container.getInstance(ArticleService.class);

一个容器就此诞生。因为它解决的是依赖的问题,把被依赖的对象像药水一样,注入到了目标对象中,所以,它得名“依赖注入”(Dependency Injection,简称 DI)。这个容器也就被称为 DI 容器了。在没有 DI 容器之前,那是怎样的一个蛮荒时代啊!有了 DI 容器之后呢?你的代码就只剩下关联的代码,对象的创建和组装都由 DI 容器完成了。甚至在不经意间,你有了一个还算不错的设计:至少你做到了面向接口编程,它的实现是可以替换的,它还是可测试的。

Inversion of control In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code. traditional control flow 是从开始到结束都是自己“写代码”,IoC 中control flow的发起是由一个framework 触发的。类只是干自己的活儿——“填代码”,然后ioc在需要的时候调用。

IOC设计模式的两个重要支持:

  1. 对象间依赖关系的建立和应用系统的运行状态没有很强的关联性,因此对象的依赖关系可以在启动时建立好,ioc容器(负责建立对象的依赖关系)不会对应用系统有很强的侵入性。
  2. 面向对象系统中,除了一部分对象是数据对象外,其他很大一部分是用来处理数据的,这些对象并不经常发生变化,在系统中以单件的形式起作用就可以满足应用的需求。

陈皓:控制反转(Inversion of Control,loC )是一种软件设计的方法,它的主要思想是把控制逻辑与业务逻辑分开(程序=控制逻辑+业务逻辑),不要在业务逻辑里写控制逻辑,因为这样会让控制逻辑依赖于业务逻辑,而是反过来,让业务逻辑依赖控制逻辑。

ioc 的接口定义

什么是ioc容器?BeanFactory是最简单的ioc容器,看了BeanFactory接口方法,也许会更直观(主要包括获取bean、查询bean的特性等方法)。

interface BeanFactory{
    FACTORY_BEAN_PREFIX
    object getBean(String)
    T getBean(String,Class<T>)
    T getBean(Class<T>)
    boolean containsBean(String)
    boolean isSingleton(String)
    boolean isPrototype(String)  
    boolean isTypeMatch(String,Class<T>)   
    class<?> getType(String)
    String[] getAliases    
}

BeanFactory

属于spring-beans包

The root interface for accessing a Spring bean container.This is the basic client view of a bean container;The point of this approach is that the BeanFactory is a central registry of application components, and centralizes configuration of application components (no more do individual objects need to read properties files,for example).

Note that it is generally better to rely on Dependency Injection(“push” configuration) to configure application objects through setters or constructors, rather than use any form of “pull” configuration like a BeanFactory lookup. Spring’s Dependency Injection functionality is implemented using this BeanFactory interface and its subinterfaces.

  1. 作为一个BeanFactory,要能够getBean、createBean、autowireBean、根据各种Bean的信息检索list bean、支持父子关系,这些能力被分散在各个接口中
  2. AbstractBeanFactory 负责实现BeanFactory,同时留了一些抽象方法交给子类实现
  3. 如何对 BeanFactory 施加影响?BeanPostProcessor,Factory hook that allows for custom modification of new bean instances,e.g. checking for marker interfaces or wrapping them with proxies.

实际使用

AutowireCapableBeanFactory factory = new ...
xxBean xx = new xxBean();
factory.autowireBean(xxBean);
factory.initializeBean(xxBean, ...);

ApplicationContext

属于spring-context包

AbstractRefreshableApplicationContext 虽然实现了BeanFactory接口,但其实是组合了一个beanFactory,这是讨论BeanFactory 和 ApplicationContext 差异时最核心的一点。 也正因此,ApplicationContext is a complete superset of the BeanFactory.

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
    private DefaultListableBeanFactory beanFactory;
    public final ConfigurableListableBeanFactory getBeanFactory() {
        synchronized (this.beanFactoryMonitor) {
            if (this.beanFactory == null) {
                throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                        "call 'refresh' before accessing beans via the ApplicationContext");
            }
            return this.beanFactory;
        }
    }
}

In contrast to a plain BeanFactory, an ApplicationContext is supposed to detect special beans(BeanFactoryPostProcessor/BeanPostProcessor/ApplicationListener)defined in its internal bean factory

public abstract class AbstractApplicationContext{
    public void refresh(){
        ...
        prepareBeanFactory(beanFactory);
        ...
        registerBeanPostProcessors(beanFactory);
        ...
    }
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        ...
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        ...
    }
}

beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));ApplicationContextAwareProcessor是一个BeanPostProcessor,其作用就是当发现 一个类实现了ApplicationContextAware等接口时,为该类注入ApplicationContext 成员。

public static void registerBeanPostProcessors(...){
    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    ...
    for (String ppName : postProcessorNames) {
        BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
        priorityOrderedPostProcessors.add(pp);
    }
    ...
    registerBeanPostProcessors(beanFactory,priorityOrderedPostProcessors);
}

BeanFactory VS ApplicationContext

ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created.Plain bean factories allow for programmatic registration of post-processors,applying to all beans created through this factory.

可以说,BeanFactory 基本实现了一个bean container 需要的所有功能,但其一些特性要通过programmatic来支持,ApplicationContext在简单容器BeanFactory的基础上,增加了许多面向框架的特性。《Spring技术内幕》中参照XmlBeanFactory的实现,以编程方式使用DefaultListableBeanFactory,从中我们可以看到Ioc容器使用的一些基本过程。

ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

简单来说,reader.loadBeanDefinitions(res);将类信息从beans.xml 读取到DefaultListableBeanFactory及其父类的各种map中。然后factory.getBean时即可做出对应的反应。

ioc启动与停止

ioc/容器上下文生命周期(从这个视角也可以看下 ApplicationContext 与BeanFactory 的不同)

  1. 上下文启动准备阶段 prepareRefresh
  2. BeanFactory 创建阶段 obtainFreshBeanFactory
  3. BeanFactory 准备阶段 prepareBeanFactory
  4. BeanFactory 后置处理阶段 postProcessBeanFactory
  5. BeanFactory 注册BeanPostProcessor 阶段
  6. 初始化内建Bean: MessageSource
  7. 初始化内建Bean:Spring 事件广播器
  8. Spring 应用上下文刷新阶段
  9. Spring 事件上下文注册阶段
  10. BeanFactory 初始化完成阶段
  11. Spring 应用上下文刷新完成阶段
  12. Spring 应用上下文启动阶段
  13. Spring 应用上下文停止阶段
  14. Spring 应用上下文关闭阶段

一个容器的功能:构建和管理Bean, 分割在启动和getBean 两个部分,启动时候没有创建Bean 对象。所以BeanFactoryPostProcessor 工作在ioc 启动阶段,BeanPostProcessor 工作在getBean 阶段。

容器启动

AbstractApplicationContext.refresh(): Load or refresh the persistent representation of the configuration such as xml. ApplicationContext 支持的很多特性都可以在个启动方法里找到迹象。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();
        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // Prepare the bean factory for use in this context. 加入了Bean依赖以及非Bean依赖(比如Environment)
        prepareBeanFactory(beanFactory);
        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);
            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            // Initialize message source for this context.
            initMessageSource();
            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();
            // Initialize other special beans in specific context subclasses.
            onRefresh();
            // Check for listener beans and register them.
            registerListeners();
            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);
            // Last step: publish corresponding event.
            finishRefresh();
        }catch (BeansException ex) {
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();
            // Reset 'active' flag.
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

其中obtainFreshBeanFactory ==> refreshBeanFactory

// AbstractRefreshableApplicationContext.java
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

refreshBeanFactory 中beanFactory 的refresh 更纯粹一点:从xml、注解 等处构造BeanDefinitions 信息。ApplicationContext refresh 杂活比较多,还要管 Listeners、MessageSource、Event ,给beanFactory 塞一些特殊的类实例或逻辑。

停止逻辑

public void close() {
    synchronized (this.startupShutdownMonitor) {
        doClose();
        // If we registered a JVM shutdown hook, we don't need it anymore now:We've already explicitly closed the context.
        if (this.shutdownHook != null) {
            try {
                Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
            }catch (IllegalStateException ex) {
                // ignore - VM is already shutting down
            }
        }
    }
}

ioc 中的对象

来源 Spring Bean对象 生命周期管理 配置元信息
是否lazyload/autowiring等
使用场景
Spring BeanDefinition 依赖查找、依赖注入
单体对象 依赖查找、依赖注入
Resolvable Dependency 依赖注入

bean在不同阶段的表现形式

  表现形式 与jvm类比
配置文件
@Configuration注释的class等
<bean class=""></bean> java代码
ioc初始化 BeanDefinition class二进制 ==> 内存中的Class对象
getBean Object 堆中的对象实例

@Configuration 含义 indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime.

Bean工厂的养料——BeanDefinition

在BeanFactory 可以getBean之前,必须要先初始化,从各种源(比如xml配置文件、@Bean)中加载bean信息

jvm 基于Class 对象将对象实例化,想new 一个对象,得先有Class 对象,或者使用classLoader 加载,或者动态生成。spring 容器类似,想getBean 对象bean 实例, 就是先register 对应的BeanDefinition,任何来源的bean 通过BeanDefinitionRegistry.registerBeanDefinition 都可以纳入到IOC 的管理。

SpringBean 生命周期

  1. Spring Bean 元信息配置阶段
  2. Spring Bean 元信息解析阶段。面向资源的BeanDefinition 解析(BeanDefinitionReader和xml 解析器BeanDefinitionParser)和面向注解的BeanDefinition 解析(AnnotatedBeanDefinitionReader,比如@ComponentScan)
  3. Spring Bean 注册阶段。BeanDefinitionRegistry
  4. Spring BeanDefinition 合并阶段
  5. Spring Bean Class 加载阶段。每个BeanDefinition 对应一个Bean的class,必然会经过ClassLoader 的加载
  6. Spring Bean 实例化前阶段。InstantiationAwareBeanPostProcesssor.postProcessBeforeInstantiation,比如通过创建一个代理类的方式来创建一个实例来替换传统的实例方法。
  7. Spring Bean 实例化阶段
  8. Spring Bean 实例化后阶段。InstantiationAwareBeanPostProcesssor.postProcessAfterInstantiation,决定bean的属性值是否需要被设置
  9. Spring Bean 属性赋值前阶段。 InstantiationAwareBeanPostProcesssor.postProcessPropertyValues
  10. Spring Bean Aware 接口回调阶段
  11. Spring Bean 初始化前阶段。BeanPostProcessor.postProcessBeforeInitialization
  12. Spring Bean 初始化阶段。@PostConstruction;InitializingBean.afterProperties;自定义初始化方法
  13. Spring Bean 初始化后阶段。BeanPostProcessor.postProcessAfterInitialization
  14. Spring Bean 初始化完成阶段
  15. Spring Bean 销毁前阶段
  16. Spring Bean 销毁阶段
  17. Spring Bean 垃圾收集

getBean

Spring的Bean生命周期,11 张高清流程图及代码,深度解析BeanFactory.getBean("beanid")得到bean的实例

ioc在实例化bean的过程中,还夹了不少“私货”/“钩子”:

内建对象Environment

Interface representing the environment in which the current application is running.Models two key aspects of the application environment: profiles and properties. Environment代表着程序的运行环境,主要包含了两种信息,一种是profiles,用来描述哪些bean definitions 是可用的;一种是properties,用来描述系统的配置,其来源可能是配置文件、jvm属性文件、操作系统环境变量等。

并不是所有的Bean 都会被纳入到ioc管理,A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the @Profile annotation for syntax details.

Properties play an important role in almost all applications,and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects,Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

其它

Spring 元信息

  1. Spring Bean配置元信息 BeanDefinition
  2. Spring Bean属性元信息 PropertyValues
  3. Spring 容器配置元信息
  4. Spring 外部化配置元信息 PropertySource
  5. Spring Profile 元信息 @Profile

当大家第一次去看Spring Bean扫描的逻辑时,它的逻辑是很复杂的,如果让我们自己去实现一个,你可能会很简单的设计出来,根据指定的路径扫描所有的类,如果有@Component的注解时就存放到BeanDefinnitionMap中,那为什么Spring要设计得这么复杂呢,原因是现实场景中Bean定义有多种方法,比如嵌套定义Bean,再比如先扫描出一部分Bean,此时这些Bean中有定义@CompentScan,又可以加载其它的Bean,所以你看这么多你不曾考虑的场景叠加在一起,实现起来的复杂度自然就高了。