返回博客

FastAPI 里的 deps.py,到底是不是 Spring 拦截器?

FastAPI 里的 deps.py,到底是不是 Spring 拦截器? 看见 workflows.py、ai.py、assets.py、tasks.py 都连到 deps.py,我第一反应是:这不就是 Spring 里的拦截器吗?答案是:认证效果有点像,但运行机制不是一回事。 我最近在看一个 Python 后端的知识图。 图里有个很显眼的现象: 几个 AP

本文目录
  1. 01 deps.py 没有特殊魔法
  2. 02 API 不是直接调用,而是在声明依赖
  3. 03 Token 校验为什么看起来像拦截器
  4. 04 get_db 为什么不只是“拿一个数据库对象”
  5. 05 你的理解对在哪里,还差哪一步
  6. 不是“导入了就执行”
  7. 不是“API 自己调用和关闭”
  8. 06 它与 Spring 到底怎么对应
  9. 认证提前拒绝
  10. 注入当前用户
  11. 解析方法参数
  12. 管理请求级资源
  13. 路径前后处理
  14. 07 为什么项目喜欢把这些东西集中到 deps.py
  15. 08 最后,用一句话记住
  16. 资料来源

看见 workflows.py、ai.py、assets.py、tasks.py 都连到 deps.py,我第一反应是:这不就是 Spring 里的拦截器吗?答案是:认证效果有点像,但运行机制不是一回事。

deps.py 到底是不是 Spring 拦截器?

我最近在看一个 Python 后端的知识图。

图里有个很显眼的现象:

Text
workflows.py
ai.py
assets.py
 tasks.py
      \
      deps.py

几个 API 文件都在导入同一个 deps.py

多个 API 模块共同依赖 deps.py

右侧还列出了几个函数:

Python
get_token_verifier
get_db
get_current_identity
get_current_subject
get_current_access_token

如果平时主要写 Spring,这个画面很容易触发一个熟悉的判断:

这是不是类似拦截器?请求先进来,检查 Token,再把用户信息交给 Controller?

这个类比不能说完全错。

但如果直接把 deps.py 翻译成“FastAPI 拦截器”,后面理解数据库会话、参数注入和资源清理时就会越来越别扭。

更准确的说法是:

deps.py是普通 Python 模块;它集中提供依赖函数。API 路由通过 Depends(...)声明需要什么,FastAPI 再负责调用依赖、注入结果和管理 yield依赖的退出。

这篇就从这句话展开。


#01 deps.py 没有特殊魔法

先拆掉第一个误解。

deps.py 不是 FastAPI 的保留文件名,也不是放进去就会自动生效的目录。

它和下面这些名字没有本质区别:

Text
dependencies.py
common.py
auth.py
context.py

都只是普通 Python 模块。

项目喜欢叫它 deps.py​,只是因为 deps​ 是 dependencies 的缩写。

里面通常集中放一组会被 API 重复使用的 callable:

Python
async def get_current_identity():
    ...

async def get_current_subject():
    ...

async def get_db():
    ...

async def get_current_access_token():
    ...

FastAPI 不会因为这些函数写在 deps.py 中,就自动扫描并执行它们。

真正让函数进入请求流程的是路由中的 Depends(...)


#02 API 不是直接调用,而是在声明依赖

普通 Python 调用可能是:

Python
identity = await get_current_identity()

FastAPI 路由通常这样写:

Python
from fastapi import Depends

@router.get("/tasks")
async def list_tasks(
    identity = Depends(get_current_identity),
    db = Depends(get_db),
):
    ...

这段代码不是在定义函数时立刻执行 get_current_identity​ 和 get_db

它是在告诉 FastAPI:

Text
调用 list_tasks 之前
我需要 identity
我还需要 db

FastAPI 官方对依赖注入的定义也很直接:代码声明自己工作所需要的东西,由系统负责提供这些依赖。

于是一次请求的核心过程变成:

Text
请求进入
→ FastAPI 读取 Depends 声明
→ 构建并解析依赖图
→ 调用依赖函数
→ 把结果注入路由参数
→ 依赖成功后调用路由函数

