FastAPI 的依赖注入 Depends 是怎么工作的?
简化版
FastAPI 的依赖注入通过 Depends 声明某个接口需要的前置逻辑,比如获取数据库连接、解析当前用户、校验权限、读取配置等。FastAPI 会在调用路由前解析依赖、执行依赖、把返回值注入到参数中,并且支持依赖嵌套和 yield 依赖做资源清理。
详细版
Depends 的作用不是传统 Spring 那种容器级对象装配,而是请求级依赖解析机制。它更像一套可组合的请求处理管道。
常见用途包括:
- 获取数据库 session;
- 解析 token 得到当前用户;
- 判断用户权限;
- 统一读取分页参数;
- 复用业务前置校验;
- 用
yield管理连接、事务、文件等资源。
示例:
from fastapi import Depends, FastAPI
app = FastAPI()
def get_current_user():
return {"id": 1, "name": "Tom"}
@app.get("/me")
def read_me(user=Depends(get_current_user)):
return user
FastAPI 会先执行 get_current_user(),再把返回值传给 read_me 的 user 参数。
完整版教学
一、为什么 FastAPI 需要依赖注入
Web 接口里有大量“每个请求都要做,但又不属于核心业务”的逻辑。例如读取请求头里的 token、查询当前用户、检查权限、创建数据库会话、解析分页参数、读取租户信息。如果这些逻辑都写在每个路由函数里,代码会重复而且容易漏。
FastAPI 的 Depends 就是为了解决这类问题。它允许你把这些前置逻辑封装成函数,然后在路由参数里声明“我需要这个依赖”。FastAPI 会负责执行依赖,并把结果注入进来。
这让路由函数更像业务入口:
@app.get("/orders")
def list_orders(
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
return query_orders(db, user.id)
从函数签名就能看出:这个接口需要当前用户,也需要数据库会话。可读性比把所有逻辑塞进函数体里好很多。
二、Depends 的执行流程
当请求进入某个路由时,FastAPI 会读取路由函数的参数声明。凡是带有 Depends(...) 的参数,都会被视为依赖。
它会先执行依赖函数。如果依赖函数也声明了其他依赖,FastAPI 会继续解析,形成一个依赖树。依赖执行完成后,返回值会作为参数传给路由函数。
例如:
def get_token(authorization: str = Header()):
return authorization.removeprefix("Bearer ")
def get_current_user(token: str = Depends(get_token)):
return decode_user(token)
@app.get("/me")
def me(user: User = Depends(get_current_user)):
return user
这里的执行顺序是:先从请求头获取 token,再根据 token 解析用户,最后进入路由函数。
三、依赖注入和普通函数调用有什么区别
表面看,Depends 似乎只是把一个函数的返回值传给另一个函数。但它的价值在于:FastAPI 会统一处理参数来源、校验、缓存、依赖树和异常。
依赖函数本身也可以声明查询参数、路径参数、请求头、Cookie、请求体或其他依赖。也就是说,依赖函数可以像路由函数一样参与请求解析。
默认情况下,同一个请求里相同依赖通常会被缓存,避免重复执行。例如多个依赖都需要当前用户时,FastAPI 不会在一个请求里反复解析 token。这个行为可以通过 use_cache=False 改变。
四、yield 依赖如何管理资源
很多资源有“进入前创建,结束后清理”的生命周期,比如数据库 session。FastAPI 支持用 yield 写依赖:
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
路由函数执行前,yield 前面的代码会运行,生成资源;路由函数执行完成后,finally 中的清理逻辑会执行。
这非常适合数据库连接、事务控制、临时文件、锁等资源。如果不使用这种方式,开发者很容易忘记关闭连接,导致连接池耗尽。
五、Depends 常见用途
权限校验是最典型的场景:
def require_admin(user: User = Depends(get_current_user)):
if not user.is_admin:
raise HTTPException(status_code=403, detail="权限不足")
return user
然后在接口中直接声明:
@app.delete("/users/{user_id}")
def delete_user(user_id: int, admin: User = Depends(require_admin)):
return {"deleted": user_id}
分页参数也很适合抽成依赖:
def pagination(page: int = 1, size: int = 20):
size = min(size, 100)
return {"offset": (page - 1) * size, "limit": size}
这样多个列表接口可以复用同一套分页规则。
六、常见误区与追问
| 依赖类型 | 典型用途 | 生命周期 |
|---|---|---|
| 普通函数依赖 | 当前用户、分页参数、权限校验 | 请求内执行,结果可缓存 |
| 嵌套依赖 | token -> user -> permission | 按依赖图从叶子到路由解析 |
yield 依赖 | DB session、临时资源、锁 | yield 前创建,响应后清理 |
请求进入
-> get_token(Header)
-> get_current_user(token)
-> require_admin(user)
-> 路由函数执行业务
-> yield 依赖执行清理逻辑
易错点:
Depends是请求级依赖解析机制,不是把对象放进全局容器后永久复用。
第一个坑是把依赖函数写得太重。依赖会在请求链路中执行,如果每个接口都依赖一个会访问多个外部服务的函数,延迟会明显升高。
第二个坑是在依赖里吞掉异常。认证失败、权限不足、资源不存在应该明确抛出 HTTPException,否则路由层可能拿到错误的空对象。
第三个坑是混淆依赖注入和全局状态。依赖可以返回请求级对象,但不要把用户信息、数据库 session 放在全局变量里,否则并发请求之间可能相互污染。
- 误区:FastAPI 的依赖注入等同于 Spring 容器。
Depends主要解决请求处理链路中的参数解析、前置逻辑、资源创建和清理,不是完整的应用级对象装配容器。 - 误区:依赖函数越通用越好。 过重的通用依赖会让所有接口都承担额外查询、远程调用或权限判断,应该按接口需要拆分。
- 误区:在依赖里静默返回
None表示认证失败。 认证和授权失败应明确抛出HTTPException,否则业务层容易误用空对象造成更隐蔽的问题。 - 追问:依赖结果会不会重复执行? 同一请求内相同依赖默认会缓存结果;如果确实需要每次重新执行,可以设置
use_cache=False。 - 追问:
yield依赖的清理时机在哪里? 路由和后续响应处理结束后会执行yield后面的清理逻辑,适合关闭 session、释放连接、回滚或提交事务。 - 追问:依赖注入适合做所有权限逻辑吗? 通用身份解析和粗粒度权限很适合放依赖;复杂资源归属校验通常还要结合业务查询,避免把业务规则藏得过深。
七、加强记忆
记 FastAPI 的 Depends,要把它看成“请求级可组合前置逻辑”:路由声明需要什么,FastAPI 负责解析、执行、注入和清理。它最适合承载数据库会话、当前用户、权限校验、分页参数这类横切逻辑。