通过最小化 Demo 验证 Django 中间件的 4 个钩子函数,并对比类视图 (CBV) 与函数视图 (FBV) 的异同。
一、环境
| 项目 | 版本 |
|---|---|
| Python | 3.13 |
| Django | 6.0.5 |
项目结构:
DjangoHook/
├── demo/
│ ├── middleware.py ← 自定义中间件(4 钩子)
│ └── views.py ← FBV + CBV 对比视图
├── screenshots/
└── middleware_demo.log ← 中间件运行日志
二、中间件 4 个钩子函数
Django 中间件是请求/响应的"钩子链",可以在请求到达视图前/后、异常发生时插入自定义逻辑。
执行顺序遵循 洋葱模型:请求阶段从上到下穿过所有中间件,响应阶段反向穿回。
2.1 四个钩子一览
| 钩子 | 触发时机 | 返回值 | 执行方向 |
|---|---|---|---|
process_request(request) |
请求进入,视图未确定 | None 或 HttpResponse |
↓ 正向 |
process_view(request, view_func, view_args, view_kwargs) |
路由匹配后、视图执行前 | None 或 HttpResponse |
↓ 正向 |
process_exception(request, exception) |
视图抛出异常时 | None 或 HttpResponse |
↑ 反向 |
process_response(request, response) |
响应返回前 | HttpResponse |
↑ 反向 |
2.2 核心源码
python
# demo/middleware.py
class DemoMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
self.process_request(request) # 钩子1
response = self.get_response(request) # → 内部触发钩子2、3
response = self.process_response(request, response) # 钩子4
return response
def process_request(self, request):
log(f"1. process_request --- {request.path}")
def process_view(self, request, view_func, view_args, view_kwargs):
log(f"2. process_view --- {view_func.__name__}")
def process_exception(self, request, exception):
log(f"3. process_exception --- {type(exception).__name__}: {exception}")
def process_response(self, request, response):
log(f"4. process_response --- {response.status_code}")
return response
2.3 实验验证
分别请求正常视图和异常视图,观察中间件日志:

结论:
- 正常路径 :
process_request→process_view→ 视图执行 →process_response - 异常路径 :
process_request→process_view→ 视图抛异常 →process_exception→process_response
浏览器截图如下------
正常请求 (FBV hello) :

异常请求 (FBV exception) :

2.4 洋葱模型图解
请求 ──────────────────────────────→ 响应
┌─────────────────────────────────────────┐
│ 1.process_request() 4.process_response() │
│ ↓ ↑ │
│ 2.process_view() 3.process_exception() │
│ ↓ ↑ │
│ ┌──────────────────┐ │
│ │ 视图函数 │ │
│ └──────────────────┘ │
└─────────────────────────────────────────┘
三、CBV vs FBV 对比
FBV (Function-Based View) 和 CBV (Class-Based View) 都能处理 HTTP 请求,核心差异在于 代码组织方式。
3.1 最简示例
python
# ========== FBV ==========
def hello_fbv(request):
return HttpResponse("Hello from FBV!")
# ========== CBV ==========
class HelloCBV(View):
def get(self, request):
return HttpResponse("Hello from CBV!")
浏览器截图:
| FBV | CBV |
|---|---|
![]() |
![]() |
3.2 处理多种 HTTP 方法
python
# ========== FBV: 手动判断 request.method ==========
@csrf_exempt
def api_fbv(request):
if request.method == "GET":
return JsonResponse({"method": "GET", "type": "FBV"})
elif request.method == "POST":
data = json.loads(request.body) if request.body else {}
return JsonResponse({"method": "POST", "type": "FBV", "received": data})
return JsonResponse({"error": "Method not allowed"}, status=405)
# ========== CBV: 方法名自动分发 ==========
@method_decorator(csrf_exempt, name='dispatch')
class ApiCBV(View):
def get(self, request):
return JsonResponse({"method": "GET", "type": "CBV"})
def post(self, request):
data = json.loads(request.body) if request.body else {}
return JsonResponse({"method": "POST", "type": "CBV", "received": data})
3.3 对比总结
| 维度 | FBV | CBV |
|---|---|---|
| 代码量 (简单场景) | 更少 | 稍多 |
| 代码量 (多方法场景) | 手动 if/elif | 方法自动分发 |
| HTTP 方法分发 | 手动判断 request.method |
自动映射 get() → GET, post() → POST |
| 复用方式 | @decorator |
继承 + Mixin |
| 可读性 | 函数体膨胀 | 每个方法职责单一 |
| Django 内置通用视图 | 不支持 | ListView, DetailView 等 |
3.4 何时用哪个?
- 逻辑简单、单一功能 → FBV,例如:重定向、简单 API、健康检查
- CRUD 操作、多种 HTTP 方法 → CBV,例如:REST API 资源、表单处理
- 需要复用 Django 内置视图 → CBV,例如:分页列表、详情页
四、路由配置
python
# DjangoHook/urls.py
from django.urls import path
from demo import views
urlpatterns = [
# FBV
path('fbv/hello/', views.hello_fbv, name='fbv_hello'),
path('fbv/api/', views.api_fbv, name='fbv_api'),
path('fbv/exception/', views.exception_fbv, name='fbv_exception'),
# CBV
path('cbv/hello/', views.HelloCBV.as_view(), name='cbv_hello'),
path('cbv/api/', views.ApiCBV.as_view(), name='cbv_api'),
path('cbv/exception/', views.ExceptionCBV.as_view(), name='cbv_exception'),
]
注意 CBV 必须调用 .as_view() 将类转换为可调用的视图函数。

