前言
这段时间准备开始阅读和熟悉一下django的源码,达到更好地了解django项目,方便未来开发django 项目的目的。这是第一部分的阅读,阅读一些django项目启动时候相关的源码,针对django版本号2.2.5。
本次专题——django项目启动
我们都知道,启动一个django工程用的是python manage.py runserver命令,所以manage.py文件无疑就是启动django项目的入口文件,这里我将通过从入口文件出发,一步一步阅读跟django项目启动相关的源码,看看在这个过程中都做了些什么,同时给出我自己的解释。
本篇是第三部分,继续上两篇的内容,上两篇链接:
Normal WLS:Django项目启动——源码阅读(一)zhuanlan.zhihu.com注:源代码中两个井号"##"表示我自己的注解,是我认为需要重点分析或者是包含代码核心逻辑的地方,因为包含较多代码,所以建议在电脑端阅读。
源代码&分析
上面部分是django项目在启动之前的准备阶段,也就是完成项目的配置、检查和准备工作,一切都安排妥当没有问题了,才能真正地runserver拉起服务器,等待网络请求,所以这里来看看runserver真正启动服务器的相关逻辑代码。
- utility.execute():首先回过头看看之前utility.execute()函数中后面的代码,在准备工作完成之后(如django.setup()),会真正执行命令相应的逻辑代码,所以先根据命令类型做一些判断,以runserver为例,则会进入else分支。
## django package: django/core/management/__init__.py
- fetch_command(subcommand):这个函数的作用如其名字,就是获取命令对应的命令类(Command),命令类需要继承BaseCommand。主要逻辑是:先通过get_commands()获取到整个项目中的所有命令的dict,然后再根据这个命令所在的包通过load_command_class()获取到对应的命令类。
## django package: django/core/management/__init__.py
- get_commands():可以看到,这个函数用于导入整个项目所有的命令,最后返回一个dict,形式是{命令: app包名}。
## django package: django/core/management/__init__.py
需要特别说下导入用户自定义命令的操作,可能很多人没用过,其实挺好用的,官方文档地址:https://docs.djangoproject.com/en/2.2/howto/custom-management-commands/。
可以看到这是用户自定义命令时候所要求的对应app的目录结构,看了这一段源码就会明白为什么需要建一个management目录了,同理,需要建一个commands目录的原因是因为上面的find_commands函数是从commands目录里去找命令的。
- load_command_class(app_name, subcommand):顾名思义,就是从对应的app包模块下的management目录下的commands目录下导入对应名字的命令文件中的Command类实例化,然后返回。
## django package: django/core/management/__init__.py
- run_from_argv(self.argv):拿到了对应命令文件中的Command类对象之后,就调用这个函数。其实这个函数是写在BaseCommand类中的,所有的内置或用户自定义命令类都需要继承这个基类,所以可以统一调用。这个函数主要是将用户输入的命令和参数做好一个划分,然后传到self.execute函数中做进一步处理,其他的不是重点。
## django package: django/core/management/base.py
- self.execute(*args, **cmd_options):本以为execute函数应该已经是实际的逻辑调用了,不过一看源码发现它还是相当于一个基类的统领,通过调用self.handle来调用具体命令的逻辑。在BaseCommand类中handle函数是直接抛错,所以它相当于一个接口,希望继承的类来实现handle函数,所以在用户实现自定义命令的用法中,也要求用户在Command类中实现handle函数作为逻辑函数,这一切都真相大白了。
## django package: django/core/management/base.py
接下来看看runserver.py中Command类中的handle函数是怎么实现的。
- handle(self, *args, **options):runserver命令真正的处理函数。
## django package: django/core/management/commands/runserver.py
- run(self, **options):这个是runsever中Command类的run函数,判断用不用自动重启然后调用inner_run函数。
## django package: django/core/management/commands/runserver.py
- inner_run(self, *args, **options):在命令行输出一些提示信息,然后真正拉起服务器,等待并接收请求。
## django package: django/core/management/commands/runserver.py
- run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):调用servers/basehttp.py的函数,可以看出这个已经是服务器内部实现的函数的,所以直到这一步,runsever命令的服务才真正被启动并等待接收请求。
## django package: django/core/servers/basehttp.py
总结
到这里,这一个专题可以说是完结了,感觉还是很有收获的,这里结合前两次的内容总结一下。
- 首先,我们了解了django项目的入口程序其实就是manage.py通过对命令行参数进行解析,然后对不同的命令进行不同的处理。
- 针对runserver(django项目启动),其实最最关键的地方就在于django/__init__.py中的setup函数,在这里先设置了url解析的前缀,让开发者既可以自定义,也可以在开发过程中的urls.py中不用添加前置'/',接下来就是对于所有app的相关配置。
- app的相关配置有两个关键部分,一个是对单个app的配置类AppConfig,一个是对所有app的管理类Apps,这两个类紧密相关,是总分关系,同时相互索引。在初始化过程中,Apps在控制流程,主要包括对所有app进行路径解析、名字去重、对每个app配置类进行初始化、赋予每个app配置类它们的相关models、还有就是执行每个app开发者自己定义的ready函数。
- 在真正执行runserver命令的部分,我们可以看到是用了fetch_command函数拿到对应命令的Command类,然后再调用BaseCommand类中的run_from_argv函数。通过这一部分,我们可以更好地了解到为什么自定义命令的时候需要在app中创建management目录和commands目录,为什么Command类需要继承BaseCommand类,为什么Command类中需要实现一个handle函数,这些都是从源码到用法的验证。
- 从整个runserver命令的流程上看,最主要的就是两个部分,一个是setup去做app相关的配置和检查,搞定之后,就找到真正的handle函数去做服务的拉起和接收请求,这样runserver的使命就算是完成了。
所以,看完源码,结合开发经验,我认为要想成功启动项目,最重要的就是把settings里面的installed_app变量写对,而且添加app之后记得把相对的路径加到配置文件中。同时,apps.py下的ready函数可以给我们一个不错的做启动的初始化的钩子,这个要记住并好好利用。最后就是自定义命令的用法,这个也还对理解该用法挺有帮助的。
如果分析得不对,欢迎指正;如果对django源码有兴趣,欢迎关注投稿。