FastAPI 的依赖解析、认证失败与 yield 清理

“依赖图”三个字很重要。

它不一定是从上到下执行的一条固定流水线。

路由可以直接依赖多个函数;依赖函数也能继续声明自己的子依赖。FastAPI 会递归解析整张图,还会在一次请求中默认复用相同依赖的结果。

所以 Depends 更像一句声明:

我需要这个能力,具体怎么准备交给框架。


#03 Token 校验为什么看起来像拦截器

假设某个私有 API 需要当前身份:

Python
@router.get("/private")
async def private_api(
    identity = Depends(get_current_identity),
):
    ...

get_current_identity 读取 Bearer Token,交给 Gateway 或认证服务校验。

Token 无效时,依赖可以主动抛出:

Python
raise HTTPException(
    status_code=401,
    detail="Invalid authentication credentials",
)

FastAPI 会停止后续调用,路由处理函数不会执行。

从外部效果看,这确实很像 Spring 里的认证过滤器或拦截器:

Text
先检查身份
→ 失败直接拒绝
→ 成功才进入业务代码

但这里要注意两个细节。

第一,Depends 本身不会自动校验 Token。

校验规则和 401​ 都是依赖函数自己定义的。函数如果只是 return 401,那只是返回了一个普通整数,并不会自动终止请求。

第二,认证失败不代表其他依赖一定从未执行。

如果认证依赖本身还依赖数据库、Token 提取器或其他子依赖,那么其中一些步骤可能已经发生。401 能保证的是:路由业务函数不再调用;已经进入生命周期的资源仍要正确清理。


#04 get_db 为什么不只是“拿一个数据库对象”

get_db 常见的写法是:

Python
async def get_db():
    async with async_session() as session:
        yield session

或者显式写成:

Python
async def get_db():
    session = async_session()
    try:
        yield session
    finally:
        await session.close()

yield 前面负责准备资源:

Text
创建数据库会话

yield session 把会话交给路由函数:

Python
async def list_assets(
    db = Depends(get_db),
):
    ...

响应完成或发生异常时,FastAPI 会退出已经进入的 yield​ 依赖,继续执行 yield​ 后面的代码或 finally 块。

但“FastAPI 自动关闭数据库”这句话不够严谨。

框架负责的是:

Text
在正确的生命周期阶段恢复并退出依赖

真正的关闭、回滚或释放动作,仍然必须由 get_db 自己定义。

如果写成:

Python
async def get_db():
    return async_session()

FastAPI 不会凭参数名叫 db​,就猜到应该执行 close()

所以更准确的职责分工是:

FastAPI 负责触发退出;依赖函数负责定义如何清理。

另外,退出时机还会受依赖 scope 和响应类型影响。尤其是流式响应,不应该把它死记成“路由 return 的下一行立刻关闭”。


#05 你的理解对在哪里,还差哪一步

我们可以把前面的理解整理成一句话:

deps.py 是一个导出依赖函数的普通模块;API 层按需导入,需要认证就声明认证依赖,需要数据库就声明数据库依赖。

这句话整体正确。

只需要补上两个限定。

#不是“导入了就执行”

仅仅写:

Python
from .deps import get_db

不会触发依赖注入。

还要通过 Depends(...) 声明:

Python
db = Depends(get_db)

#不是“API 自己调用和关闭”

通常不是路由手动写:

Python
db = await get_db()
await db.close()

而是 FastAPI 解析依赖、注入 db​,并驱动 yield​ 依赖退出;具体关闭动作由依赖函数中的 finally 或上下文管理器定义。

完整版本可以写成:

deps.py是普通 Python 模块,集中定义认证、用户身份、数据库会话等依赖 callable。API 路由导入这些 callable,并通过 Depends(...)声明需求。FastAPI 在调用路由前解析依赖图、执行依赖并注入结果;对已经进入的 yield依赖,在响应生命周期结束或异常时触发退出清理。


#06 它与 Spring 到底怎么对应

答案不是一个类名,而是一组机制。

