解耦是永恒的主题:MVC框架的发展
JSP 和 Servlet
概念介绍
Servlet 指的是服务端的一种 Java 写的组件,它可以接收和处理来自浏览器的请求,并生成结果数据,通常它会是 HTML、JSON 等常见格式,写入 HTTP 响应,返回给用户。
JSP,它的全称叫做 Java Server Pages,它允许静态的 HTML 页面插入一些类似于“<% %>”这样的标记(scriptlet),而在这样的标记中,还能以表达式或代码片段的方式,嵌入一些 Java 代码,在 Web 容器响应 HTTP 请求时,这些标记里的 Java 代码会得到执行,这些标记也会被替换成代码实际执行的结果,嵌入页面中一并返回。这样一来,原本静态的页面,就能动态执行代码,并将执行结果写入页面了。
动手验证
- jdk
- tomcat, 它是一款 Web 容器,也是一款 Servlet 容器
现在你可以打开 hello_005fworld_jsp.java,如果你有 Java 基础,那么你应该可以看得懂其中的代码。代码中公有类 hello_005fworld_jsp 继承自 HttpJspBase 类,而如果你查看 Tomcat 的 API 文档,你就会知道,原来它进一步继承自 HttpServlet 类,也就是说,这个自动生成的 Java 文件,就是 Servlet。
out.write("Hello world! Time: ");
out.print( new java.util.Date() );
out.write('\n');
JSP 和 Servlet 并不是完全独立的“两个人”,JSP 实际工作的时候,是以 Servlet 的形式存在的,也就是说,前者其实是可以转化成后者的。
深入理解
为什么不直接使用 Servlet,而要设计出 JSP 这样的技术,让其在实际运行中转化成 Servlet 来执行呢?
- 从编程范型的角度来看,JSP 页面的代码多是基于声明式(Declarative),而 Servlet 的代码则多是基于命令式(Imperative). 这两种技术适合不同的场景。这两个概念,最初来源于编程范型的分类,声明式编程,是去描述物件的性质,而非给出指令,而命令式编程则恰恰相反。
命令式代码,在 Servlet 中,它会一条一条语句告诉计算机下一步该做什么,这个过程就是命令式的。我们绝大多数的代码都是命令式的。声明式代码是告诉计算机“什么样”,而不关注“怎么做”;命令式代码则是告诉计算机“怎么做”,而不关注“什么样”。
为什么需要两种方式?
- 对于某些问题,使用声明式会更符合直觉,更形象,因而更接近于人类的语言;
- 而另一些问题,则使用命令式,更符合行为步骤的思考模式,更严谨,也更能够预知机器会怎样执行。
计算机生来就是遵循命令执行的,因此声明式的 JSP 页面会被转化成一行行命令式的 Servlet 代码,交给计算机执行。可是,你可以想象一下,如果 HTML 那样适合声明式表述的代码,程序员使用命令式来手写会是怎样的一场噩梦——代码将会变成无趣且易错的一行行字符串拼接。
MVC 的演进
MVC 模式包含这样三层:
- 控制器(Controller),恰如其名,主要负责请求的处理、校验和转发。
- 视图(View),将内容数据以界面的方式呈现给用户,也捕获和响应用户的操作。
- 模型(Model),数据和业务逻辑真正的集散地。
JSP Model 1
JSP Model 1 是整个演化过程中最古老的一种,请求处理的整个过程,包括参数验证、数据访问、业务处理,到页面渲染(或者响应构造),全部都放在 JSP 页面里面完成。JSP 页面既当爹又当妈,静态页面和嵌入动态表达式的特性,使得它可以很好地容纳声明式代码;而 JSP 的 scriptlet,又完全支持多行 Java 代码的写入,因此它又可以很好地容纳命令式代码。
![](https://img.haomeiwen.com/i24563685/72a0f13bdf756c11.png)
JSP Model 2
在 Model 1 中,你可以对 JSP 页面上的内容进行模块和职责的划分,但是由于它们都在一个页面上,物理层面上可以说是完全耦合在一起,因此模块化和单一职责无从谈起。和 Model 1 相比,Model 2 做了明显的改进。
- JSP 只用来做一件事,那就是页面渲染,换言之,JSP 从全能先生转变成了单一职责的页面模板;
- 引入 JavaBean 的概念,它将数据库访问等获取数据对象的行为封装了起来,成为业务数据的唯一来源;
- 请求处理和派发的活交到纯 Servlet 手里,它成为了 MVC 的“大脑”,它知道创建哪个 JavaBean 准备好业务数据,也知道将请求引导到哪个 JSP 页面去做渲染。
![](https://img.haomeiwen.com/i24563685/395f66d955a6f29f.png)
MVC 的一般化
JSP Model 2 已经具备了 MVC 的基本形态,但是,它却对技术栈有着明确限制——Servlet、JSP 和 JavaBean。今天我们见到的 MVC,已经和实现技术无关了,并且,在 MVC 三层大体职责确定的基础上,其中的交互和数据流动却是有许多不同的实现方式的。
第一种
![](https://img.haomeiwen.com/i24563685/4f578b839095dc7f.png)
用户请求发送给 Controller,而 Controller 是大总管,需要主动调用 Model 层的接口去取得实际需要的数据对象,之后将数据对象发送给需要渲染的 View,View 渲染之后返回页面给用户。
在这种情况下,Controller 往往会比较大,因为它要知道需要调用哪个 Model 的接口获取数据对象,还需要知道要把数据对象发送给哪个 View 去渲染;View 和 Model 都比较简单纯粹,它们都只需要被动地根据 Controller 的要求完成它们自己的任务就好了。
第二种
![](https://img.haomeiwen.com/i24563685/3acf0d29b6827d0a.png)
Controller 调用 Model 的接口发起数据更新操作,接着就直接转向最终的 View 去了;View 会调用 Model 去取得经过 Controller 更新操作以后的最新对象,渲染并返回给用户。
Controller 先调用 Model, 再调用 View, 而 View 调用 Model.
在这种情况下,Controller 相对就会比较简单,而这里写操作是由 Controller 发起的,读操作是由 View 发起的,二者的业务对象模型可以不相同,非常适合需要 CQRS(Command Query Responsibility Segregation,命令查询职责分离)的场景
MVC 的变体
当它的核心三层和它们的基本职责发生变化,这样的架构模式就不再是严格意义上的 MVC 了。这里我介绍两种 MVC 的变体:MVP 和 MVVM。
MVP
MVP 包含的三层为 Model、View 和 Presenter,它往往被用在用户的界面设计当中,和 MVC 比起来,Controller 被 Presenter 替代了。
Presenter: 主持人.
- Model 的职责没有太大的变化,依然是业务数据的唯一来源。
- View 变成了纯粹的被动视图,它被动地响应用户的操作来触发事件,并将其转交给 Presenter;反过来,它的视图界面被动地由 Presenter 来发起更新。
- Presenter 变成了 View 和 Model 之间的协调者(Middle-man),它是真正调度逻辑的持有者,会根据事件对 Model 进行状态更新,又在 Model 层发生改变时,相应地更新 View。
![](https://img.haomeiwen.com/i24563685/28c4441e476642c2.png)
MVVM
MVVM 是在 MVP 的基础上,将职责最多的 Presenter 替换成了 ViewModel,它实际是一个数据对象的转换器,将从 Model 中取得的数据简化,转换为 View 可以识别的形式返回给 View。View 和 ViewModel 实行双向绑定,成为命运共同体,即 View 的变化会自动反馈到 ViewModel 中,反之亦然。
![](https://img.haomeiwen.com/i24563685/726540cd0723f5d5.png)
![](https://img.haomeiwen.com/i24563685/ffa36deadb59ab26.png)