DI依赖注入容器零基础完整讲解
目录导航
一、DI / IoC / 容器 基础概念
零基础通俗解释
依赖 Dependency:A类运行需要B类,B就是A的依赖。例如订单服务需要数据库操作类。
DI 依赖注入:不在类内部自己new依赖对象,由外部把依赖传入类中使用。
IoC 控制反转:传统代码自己创建对象;IoC把对象创建、管理的控制权交给容器。
DI容器:统一存放、管理项目所有服务的工具,自动组装依赖,开发者无需手动实例化各类工具。
二、不使用DI容器的代码痛点
传统硬编码 new 写法问题
- 多层依赖嵌套,大量重复new代码
- 依赖写死,切换数据库/缓存需要大面积改代码
- 单元测试无法模拟依赖,必须连接真实中间件
- 无法统一管控单例,重复创建对象浪费内存
- 底层工具修改后,上层所有业务代码连锁报错
DI容器标准化解决方案
- 所有服务统一一处注册,依赖集中管理
- 切换底层实现仅修改注册代码,业务逻辑零改动
- 测试时替换Mock模拟服务,完全隔离真实环境
- 内置单例/瞬时生命周期,自动复用对象节省资源
- 底层工具迭代升级,上层业务代码无感知
# 反面示例:强耦合硬编码
class OrderService:
def __init__(self):
# 内部直接实例化,无法替换数据库实现
self.db = MysqlDB()
self.cache = RedisCache()
self.notify = EmailNotify()
三、DI容器三大核心优势
1. 代码解耦
2. 单元测试极简
3. 易扩展维护
四、注册、解析、生命周期核心术语
1. 注册 Register
告知容器:抽象接口对应哪个实现、对象是单例还是每次新建。相当于给仓库登记商品。
# 伪代码注册示例 container.register(DBInterface, MysqlDB, singleton=True) container.register(CacheInterface, RedisCache)
2. 解析 Resolve
业务需要服务时向容器索取实例;容器自动递归创建所有依赖、完成注入,直接返回完整可用对象。
# 解析订单服务,自动注入DB、Cache依赖 order_service = container.resolve(OrderService) order_service.create_order()
3. 生命周期 Lifetime
控制容器创建对象的复用规则,分为单例、瞬时、作用域三类。
五、手写迷你DI容器,看懂底层原理
# Python极简DI容器实现
class MiniDIContainer:
def __init__(self):
self.registry = {} # 存储抽象与实现映射
self.singleton_cache = {} # 缓存全局单例
# 绑定抽象与实现,设置是否单例
def register(self, abstract_cls, impl_cls, singleton=False):
self.registry[abstract_cls] = (impl_cls, singleton)
# 递归解析所有依赖,自动注入
def resolve(self, target_cls):
# 命中单例缓存直接返回
if target_cls in self.singleton_cache:
return self.singleton_cache[target_cls]
impl_cls, is_singleton = self.registry.get(target_cls, (target_cls, False))
# 读取构造函数依赖注解
import inspect
sig = inspect.signature(impl_cls.__init__)
deps = []
for param in sig.parameters.values():
if param.annotation != param.empty:
deps.append(self.resolve(param.annotation))
instance = impl_cls(*deps)
# 单例存入缓存复用
if is_singleton:
self.singleton_cache[target_cls] = instance
return instance
# 使用演示
container = MiniDIContainer()
class DB: pass
class MysqlDB(DB): pass
container.register(DB, MysqlDB, singleton=True)
class OrderService:
def __init__(self, db: DB):
self.db = db
def create(self):
print("创建订单,使用数据库", self.db)
svc = container.resolve(OrderService)
svc.create()
六、三种依赖注入方式对比
构造注入(推荐首选)
依赖全部写在构造函数入参,实例化强制传入,依赖清晰、不会缺失,单元测试最友好。
class OrderService:
def __init__(self, db: DB, cache: Cache):
self.db = db
self.cache = cache
属性注入(仅可选依赖)
对象创建完成后容器赋值,适合非必需依赖,容易出现依赖未注入空值报错。
class OrderService:
db: DB = None
cache: Cache = None
方法注入(业务极少使用)
通过专用方法传入临时依赖,多用于框架钩子、回调场景,常规业务代码不推荐。
七、服务生命周期:单例/瞬时/作用域
| 生命周期类型 | 对象创建规则 | 适用业务场景 |
|---|---|---|
| 单例 Singleton | 程序全局仅1个实例,全程复用 | 数据库连接池、Redis客户端、全局配置 |
| 瞬时 Transient | 每次Resolve都生成全新对象 | 轻量无状态工具、临时计算类 |
| 作用域 Scoped | 单次请求/会话共用1个实例,跨请求隔离 | Web接口请求上下文、事务会话 |
八、新项目标准完整使用流程
- 定义数据库、缓存、消息通知等底层抽象接口,隔离具体实现;
- 程序启动时初始化全局唯一DI容器;
- 批量注册所有底层工具,区分单例/瞬时生命周期;
- 注册订单、用户、支付等全部业务服务;
- 项目入口文件从容器Resolve顶层业务服务;
- 全程禁止手动new,业务代码仅依赖抽象接口;
- 单元测试时重新注册Mock实现,无需修改业务代码;
九、各语言成熟DI工具推荐
Python
Java
TypeScript/NodeJS
.NET C#
十、新手高频踩坑与解决方案
容器解析时无限递归直接报错;解决:拆分业务模块,抽取公共中间服务消除双向依赖。
单例仅初始化一次,内部瞬时依赖永久固定无法刷新;解决:单例内通过容器工厂动态获取实例。
绕开容器管理,彻底失去解耦、单元测试全部优势;规范:所有实例统一交给容器注册解析。
DI Container Full Beginner Tutorial
Table of Contents
- 1 Core Concept: DI / IoC / Container
- 2 Pain Points Without DI Container
- 3 Three Core Advantages
- 4 Core Terms: Register / Resolve / Lifetime
- 5 Minimal Handwritten DI Container Demo
- 6 Three Injection Types Comparison
- 7 Service Lifetime Rules
- 8 Standard New Project Workflow
- 9 Popular DI Libraries For All Languages
- 10 Common Mistakes & Fixes
1 Core Concept Explanation
Plain Language Definition
Dependency: Class A requires Class B to execute logic.
DI (Dependency Injection): Pass dependencies externally instead of instantiating inside class.
IoC (Inversion of Control): Move object creation control from business code to container.
DI Container: Central service warehouse to register, instantiate and auto inject all dependencies.
2 Pain Points Of Hardcoded New()
Traditional Hardcode Drawbacks
- Tight coupling between service and database implementation
- Hard to write isolated unit tests with real middleware
- Duplicate object instances waste memory
- Modifying base service breaks all upper-level business code
DI Container Solutions
- Loose coupling via abstract interface
- Mock service registration for fast unit test
- Built-in singleton / transient lifetime control
- Centralized service registration, easy to maintain & extend
3 Three Core Advantages
1 Loose Coupling
2 Test Friendly
3 Easy Extend
4 Core Terminology
Register
Bind abstract type to concrete class, define service lifetime rule.
Resolve
Request instance from container, auto resolve all nested dependencies recursively.
Lifetime
Rules to control object reuse: Singleton / Transient / Scoped.
5 Minimal DI Container Demo Code
class MiniDIContainer:
def __init__(self):
self.registry = {}
self.singleton_cache = {}
def register(self, abstract, impl, singleton=False):
self.registry[abstract] = (impl, singleton)
def resolve(self, target):
if target in self.singleton_cache:
return self.singleton_cache[target]
impl, is_singleton = self.registry.get(target, (target, False))
import inspect
sig = inspect.signature(impl.__init__)
deps = []
for param in sig.parameters.values():
if param.annotation != param.empty:
deps.append(self.resolve(param.annotation))
instance = impl(*deps)
if is_singleton:
self.singleton_cache[target] = instance
return instance
6 Three Injection Modes Comparison
Constructor Injection (Recommended)
Dependencies passed via constructor, mandatory & explicit, best for unit test.
Property Injection
Inject after instance created, only for optional dependencies.
Method Injection (Rarely Used)
Inject temporary dependencies via dedicated function, only for framework callbacks.
7 Service Lifetime Table
| Lifetime | Rule | Applicable Scene |
|---|---|---|
| Singleton | One global instance for whole app | DB pool, Redis client, global config |
| Transient | New instance every resolve call | Stateless lightweight utility |
| Scoped | One instance per single request/session | Web request context, transaction session |
8 Standard Production Workflow
- Define abstract interfaces for external database/cache services
- Initialize global DI container when app starts
- Register all base services with lifetime configuration
- Register all domain business services
- Resolve top-level service at app entry file
- Never manually new() objects in business logic
- Register mock implementation during unit testing
9 Popular DI Libraries By Language
Python
Java
TS/JS
.NET C#
10 Common Pitfalls & Solutions
Class A depends B, B depends A → split modules to eliminate bidirectional dependency.
Transient object frozen inside singleton, use factory pattern to fetch instance dynamically.
Lose all decoupling and unit testing advantages, manage all services via container only.