FastAPI Depends 与 Spring 机制对照

#认证提前拒绝

FastAPI 的认证依赖:

Python
Depends(get_current_identity)

在效果上接近 Spring Security Filter Chain:身份失败时,业务 Controller 或路由函数不执行。

而且 Spring 官方明确提醒:MVC HandlerInterceptor 并不适合作为安全层,安全逻辑应优先使用 Spring Security,或者尽早集成到 Servlet Filter Chain。

所以如果讨论 Token 认证,最接近的首先是 Spring Security Filter,而不是普通 MVC Interceptor。

#注入当前用户

FastAPI:

Python
identity = Depends(get_current_identity)

更像 Spring Controller 中的:

Java
@AuthenticationPrincipal UserPrincipal principal

两边都是把已经准备好的认证主体注入方法参数。

#解析方法参数

FastAPI 会调用依赖 callable,把结果放进路由参数。

这一点又很像 Spring MVC 的 HandlerMethodArgumentResolver:它根据参数声明解析并提供 Controller 方法参数。

#管理请求级资源

get_db​ 的 yield + finally 负责请求期间的资源建立和释放。

这在职责上接近 Spring 对事务、持久化上下文和 EntityManager 生命周期的管理。

但 FastAPI 不会因为用了 yield 就自动拥有 Spring 的事务语义。提交、回滚和关闭仍然要在依赖或数据库框架中明确实现。

#路径前后处理

HandlerInterceptor 能在 Handler 前后执行逻辑。

FastAPI 依赖也能在路由之前运行,yield 后还能执行退出代码,所以这部分看起来相似。

但它不是通用请求拦截协议。如果目标是统一包裹整个请求、修改 Request/Response 或处理全局横切逻辑,FastAPI/Starlette 的 Middleware 往往更接近 Spring Filter。


#07 为什么项目喜欢把这些东西集中到 deps.py

因为 API 路由很容易重复这些基础逻辑:

Text
解析 Authorization
验证 Token
获取当前用户 ID
创建数据库会话
向下游透传访问令牌

如果每个路由都手写,认证策略和资源清理很快就会分叉。

集中到 deps.py 后,路由只声明业务真正需要的参数:

Python
@router.post("/workflows")
async def create_workflow(
    subject = Depends(get_current_subject),
    db = Depends(get_db),
):
    ...

它带来的不只是少写几行代码。

更重要的是:

  • 认证失败行为保持一致;
  • 当前用户的来源保持一致;
  • 数据库会话的生命周期集中管理;
  • 下游 Token 的获取方式集中管理;
  • 测试时可以替换依赖;
  • OpenAPI 文档能整合依赖产生的参数和安全要求。

这就是为什么知识图里会看到多个 API 文件共同指向 deps.py

它是 API 层共用的基础设施入口,但不是自动扫描的全局拦截器。


#08 最后,用一句话记住

如果你来自 Spring,可以这样记:

从认证效果看,deps.py + Depends有点像拦截器;从机制看,它更像 Spring Security、方法参数解析和请求级资源管理的组合。

再精确一点:

Text
deps.py:普通模块,提供依赖 callable
Depends:声明路由需要什么
FastAPI:解析、调用并注入依赖
yield/finally:定义资源退出和清理
Middleware:处理更全局的请求/响应横切逻辑

所以,deps.py 不是“Python 版 Interceptor”。

它只是把 API 层反复需要的认证、身份和资源管理函数放在了一起。

真正让这套机制运转起来的,是 Depends(...) 声明和 FastAPI 的依赖解析系统。


#资料来源

  1. FastAPI Dependencies:https://fastapi.tiangolo.com/tutorial/dependencies/
  2. FastAPI Dependencies with yield:https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/
  3. Spring Framework MVC Interceptors:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/interceptors.html
  4. Spring Security Servlet Architecture:https://docs.spring.io/spring-security/reference/servlet/architecture.html
  5. Spring MVC Method Arguments:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/arguments.html

返回顶部

评论

还没有评论,来说点什么吧。

评论经发布者审核后公开