Django CBV 源码解读:一个请求是怎么找到你的 get() 方法的

17 阅读4分钟

Django CBV 源码解读:一个请求是怎么找到你的 get() 方法的

作者:呱呱复呱呱 | DRF 系列第一篇


前言

刚从前端转过来学 Django,第一次看到这种写法时懵了:

class TestView(APIView):
    def get(self, request):
        return Response({"message": "hello"})

URL 里这样注册:

from django.urls import path
from . import views

urlpatterns = [
    path("test/", views.TestView.as_view()),
]

问题来了:

  • as_view() 是什么?为什么不直接写 TestView
  • 请求进来怎么就自动找到 get() 方法了?
  • 我要是定义了 post(),POST 请求又怎么知道去调它?

带着这些问题去翻了源码,一下子全想通了。


FBV vs 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 ==,每种请求方法对应一个函数,职责清晰
  • 可以继承复用,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.py

def 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) 的意思是:

去 self 上找名字叫 'get' 的属性(方法),找不到就用 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"})

  ↓

客户端收到响应

如果客户端发了一个 DELETE 请求,但你没有定义 delete() 方法:

getattr(self, 'delete', self.http_method_not_allowed)
→ 找不到 self.delete
→ 返回默认值 self.http_method_not_allowed
→ 自动响应 405 Method Not Allowed

框架帮你兜底了,不需要自己处理。


View vs APIView

上面说的 dispatch 是 Django 原生 View 的逻辑。DRF 的 APIView 继承了它,并在 dispatch 里做了增强:

# rest_framework/views.py

def 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 多了四件事:

原生 ViewAPIView
request 对象Django HttpRequestDRF 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 路由系统能用它。每次请求进来都会实例化一个新对象。

请求怎么找到 get() 方法? dispatch()getattr(self, request.method.lower(), ...) 动态取方法。Python 里方法也是属性,所以 getattr 能取到。

APIViewView 多了什么? 包装了 request 对象,自动执行认证/权限/限流,统一处理异常。


系列持续更新,下一篇:DRF Serializer 核心机制 — 数据是怎么进来又怎么出去的