FastAPI 中间件 (Middleware) vs 依赖注入 (Dependency Injection)
虽然两者都可以用于将通用逻辑从主要业务代码中解耦出来,但它们在工作方式、作用范围和适用场景上有显著区别。
1. 核心概念与执行时机
| 特性 | 中间件 (Middleware) | 依赖注入 (Dependency Injection) |
|---|---|---|
| 核心概念 | 一个拦截器/过滤器,包裹着整个应用或特定路径。 | 一个函数或可调用对象,用于在请求处理期间提供(注入)共享资源或逻辑。 |
| 执行时机 | 在每个请求到达任何路由处理函数之前执行,以及在每个响应返回给客户端之后执行(洋葱模型)。 | 在特定的路径操作函数执行之前,当该函数声明了 Depends(依赖项) 时才执行。 |
2. 作用范围
| 特性 | 中间件 (Middleware) | 依赖注入 (Dependency Injection) |
|---|---|---|
| 作用范围 | 全局。影响应用接收到的每一个请求和发出的每一个响应。 | 局部。可以应用于单个路径操作函数、单个路径装饰器 (dependencies=[...])、APIRouter 或通过 Depends() 在函数内声明。 |
3. 数据流向与返回
| 特性 | 中间件 (Middleware) | 依赖注入 (Dependency Injection) |
|---|---|---|
| 数据返回给路由 | 通常不直接返回数据给路径操作函数(虽然可以通过 request.state 传递少量信息)。 |
直接返回数据给路径操作函数,作为函数的参数。 |
| 访问 Request/Response | 可以访问和修改 Request 对象(在处理函数之前)和 Response 对象(在处理函数之后)。 |
不能直接访问 Request 或 Response(除非显式传递)。 |
4. 应用场景
| 场景 | 中间件 (Middleware) | 依赖注入 (Dependency Injection) |
|---|---|---|
| 日志记录 | ✅ 记录所有请求的进入和离开时间、状态码等。 | - |
| CORS (跨域资源共享) | ✅ 全局设置允许的源、方法、头等。 | - |
| 请求/响应修改 | ✅ 统一添加响应头、修改请求体(较少见)。 | - |
| 性能监控 | ✅ 计算每个请求的处理时间。 | - |
| 全局认证/授权检查 | ✅ 检查每个请求的令牌(虽然也可以用依赖注入实现)。 | ✅ 检查特定路由的令牌。 |
| 数据库连接/会话获取 | - | ✅ 为需要数据库连接的特定路由提供连接。 |
| 参数校验与预处理 | - | ✅ 为特定路由校验查询参数、路径参数或请求体。 |
| 资源获取 | - | ✅ 为特定路由获取用户信息、配置等。 |
代码示例
示例 1: 中间件 - 全局日志记录
这个中间件会记录每一个进入应用的请求和响应时间。
python
from fastapi import FastAPI, Request
from fastapi.middleware.base import BaseHTTPMiddleware
import time
import logging
# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
#构造中间件的日志处理函数
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next): #(request: Request, call_next) 固定参数
# --- 请求前处理 ---
start_time = time.time()
logger.info(f"MIDDLEWARE: Received {request.method} {request.url.path}")
# 调用下一个中间件或路由处理函数
response = await call_next(request)
# --- 响应后处理 ---
process_time = time.time() - start_time
logger.info(f"MIDDLEWARE: Response status {response.status_code} in {process_time:.4f}s for {request.url.path}")
return response
# 注册中间件
app.add_middleware(LoggingMiddleware)
@app.get("/")
async def read_root():
logger.info("ROUTE: Processing / route") #控制台输出路由信息
return {"message": "Hello World"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
logger.info(f"ROUTE: Processing /users/{user_id} route") #控制台输出路由信息
return {"user_id": user_id}
# 访问 / 或 /users/123 都会触发 LoggingMiddleware
# 控制台输出示例:
# INFO: MIDDLEWARE: Received GET /users/123
# INFO: ROUTE: Processing /users/123 route
# INFO: MIDDLEWARE: Response status 200 in 0.0012s for /users/123
示例 2: 依赖注入 - 局部数据库会话获取
这个依赖项只为需要数据库会话的路径操作函数提供服务。
python
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
app = FastAPI()
# 路径操作函数 1:不需要数据库会话,不会触发db_connect()依赖
@app.get("/status")
async def get_status():
logger.info("ROUTE: 没有使用依赖注入")
return {"status": "ok"}
# 路径操作函数 2:需要数据库会话,触发db_connect()依赖
# 需要引入Depends
# 定义数据库链接函数,用于重复使用
#@app.post("/db")
async def db_connect(sqlText:str):
# 1. 定义数据库连接参数
config = {
'host': 'localhost', # MySQL 服务器地址,通常是 'localhost' 或 IP
'port': 3307, # MySQL 默认端口
'user': 'root', # 你的 MySQL 用户名
'password': 'root', # 你的 MySQL 密码
'database': 'world', # 你要连接的数据库名
'charset': 'utf8mb4' # 推荐使用 utf8mb4 支持完整的 UTF-8 字符集
}
try:
# 2. 建立数据库连接
connection = pymysql.connect(**config)
print("成功连接到数据库!")
# 3. 创建游标对象
with connection.cursor() as cursor: # 使用 with 确保游标在使用后关闭
# 4. 执行 SQL 查询
cursor.execute(sqlText)
# 5. 获取查询结果
result = cursor.fetchall()
print(f"数据库版本: {result[0]}")
except pymysql.MySQLError as e:
print(f"数据库错误: {e}")
finally:
# 6. 关闭数据库连接
if connection:
connection.close()
print("数据库连接已关闭。")
return result
#接口路由函数,使用依赖注入Depends(函数名称)
@app.post("/getBookList")
async def getBookList(con=Depends(db_connect)): # 注入数据库连接函数,并获取返回的数据
print(type(con)) #获取注入函数返回的对象con
num :int =0
ls :list = []
while num <10:
num += 1
print(con[num])
ls.append(con[num])
return ls