🧠 什么是依赖注入(DI)?FastAPI 为何离不开它?
“你声明需要什么,框架自动准备好。”
在 FastAPI 中,依赖(Dependency) 是一段可复用的逻辑单元,例如:
- 用户身份校验
- 数据库会话获取
- 请求限流/日志记录
- 配置读取
而 依赖注入(Dependency Injection, DI) 就是 FastAPI 自动执行这些逻辑,并将结果(或副作用)传递给你的接口函数的过程。
✅ 优势:
- 遵循 DRY:避免每个接口重复写
if not token: raise ... - 解耦业务与横切关注点(cross-cutting concerns)
- 支持多级作用域(单接口 → 路由组 → 全局)
- 类型安全 + 自动文档生成(Swagger 自动带上依赖参数)
🎯 一句话总结:
Depends()是 FastAPI 的“自动工具管理员”——你要锤子?它递给你;你要扳手?它也备好了。
🛠️ 实战准备:项目结构 & 环境搭建
mkdir fastapi-di-demo && cd fastapi-di-demo
python -m venv .venv
source .venv/bin/activate # Linux/macOS;Windows 用 `.venv\Scripts\activate`
pip install 'fastapi[all]'
目录结构:
fastapi-di-demo/
├── .venv/
├── main.py # 主应用入口(全局依赖)
├── deps.py # 所有依赖定义(函数/类)
├── users.py # 用户路由(演示路径/路由级依赖)
└── requirements.txt
🧩 1. 函数依赖:最常用、最直观
场景:校验用户凭据(用户名+密码)
✅ 步骤 1:定义依赖函数(deps.py)
# deps.py
from fastapi import HTTPException
# 模拟用户数据库(生产环境请用 DB)
FAKE_USERS = {
"alice": "pass123",
"bob": "secret456",
}
def verify_user(name: str, password: str) -> dict:
"""✅ 函数依赖:校验用户,返回用户信息"""
if name in FAKE_USERS and FAKE_USERS[name] == password:
return {
"username": name, "is_valid": True}
raise HTTPException(status_code=401, detail="Unauthorized")
✅ 步骤 2:注入到接口(users.py)
# users.py
from fastapi import APIRouter, Depends
from deps import verify_user
router = APIRouter(prefix="/users", tags=["Users"])
@router.get("/profile")
def get_profile(current_user: dict = Depends(verify_user)):
# current_user ← verify_user() 的返回值!
return {
"message": f"Hello, {current_user['username']}!",
"data": {
"theme": "dark", "lang": "zh-CN"}
}
✅ 测试(main.py 注册路由)
# main.py
from fastapi import FastAPI
from users import router as user_router
app = FastAPI()
app.include_router(user_router)
# 启动:uvicorn main:app --reload
访问:GET /users/profile?name=alice&password=pass123
→ 200 OK + 用户数据GET /users/profile?name=alice&password=wrong
→ 401 Unauthorized
💡 关键点:依赖函数的参数(
name,password)会自动从请求中解析(query/form/header/path 均支持)。
🏗️ 2. 类依赖:面向对象 + 状态管理
当依赖需要配置参数或维护状态时,类更合适。
场景:带自定义消息的认证器
# deps.py(追加)
from fastapi import HTTPException
class AuthGuard:
def __init__(self, realm: str = "api"):
self.realm = realm # 可配置项!例如区分 admin/web/api
def __call__(self, name: str, password: str) -> str:
"""__call__ 使实例可调用 → FastAPI 视为依赖函数"""
if name in FAKE_USERS and FAKE_USERS[name] == password:
return name # 返回用户名(轻量)
raise HTTPException(
status_code=401,
detail="Invalid credentials",
headers={
"WWW-Authenticate": f'Bearer realm="{self.realm}"'}
)
注入使用:
# users.py(追加)
from deps import AuthGuard
# 固定 realm="protected"
guard = AuthGuard(realm="protected")
@router.get("/dashboard")
def dashboard(username: str = Depends(guard)):
return {
"welcome": username, "role": "user"}
✅ 优势:
- 同一依赖可创建多个实例(如
admin_guard = AuthGuard("admin"))- 支持
__init__注入配置,__call__执行逻辑,职责分离清晰
🌐 3. 多级依赖作用域(Scope)
| 作用域 | 写法 | 适用场景 |
|---|---|---|
| 接口级(带返回值) | param = Depends(dep) 参数注入 |
需要依赖结果(如 current_user) |
| 接口级(无返回值) | dependencies=[Depends(dep)] 装饰器参数 |
仅做检查(鉴权/限流/日志) |
| 路由级 | APIRouter(..., dependencies=[Depends(dep)]) |
整组接口共享逻辑(如 /admin/* 需管理员权限) |
| 应用级(全局) | FastAPI(dependencies=[Depends(dep)]) |
全站生效(如全链路日志、全局 API Key 校验) |
✅ 示例 1:装饰器依赖(无返回值)
# deps.py
def log_request(name: str, password: str):
print(f"🔍 Request from {name} at {__import__('datetime').datetime.now()}")
# users.py
@router.get("/audit", dependencies=[Depends(log_request)])
def audit():
return {
"status": "logged"} # 不接收 log_request 返回值(它没 return)
✅ 示例 2:路由级依赖(整组保护)
# users.py
from deps import AuthGuard
protected_router = APIRouter(
prefix="/admin",
tags=["Admin"],
dependencies=[Depends(AuthGuard("admin"))] # ✅ 整个 /admin/* 都要认证
)
@protected_router.get("/stats")
def stats():
return {
"users": 1000, "active": 42}
✅ 示例 3:应用级依赖(全局日志 + 认证)
# main.py
from deps import log_request, verify_user
app = FastAPI(
dependencies=[
Depends(log_request), # 所有请求记录日志
Depends(verify_user), # 甚至 /docs 也要求登录!⚠️ 实际慎用
]
)
⚠️ 注意:全局依赖会影响 Swagger UI (
/docs)!若需开放文档,建议:
- 将
/docs单独排除- 或使用中间件替代部分全局依赖
🚀 常见实战场景 & 最佳实践
| 场景 | 推荐实现方式 | 说明 |
|---|---|---|
| 数据库会话(SQLAlchemy) | @contextmanager + Depends |
确保每个请求独立 session,自动 commit/rollback |
| JWT 认证 | 类依赖 + Security() |
结合 oauth2_scheme = OAuth2PasswordBearer(...) |
| 配置管理 | @lru_cache() 依赖函数 |
缓存 .env 读取结果,避免重复 IO |
| 分页参数 | 函数依赖(skip: int = 0, limit: int = 10) |
统一接口分页风格 |
| 限流(Rate Limit) | 装饰器依赖 + Redis | 无返回值,失败直接抛 429 |
✅ 小结:何时该用依赖注入?
| 场景 | 用 DI? | 理由 |
|---|---|---|
| 每个接口都要查用户信息 | ✅ | 避免重复 get_user(token) |
| 仅一个接口需特殊校验 | ❌ | 直接写在函数里更简单 |
| 多个服务需共享 DB 连接池 | ✅ | 统一生命周期管理 |
简单字符串处理(如 .upper()) |
❌ | 过度设计 |
🎯 黄金法则:
“重复出现 ≥ 2 次的逻辑 → 提取为依赖”
“需要与请求生命周期绑定的资源 → 必须用依赖”