相信MVC模式是大家相当熟悉的一种web开发模式了。大多数web开发者应该都会用到这样或那样的mvc框架,struts,ruby on rails,express等等。但是对于很多初学者,甚至很多熟练使用那些mvc框架的人,他们也并没有真正的理解mvc的内涵,也很难准确的说出哪部分是M,哪部分是V,哪部分是C,同时这三部分又分别干了什么工作。虽然说实际上MVC的划分本身界限也不是那么清晰,不同框架的实现也不会简简单单的很明确的分成3层。MVC作为一种不断发展,更新,迭代的思想和编程模式,所以它并没有很明确的定义,我们不必纠结与到底哪句代码属于哪个层次(很多代码可能根本不属于任何层次,或者是一些不同实现中添加的辅助层次,像是中间件,fiter等)。
但是这种大致的思想和划分我们需要有明确的个人理解,即便它与某些观点并不完全一致。我习惯于从发展的角度来看待问题,历史使人明智,从一个技术的发展不仅可以很深刻的理解这门技术,同时也会对很多相关概念有深入的理解。
实际上web开发处理的最底层就是http协议,浏览器作为客户端发送http请求,http请求包括请求头部和请求体。而无论任何服务器,最开始处理的肯定就是这个http请求。有的服务器需要相应容器,例如servlet一般在tomcat中,.net一般在iis中,而有的服务器不需要相应容器,本身就具备底层解析能力,例如node。然后所有的网站的处理过程就是得到http请求对象后,对请求进行处理,通过设置response对象进行响应。
最开始网站大多是静态网站,在服务器上网站就是一个目录结构,里面全是html的静态文件。然后http直接请求某个静态文件,服务端读取那个静态文件作为response响应。很简单的过程,没有任何层次化,只有三个步骤:
1、接收request并读取request请求文件的路径;
2、读取文件;
3、通过response将读取的文件响应给客户端。
从代码的角度可以很简单的实现,伪代码:
var path = request.url;
var html =readFile(path);
response.setHead(200,{'Content-type','text/html'});
response.end(html);
接下来人们不满足于静态的网站,于是出现了动态网页技术。我想最开始动态网页实现是同过cgi,这个时候服务端的处理过程不再是直接读取一个html文件作为响应字符串。人请求的也不再是单纯的html静态文件。http请求的将是一个路径,并且可以带有数据(因为服务端已经具有了处理能力),而那个路径对应着一个处理程序。cgi就是那个处理程序,一个可以让服务器调用的存在于服务端的独立的应用程序,可以用各种语言去编写。这个程序的执行结果就是一个html字符串,然后把这个字符串交给response作为响应。这样的话html将不再是静态的,而是可控的,可自由拼接的,加入各种数据的。所以到这里大家应该明白什么是动态网页,什么是静态网页了吧。这个过程相比于静态网站多出来的处理主要是两个,同时不需要读取html文件:
1、把请求路径与cgi关联映射,这个过程一般称为路由解析;
2、服务器调用cgi。
所以从代码的角度看应该是这样的:
var path = request.url;
var request.data = getData(request);
var cgi =route(path);
var html = call(cgi,request,response);//多进程的调度
response.setHead(200,{'Content-type','text/html'});
response.end(html);
但是cgi有一个比较大的缺陷就是cgi程序不属于服务器程序的一部分,而是一个单独的进程,这样的话,每一个请求都要启动一个进程,一旦访问量过大,服务器将难以承受。于是servlet等技术出现,它是能够运行在服务器程序中的一段java代码,每一个请将也不会启动一个进程,而是创建一个线程用来处理请求。这样开销将会小很多。过程一般如下:
var path =request.url
var request.data = getData(request);
var servlet = route(url);
servlet(request,response);
与cgi的不同之处应该在于可以在servlet内部直接用response来进行响应,因为他们的上下文是一样的,而cgi是另一个进程,无法直接响应。
接下来人们又发现这种在程序中拼接html字符串的方式太繁琐,我们不得不花费精力去处理这种拼接逻辑,无法专注于核心业务逻辑,也就是人们经常说的视图呈现与业务处理耦合性太大。于是乎又一类技术出现了,即jsp,asp,php等真正的动态页面技术,之所以称他们为真正的动态页面,是因为它们都是独立的文件,而不在是程序拼接的html字符串。以jsp为例,它又是如何实现这种视图解耦的呢?jsp这类页面实际上就是拥有一些特殊标签的html页面,它包含着一些非html的自己设定的标签,例如“<% %>,<%=%> ,<% include %>,<@page >”等等,所以它们实际上也算得上是最初的模板技术。想想最开始的静态网站,作为响应的html字符串是通过读取一个html文件得到的。而现在我们要做的就是读取一个jsp文件,但是这个文件并不是符合html规范的字符串(因为它有特殊标签,否则的话不又变成了了静态页面了么),所以在读取它们的时候我们需要解析,把其中的特殊标签变成合法的html字符串,这个解析过程一般称为页面渲染,用于解析的程序一般就成为模板引擎了。这些特殊标签的作用实际上就是用于数据的替换,这样最终html字符串的生成也不再是静态的,而是模板加数据通过模板引擎的结合。到这里我们能看出这个过程中最重要的就是模板引擎的解析了,它的输入数据有两个,一个是模板,即jsp文件;一个就是数据。那么数据从哪来呢,这个时候就出现了所谓的module层,实际上就是用于模板引擎解析的数据,在java中一般就是一个对象,被称为javabean。而这个javabean对象的填充可以是直接根据请求填充的,也可以在servlet中进行修改。这样模板引擎的启动条件就具备了,最终的html字符串也就可以生成了。
过程如下:
var path =request.url;
var request.data = getData(request);
var servlet =route(url);
servlet内部:
var data =change(data);//填充module层
var html = render(url,data);//render为模板引擎,url为模板文件路径
Response.setHead(200,{'Content-type','text/html'});
Response.end(html);
模板引擎的内部实现一般是两个步骤:1、读取模板文件;2、通过正则表达式替换特殊标签(这个时候就体现出了正则的强大之处,需要好好学哦)。
实际上第二个步骤还需要细分为两个步骤:1、模板编译;2、数据填充
其中关键的地方自然是模板编译,模板编译的结果实际上是生成一个中间函数,然后用中间函数接收data进行数据填充。之所以这样分离是为了模板一次编译可以多次执行。这里关于模板的问题不在详细描述,请自己查阅相关资料。
到这里,大家应该已经感受到了mvc的层次感,首先整个逻辑处理都封装在servlet中,所以sevlert毫无疑问就是mvc中的c,控制器。然后看servlet内部,对数据的处理形成的数据对象就是module层,而那个jsp文件就是view层,他们就代表着层的实体,之所以这样说,就是因为他们都是独立存在的文件,对象或者程序,无论是servlet,javabean还是jsp都是可以单独编辑的。这样才形成了隔离与解耦。但是如果不从这个角度来看,最终的视图呈现实际上是通过模板引擎渲染得到的html字符串。所以最简单的mvc就是module和view通过control的控制操作最终形成视图反馈给客户。
以上讲的实际上并不是jsp真正的实现,而是独立模板技术的实现,独立的模板需要在servlet手动调用模板引擎进行整合。因为我感觉这样讲更清晰。真正的servlet加jsp技术还是有些区别的,它的模板解析并不需要我们在servlet中去手动调用,而是一种内置功能,我们只需在配置中指定jsp路径,模板引擎就是其web容器。因此这类模板极度依赖上下文环境。所以这也是独立模板技术的出现的原因,可以脱离上下文。
mvc实现在本质都是在实现封装以上过程,并且提供了更多更强大功能,如添加了一些过滤层,路由映射配置化(servlet的web.xml),更巧妙优化的模板引擎设计等等。而一些著名的mvc框架有再此基础上进一步进行优化,可配置性,可视化更好,方便人们进行业务分离。以上理解只是在在对一些框架的使用基础上得出的,加上本人入行不久,才疏学浅,肯定有很多错误之处,希望共同探讨。相信如果读一些框架源码能够学到更多的东西,并对整个过程会有更深刻的理解。