依赖注入
前言
本文为依赖注入的学习笔记,参考了Go语言:一文看懂什么是DI依赖注入(dependency injection)设计模式
DI依赖注入(dependency injection)设计模式
什么是DI
在理解它在编程中的含义之前,首先让我们了解一下它的总体含义,这可以帮助我们更好地理解这个概念。
依赖是指依靠某种东西来获得支持。比如我会说我们对手机的依赖程度过高。
在讨论依赖注入之前,我们先理解编程中的依赖是什么意思。
当 class A 使用 class B 的某些功能时,则表示 class A 具有 class B 依赖。
在 Java 中,在使用其他 class 的方法之前,我们首先需要创建那个 class 的对象(即 class A 需要创建一个 class B 实例)。
因此,将创建对象的任务转移给其他 class,并直接使用依赖项的过程,被称为“依赖项注入”。
依赖注入(Dependency Injection, DI)是一种设计模式,也是Spring框架的核心概念之一。其作用是去除Java类之间的依赖关系,实现松耦合,以便于开发测试。为了更好地理解DI,先了解DI要解决的问题。
我们先用Java代码理解一下普遍的情况:
耦合太紧的问题
如果使用一个类,自然的做法是创建一个类的实例:
class Player{
Weapon weapon;
Player(){ // 与 Sword类紧密耦合
this.weapon = new Sword();
} public void attack() {
weapon.attack();
}
}
这个方法存在耦合太紧的问题,例如,玩家的武器只能是剑Sword,而不能把Sword替换成枪Gun。要把Sword改为Gun,所有涉及到的代码都要修改,当然在代码规模小的时候这根本就不是什么问题,但代码规模很大时,就会费时费力了。
依赖注入(DI)过程
依赖注入是一种消除类之间依赖关系的设计模式。例如,A类要依赖B类,A类不再直接创建B类,而是把这种依赖关系配置在外部xml文件(或java config文件)中,然后由Spring容器根据配置信息创建、管理bean类。
示例:
class Player{
Weapon weapon; // weapon 被注入进来
Player(Weapon weapon){ this.weapon = weapon;
} public void attack() {
weapon.attack();
} public void setWeapon(Weapon weapon){ this.weapon = weapon;
}
}
如上所示,Weapon类的实例并不在代码中创建,而是外部通过构造函数传入,传入类型是父类Weapon,所以传入的对象类型可以是任何Weapon子类。
传入哪个子类,可以在外部xml文件(或者java config文件)中配置,Spring容器根据配置信息创建所需子类实例,并注入Player类中,如下所示:
<bean id="player" class="com.qikegu.demo.Player">
<construct-arg ref="weapon"/>
</bean>
<bean id="weapon" class="com.qikegu.demo.Gun">
</bean>
上面代码中<construct-arg ref="weapon"/> ref指向id="weapon"的bean,传入的武器类型是Gun,如果想改为Sword,可以作如下修改:
<bean id="weapon" class="com.qikegu.demo.Sword">
</bean>
注意:松耦合,并不是不要耦合。A类依赖B类,A类和B类之间存在紧密耦合,如果把依赖关系变为A类依赖B的父类B0类,在A类与B0类的依赖关系下,A类可使用B0类的任意子类,A类与B0类的子类之间的依赖关系是松耦合的。
python 依赖注入
在 Python 中,依赖注入(Dependency Injection, DI) 的核心思想与 Java 或其他语言类似,即通过外部将依赖项传递给需要它们的对象,而不是由对象自己创建依赖。Python 的灵活性(动态类型、无强制接口等)使得实现依赖注入更加灵活,但核心目标仍然是解耦组件、提高可测试性、增强代码可维护性。
Python 中依赖注入的核心概念
依赖(Dependency)
当一个类(或函数)需要另一个类(或对象)的功能时,后者就是前者的依赖。例如:
class Database:
def query(self):
return "Data from DB"
class Service:
def __init__(self):
self.db = Database() # Service 依赖 Database
def get_data(self):
return self.db.query()
这里,Service 类直接创建了 Database 对象,导致两者高度耦合。
依赖注入(DI)
将依赖项的创建和管理交给外部,通过参数、属性或方法传递给需要它们的对象。例如:
class Service:
def __init__(self, db): # 通过构造函数注入依赖
self.db = db
def get_data(self):
return self.db.query()
现在,Service 不再自己创建 Database,而是由外部传入。
Python 中实现依赖注入的常见方式
以下是 Python 中实现依赖注入的典型方法:
- 构造函数注入(Constructor Injection)
通过类的构造函数传递依赖项,是最常见的方式:
# 依赖项
class Database:
def query(self):
return "Real data"
# 使用依赖项的类
class Service:
def __init__(self, db): # 通过构造函数注入
self.db = db
def get_data(self):
return self.db.query()
# 使用时由外部传入依赖
db = Database()
service = Service(db) # 依赖注入完成
print(service.get_data()) # 输出:Real data
- 属性注入(Setter Injection)
通过类的属性或方法动态设置依赖项:
class Service:
def __init__(self):
self.db = None # 初始无依赖
def set_db(self, db): # 通过 setter 方法注入
self.db = db
def get_data(self):
if self.db is None:
raise ValueError("Database dependency not set")
return self.db.query()
# 使用时动态注入依赖
service = Service()
service.set_db(Database()) # 依赖注入完成
print(service.get_data()) # 输出:Real data
- 方法注入(Method Injection)
在调用方法时直接传递依赖项:
class Service:
def get_data(self, db): # 依赖直接作为参数传入
return db.query()
db = Database()
service = Service()
print(service.get_data(db)) # 输出:Real data
- 装饰器注入(Decorator Injection)
利用 Python 的装饰器特性,在函数或方法调用时动态注入依赖:
def inject_db(func):
def wrapper(*args, **kwargs):
db = Database() # 或从容器获取依赖
return func(db, *args, **kwargs) # 将依赖作为参数传入
return wrapper
class Service:
@inject_db
def get_data(self, db): # 依赖由装饰器注入
return db.query()
service = Service()
print(service.get_data()) # 输出:Real data
Python 中依赖注入的优势
-
解耦组件
类不再直接依赖具体实现,而是依赖接口或抽象类型。例如:class DatabaseInterface: def query(self): pass class RealDatabase(DatabaseInterface): def query(self): return "Real data" class MockDatabase(DatabaseInterface): def query(self): return "Mock data" class Service: def __init__(self, db: DatabaseInterface): # 依赖接口而非具体类 self.db = db这样,Service 可以接受任何实现 DatabaseInterface 的类(如 RealDatabase 或 MockDatabase)。
-
提高可测试性
可以轻松替换依赖项为模拟对象(Mock):# 测试时注入 Mock 依赖 mock_db = MockDatabase() service = Service(mock_db) assert service.get_data() == "Mock data" -
灵活性
Python 的动态特性允许在运行时动态修改依赖,例如:service = Service(RealDatabase()) service.set_db(MockDatabase()) # 运行时切换依赖
Python 中的依赖注入框架
虽然 Python 可以通过纯代码手动实现依赖注入,但框架可以简化复杂系统的管理。常用框架包括:
-
FastAPI 的依赖注入
FastAPI 内置了依赖注入机制,常用于 Web 开发:
from fastapi import Depends def get_db(): return Database() class Service: def __init__(self, db): self.db = db @app.get("/data") def read_data(service: Service = Depends(lambda: Service(get_db()))): return service.get_data() -
其他框架
- injector:基于接口的依赖注入框架。
- Pydantic:结合类型注解实现依赖注入(如通过 Field 和工厂模式)。
FASTAPI中的依赖注入
在 FastAPI 中,无需通过复杂的类或额外的注册步骤来声明依赖项,只需要将依赖项函数直接传递给 Depends,FastAPI 就能自动识别并处理它。具体来说:
-
无需创建专门的类:
在其他框架(如某些 Java/Spring 框架)中,依赖项可能需要定义为类,并通过 @Component 或其他注解“注册”到框架中,才能被注入。
但在 FastAPI 中,依赖项只是一个普通的函数或异步函数,不需要额外的类或装饰器。你只需要定义一个函数,然后通过 Depends 直接引用它即可。 -
无需显式注册到 FastAPI:
其他框架可能需要将依赖项“注册”到应用实例中(例如通过 app.add_dependency() 或类似方法)。
但在 FastAPI 中,只要你在路径操作函数的参数中使用 Depends(依赖项函数),FastAPI 会自动识别并执行该依赖项,无需任何额外配置。
示例对比
FastAPI 的简单方式
from fastapi import FastAPI, Depends
app = FastAPI()
# 定义依赖项:只需要一个普通函数
def get_db():
return "Database Connection"
# 直接在路径操作函数中通过 Depends 引用依赖项
@app.get("/items/")
def read_items(db=Depends(get_db)):
return {"db": db}
其他框架的典型方式(对比)
# 假设是某个需要注册的框架
from my_framework import App, Component
# 需要定义一个类作为依赖项
class DatabaseComponent:
def get_db(self):
return "Database Connection"
# 需要显式注册到框架中
app = App()
app.register_component(DatabaseComponent())
# 在路径操作中注入
@app.get("/items/")
def read_items(db=Inject(DatabaseComponent).get_db()):
return {"db": db}
为什么 FastAPI 这样设计?
-
简洁性:
FastAPI 的设计理念是“代码第一,配置最少”。通过直接传递函数给 Depends,开发者无需关心底层的依赖管理,只需关注业务逻辑。 -
灵活性:
依赖项可以是同步函数、异步函数,甚至其他依赖项的组合,而无需额外的类约束。 -
自动解析参数:
FastAPI 会自动解析依赖项的参数,例如从请求查询参数、路径参数、Header 等中获取值,就像处理路径操作函数一样。

浙公网安备 33010602011771号