Django CBV 源码解析:一个 HTTP 请求如何精准定位到你的 get() 方法
刚从前端转到 Django 开发的同学,初看这种写法时往往会感到困惑:

复制代码class TestView(APIView):
def get(self, request):
return Response({"message": "hello"})
URL 配置中是这样注册的:
复制代码from django.urls import path
from . import viewsurlpatterns = [
path("test/", views.TestView.as_view()),
]
疑问随之而来:
as_view()究竟做了什么?为什么不能直接传入TestView类?- HTTP 请求进入后,框架是如何自动匹配到
get()方法的? - 如果我在类中定义了
post(),POST 请求又是如何准确调用它的?
带着这些疑问深入阅读源码后,一切豁然开朗。
FBV 与 CBV 的对比
先了解背景知识。Django 编写视图有两种主流方式:
FBV(函数视图)
复制代码def test_view(request):
if request.method == 'GET':
return JsonResponse({"message": "hello"})
if request.method == 'POST':
name = request.POST.get('name')
return JsonResponse({"message": f"hello {name}"})
CBV(类视图)
复制代码class TestView(APIView):
def get(self, request):
return Response({"message": "hello"}) def post(self, request):
name = request.data.get('name')
return Response({"message": f"hello {name}"})
CBV 的优势十分显著:
- 告别大量
if request.method ==条件判断,每个 HTTP 方法对应独立函数,职责划分明确 - 支持继承与复用,
APIView内置了认证、权限、解析器等通用逻辑 - 代码架构更加符合面向对象设计原则
第一个核心问题:as_view() 的作用是什么
URL 注册时使用的是:
复制代码path('test/', TestView.as_view()),
而不是:
复制代码path('test/', TestView),
原因何在?
Django 的 URL 路由系统期望得到一个可调用函数,用于处理请求。但 TestView 是一个类,无法直接被当作函数调用。
as_view() 的核心使命就是将类转换为函数。来看源码实现:
复制代码# django/views/generic/base.py@classonlymethod
def as_view(cls, **initkwargs):
def view(*args, **kwargs):
self = cls(**initkwargs) # 每次请求到来时,实例化一个新对象
return self.dispatch(*args, **kwargs) # 转交给 dispatch 方法处理
return view # 返回这个内层函数
简化后的逻辑:
复制代码as_view() 返回一个名为 view 的函数
URL 匹配时调用 view(request)
view 内部 → 实例化当前类 → 调用 dispatch()
值得注意的是,每次请求都会创建全新的类实例,因此完全不必担心多个请求之间的状态相互干扰。
第二个核心问题:dispatch() 如何定位到 get() 方法
这是 CBV 机制中最精华的部分,源码如下:
复制代码# django/views/generic/base.pydef dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
逐行解读:
第一步:将请求方法转换为小写
复制代码request.method # 例如 'GET'
request.method.lower() # 转换为 'get'
第二步:借助 getattr 动态获取对应方法
复制代码handler = getattr(self, 'get', self.http_method_not_allowed)
这里巧妙利用了 Python 的特性:方法本身就是对象的属性。
复制代码class TestView(APIView):
def get(self, request): # 定义了 get 方法
...view = TestView()
view.get # 可以像访问属性一样获取该方法
getattr(view, 'get') # 完全等价的写法
因此,getattr(self, 'get', self.http_method_not_allowed) 的含义是:
第三步:执行找到的方法
复制代码return handler(request, *args, **kwargs)
# 等价于直接调用 self.get(request, *args, **kwargs)
完整请求处理流程
复制代码客户端发起 GET /api/v1/test/ ↓ URL 路由匹配成功,执行 TestView.as_view() 返回的 view 函数 ↓实例化 TestView,并调用 dispatch(request) ↓request.method.lower() → 'get'
getattr(self, 'get', http_method_not_allowed) → 成功获取 self.get 方法 ↓self.get(request) 被正式调用 ↓return Response({"message": "hello"}) ↓客户端收到成功的 HTTP 响应
如果客户端发起了一个 DELETE 请求,但你并未定义 delete() 方法:
复制代码getattr(self, 'delete', self.http_method_not_allowed)
→ 未找到 self.delete
→ 返回默认值 self.http_method_not_allowed
→ 自动响应 405 Method Not Allowed
框架已经为你做好了兜底处理,无需额外编写判断逻辑。
View 与 APIView 的差异
上文提到的 dispatch 是 Django 原生 View 的核心逻辑。DRF 的 APIView 继承了它,并在 dispatch 中进行了增强:
复制代码# rest_framework/views.pydef dispatch(self, request, *args, **kwargs):
# 1. 将原生 request 包装为 DRF Request 对象
request = self.initialize_request(request, *args, **kwargs)
try:
# 2. 执行认证、权限、限流等检查
self.initial(request, *args, **kwargs)
# 3. 与原生 View 一致,根据方法名动态查找并执行
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs) except Exception as exc:
# 4. 统一异常处理
response = self.handle_exception(exc) return self.finalize_response(request, response, *args, **kwargs)
相比原生 View,APIView 额外增加了四层能力:
| 原生 View | APIView | |
|---|---|---|
| request 对象 | Django HttpRequest | DRF Request(支持 request.data) |
| 认证 | 需手动处理 | 自动执行 |
| 权限 | 需手动处理 | 自动执行 |
| 异常处理 | 需手动处理 | 统一兜底 |
这也正是开发 DRF 接口时推荐继承 APIView 而非 View 的原因:无需重复处理这些通用逻辑,可以更专注于业务实现。
DRF Request 与原生 Request 的关键区别
APIView 对原生 request 进行了封装,最直观的差异体现在:
复制代码# 原生 View
request.POST.get('name') # 仅能获取表单数据
json.loads(request.body) # JSON 数据需要手动解析# APIView
request.data.get('name') # 自动解析 JSON / 表单 / multipart 等格式
request.query_params # 等价于 request.GET,语义更加清晰
总结
阅读完源码后,开头的三个问题都有了明确的答案:
as_view() 的本质是什么?
它将类转换为可调用的函数,使得 URL 路由系统能够正常使用。每次请求进入时都会创建该类的一个新实例。
HTTP 请求是如何找到 get() 方法的?
dispatch() 方法通过 getattr(self, request.method.lower(), ...) 动态获取对应方法。在 Python 中,方法本身也是属性,因此 getattr 可以精准定位。
APIView 相比 View 增强了哪些能力?
它封装了 request 对象,自动执行认证、权限和限流检查,并提供统一的异常处理机制。
