FastAPI 的依赖注入(Dependency Injection)是我最喜欢的特性之一。只需在路由参数中写 Depends(get_db),框架就会自动解析依赖链、处理缓存、管理生命周期。但当你需要自定义依赖行为时,理解它的内部原理就变得重要。

Depends 的本质

Depends() 返回的是一个 Depends 实例,这个实例不包含任何执行逻辑——它只是一个标记对象,告诉 FastAPI 的依赖解析器:"这个参数需要从某个可调用对象获取"。

from fastapi import Depends

def get_current_user(token: str = Header(...)) -> User:
    return decode_token(token)

@app.get("/me")
def me(user: User = Depends(get_current_user)):
    return user

当请求到达 /me 时,FastAPI 做了以下几件事:检查路由函数的参数签名,发现 userDepends 标记,递归解析 get_current_user 的依赖(本例中 Header),按顺序执行依赖链,将结果注入路由函数。

依赖缓存与 Scope

FastAPI 提供 use_cache 参数控制依赖的复用行为:

依赖的生命周期与请求绑定。FastAPI 通过 contextlib.AsyncExitStack 管理带清理逻辑的依赖(如 yield 的数据库 session),确保即使中途抛异常也能正确释放资源。

常见陷阱

循环依赖:A 依赖 B,B 又依赖 A,FastAPI 会抛出 RecursionError。解决方案是引入中间服务层或使用 lazy 参数延迟加载。

过度嵌套:依赖链超过 3 层时,代码可读性急剧下降。每多一层抽象,新同事理解成本翻倍。经验法则:如果依赖的依赖本身又有复杂依赖,考虑扁平化。

async vs sync:如果依赖函数是 async def,FastAPI 会用 await 调用;同步函数则在线程池中执行。混用时需要注意,一个同步依赖如果被 async 路由引用,不会自动获得异步优势。

依赖注入的本质是控制反转。用好它,你的 FastAPI 代码会像乐高积木一样可组装。

--- 约 700 字