我对Web MVC框架实现原理的思考(第一篇)
虽然从软件架构的角度,我们需要权衡取舍,需要克制在系统中过分追求技术。但是对于每一个有理想的软件工程师,造轮子的情结是永恒的。各种各样的轮子,始终是软件工程师挑战自我的必经之路;web框架无疑应该是一个比较好的开始,我自己也曾经试图造一个精美的轮子,但是始终实在业务发展压倒一切的创业中,只能是点到为止。
所以,在有机会再次完整的去造轮子之前,通过自然语言,来整理一些思路;如果用画画来比喻,就是试着在画竹子之前,先整理一下自己胸中竹子的形象。Web框架是一个好的选择。
首先是Web框架的定义。Web框架应该是使用某种特定的语言开发,运行在服务器端,利用特定语言已经提供的http库或者http服务器完成自己的工作,处理用户请求,返回主要是Web页面在内的资源。Web框架应该是以编程语言API的形式出现;提供一套契约设计好的脚手架,一些实用工具。
理论上说,支持Http协议的Web服务器应该Web框架要讨论的范畴,但是根据具体的架构和技术选型,可以是跟web框架的定义或近或远的。下面是我自己使用过或者有印象的一些主流Web框架:
- PHP语言:CodeIgniter、ThinkPHP、Laraval;php的框架,http服务器,基本上是不包含在Web框架核心中的,Apache httpd或者php-fpm+nginx处理请求响应两端,web框架不需要考虑端口监听,这个相当于是一种解耦和。
- Node.js:Express,没有接触过太多其他的框架,在node.js的架构下,node.js本身是有完整的http客户端和服务器端能力的,我们完全可以用原生的node.js服务器端API,处理request,然后完成自己的业务逻辑,之后返回用户要请求的数据。
- Java:Spring MVC、Spring Boot;实际上Spring Boot框架下,严格的Web框架还是Spring MVC;Spring Boot通过内置的Jetty或者Tomcat来提供Http服务器的工作。在Java领域,应该可以有两种不同的思路,一种是基于Tomcat,Jetty作为Servlet容器,Web框架对Servlet进行封装,并提供自己的API,开发人员在此API之上开发;或者还可以使用Netty自己来编写全功能框架。
- Python:Django、Tornado;Django提供了脚手架支持,集成了一套功能强大的ORM,可以通过代码生成器,从代码直接创建数据表。
- Ruby:rails;做过简单的尝试,没有机会深入使用过,听说rails的模式是敏捷开发最初的利器。
Web框架的组成
一般说道Web框架,我觉得应该包含以下一些组件或者模块:URL路由、MVC模式支持、特定的html模版语言、ORM、脚手架或者代码生成器、配置管理。有些组件是必须,有些只是可选的,并不一定需要在框架中默认提供,开发者可以根据自己的需要自由集成需要的组件。
URL路由和控制器
url路由,是web框架的一个核心组件,资源的绑定,通过路由来配置。mvc的控制器层,在运行时视角,基本是从url路由来调度的。
说到路由,首先是对http服务器的理解,web mvc框架层,代码层面上,有各种不同的资源文件,各种不同的链接,函数。通过逻辑上的分解,降低了系统复杂性,将系统各层面的代码解耦到了不同的逻辑单元中。但是回到HTTP服务器的机制,其实物理上,http服务器都是单入口点,监听在特定端口的http服务器进程,所有的http请求都是从入口点程序开始执行的,入口点首先是对tcp套接字的封装,有的web框架会直接内嵌完整的http服务器,全功能的可以作为http服务器来运行,有的需要借助容器或者是纯web服务器来配合完成工作。
Python Tornado框架Tornado的原理跟nodejs差不多,入口程序调用http api,开启监听在绑定端口的套接字,框架底层嵌入的http服务器,会处理网络报文,将http协议的header解析出来,并且以api形式可以提供给基于框架的应用调用(这个地方语言和框架的边界不是十分清晰,语言底层一般会提供,框架可以自己在对api进行一层包装,提供自己的api)。
在web框架中,url路由,基本上是和MVC的controller层紧密协作的,路由负责控制对象的初始化和调用。一般控制器的原理都是通过框架提供的抽象类来封装一些公共的方法,然后子类实现具体的接口的方式工作的,将子类定义的公共方法,作为特定url指向的资源。
在tornado、express、laraval提供的url路由类型机制里,都是通过开发者自己配置维护一个路由表的方式,将url链接,跟静态资源,或者MVC的控制器绑定起来。在请求到来的时候,单入口执行过程中会有一个路由解析的过程,提取出url字符串,对字符串进行解析,然后跟路由表进行比较,使用字典是比较好的选择,然后根据不同语言的特性,可以有不同的方式,实现对路由表指向的特定资源的代码调用。
底层运行时,存在一个路由到控制器的运行时路由表,对于web框架来说是必须的。除了开发者可以直接配置的统一路由表的实现,还可以有其他不同的机制。
几种不同的URL路由机制
- 完全基于契约的CodeIgnitor模式,约定路由加载控制器的特定目录,然后根据控制器的命名和共有方法的名字,来跟请求的url进行匹配。这个是基于php的语言特性支持,能提供的一种优雅解决方案。
- 通过注解实现的,预加载路由表方式;Tomcat容器的在加载应用的时候会在加载阶段就加载所有的类,不同于PHP的运行时动态加载,所以需要占用更多的内存。Spring MVC是基于Java的Servlet,Tomcat是运行Servlet的容器;个人的观察Tomcat容器默认会提供Servlet、jsp等Jar包形式提供的类库。不过在注册特定uri资源的入口点,资源请求到来的时候,动态执行路由指定的控制器上,是一样的。
- 上面主要介绍的,显示的用专门的路由表,由开发人员自己配置的模式;这种方式的话,个人感觉比较容易维护路由表。
HTML模版语言
理论上说,对于控制器层而言,其实html代码,就是最终将字符串拼接起来,在对应需要服务端数据的地方,拼接上对应的存储数据的变量。这种输出的过程当然是比较原始的,早期的web或者JSP可能存在着大量这种混合式的调用,在逐步分解出jsp的过程中,会在jsp文件中,以html格式做主框架,然后任何需要数据的地方,直接调用底层的数据查询和处理代码。这种方式,逻辑是很混乱的。
有了MVC,我们会在控制器层,完成所有数据查询和组织,还有计算过程,以model的方式,将简单的Map、List、还有直接对象的方式,将数据变量绑定到Model中。这样View层的html模版中,只需要考虑怎么展示Model绑定的数据。
一般框架都会自带或者内嵌一个模版引擎,在控制器层,提供某种机制,将变量绑定到模版中。而模版中会提供很多处理数据显示,进行格式化输出,还有数值计算的使用函数,都是通过模版引擎支持的特定语法实现的。
脚手架和代码生成器
这个层面并非是必须的,express、rails、django,都是通过各自的包管理工具,可以直接初始化创建该框架应用的脚手架。在脚手架领域,基于npm有专门的Yeoman,可以自定义各种脚手架。
脚手架Yeoman一般来说,像django和laraval,都提供了migrant工具,就是一种代码生成器类似的概念,可以直接将配置文件或者定义的模型层代码,生成数据库的表和字段。另外他们还提供了方便的代码生成器功能,直接通过命令行下的命令就能快速生成控制器的代码。
另外还有与业务逻辑更紧密一些的ORM层代码生成器,离web框架的核心有一定距离,例如针对MyBatis,我们可以使用官方的mybatis-generator,编写配置文件,直接从数据库结构生成DAO层对应的一些CRUD代码。
结语
理论上说,提供数据访问支持,也是一个全功能web框架通常会提供的组件,不过数据访问,可以专门在另外一个主题讨论。本文主要就讨论一下我对MVC、URL路由、html模版、还有脚手架的一些理解和思考,试着理清自己的思路,后续,肯定会最终作为自己的一种能力提升的挑战去造轮子。