Java WEB 模式演变过程

JAVA WEB设计模式的演变过程,以及MVC、MVP、MVVM概念介绍

早期模式Servlet+JSP

Servlet+JSP(Java Server Page)。比较适合小项目。项目变大复杂后,后端各种复杂调用,各种代码掺杂在一起很难维护。JSP的代码维护性也会越来越差,还可以嵌套后端代码,如果嵌套了后端代码就导致前后端职责不明,维护成本越来越大。

MVC设计模式

MVC 是个非常好的协作模式,为了让 View 层更简单干脆,还可以选择 Velocity、Freemaker 等模板,问题点:
1、前端开发重度依赖开发环境。这种架构下,前后端协作有两种模式:一种是前端写 demo,写好后,让后端去套模板。淘宝早期包括现在依旧有大量业务线是这种模式。另一种协作模式是前端负责浏览器端的所有开发和服务器端的 View 层模板开发,支付宝是这种模式
2、前后端职责依旧纠缠不清。
Velocity 模板还是蛮强大的,变量、逻辑、宏等特性,依旧可以通过拿到的上下文变量来实现各种业务逻辑。这样,只要前端弱势一点,往往就会被后端要求在模板层写出不少业务代码。还有一个很大的灰色地带是 Controller,页面路由等功能本应该是前端最关注的,但却是由后端来实现。

经常会有人吐槽 Java,但 Java 在工程化开发方面真的做了大量思考和架构尝试。Java 蛮符合马云的一句话:让平凡人做非凡事。

例子:

spring mvc,spring boot,mybatis

package分层:

  • Controller
  • service
  • entity
  • dao

resources分层:

  • mappers

Controller只应该做自己应该做的事,它不应该处理业务相关的代码。Domain(service + dao)是一个相当复杂的层级,这里是业务的核心。dao文件夹和mappers文件夹都是数据层的一部分

我们在Controller层应该做的事是:

  • 处理请求的参数
  • 渲染和重定向
  • 选择Model和Service
  • 处理Session和Cookies

View(html)层是一直在变化的层级

Ajax 带来的 SPA 时代

历史滚滚往前,2004 年 Gmail 像风一样的女子来到人间,很快 2005 年 Ajax 正式提出,加上 CDN 开始大量用于静态资源存储,于是出现了 JavaScript 王者归来的 SPA (Single Page Application 单页面应用)时代。

这种模式下,前后端的分工非常清晰,前后端的关键协作点是 Ajax 接口。看起来是如此美妙,但回过头来看看的话,这与 JSP 时代区别不大。复杂度从服务端的 JSP 里移到了浏览器的 JavaScript,浏览器端变得很复杂。类似 Spring MVC,这个时代开始出现浏览器端的分层架构

对于 SPA 应用,有几个很重要的挑战:
1、前后端接口的约定。如果后端的接口一塌糊涂,如果后端的业务模型不够稳定,那么前端开发会很痛苦。所以要规范接口
2、前端开发的复杂度控制。SPA 应用大多以功能交互型为主,JavaScript 代码过十万行很正常。大量 JS 代码的组织,与 View 层的绑定等,都不是容易的事情。典型的解决方案是业界的 Backbone,但 Backbone 做的事还很有限,依旧存在大量空白区域需要挑战。.
SPA 让前端看到了一丝绿色,但依旧是在荒漠中行走。

前端为主的 MV* 时代

为了降低前端开发复杂度,除了 Backbone,还有大量框架涌现,比如 EmberJS、KnockoutJS、AngularJS 等等。这些框架总的原则是先按类型分层,比如 Templates、Controllers、Models,然后再在层内做切分,如下图:

好处很明显:

  1. 前后端职责很清晰。前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难,前端可以本地开发。后端则可以专注于业务逻辑的处理,输出 RESTful 等接口。
  2. 前端开发的复杂度可控。
  3. 部署相对独立,产品体验可以快速改进。

但依旧有不足之处:

  1. 代码不能复用。
  2. 全异步,对 SEO 不利。
  3. 性能并非最佳,特别是移动互联网环境下。
  4. SPA 不能满足所有需求,依旧存在大量多页面应用。URL Design 需要后端配合,前端无法完全掌控。

Node.js 带来的全栈时代

前端为主的 MV* 模式解决了很多很多问题,但如上所述,依旧存在不少不足之处。随着 Node.js 的兴起,JavaScript 开始有能力运行在服务端。这意味着可以有一种新的研发模式:

在这种研发模式下,前后端的职责很清晰。对前端来说,两个 UI 层各司其职:

  1. Front-end UI layer 处理浏览器层的展现逻辑。通过 CSS 渲染样式,通过 JavaScript 添加交互功能,HTML 的生成也可以放在这层,具体看应用场景。

  2. Back-end UI layer 处理路由、模板、数据获取、cookie 等。通过路由,前端终于可以自主把控 URL Design,这样无论是单页面应用还是多页面应用,前端都可以自由调控。后端也终于可以摆脱对展现的强关注,转而可以专心于业务逻辑层的开发。

通过 Node,Web Server 层也是 JavaScript 代码,这意味着部分代码可前后复用,需要 SEO 的场景可以在服务端同步渲染,由于异步请求太多导致的性能问题也可以通过服务端来缓解。前一种模式的不足,通过这种模式几乎都能完美解决掉。

与 JSP 模式相比,全栈模式看起来是一种回归,也的确是一种向原始开发模式的回归,不过是一种螺旋上升式的回归。

基于 Node 的全栈模式,依旧面临很多挑战:

  1. 需要前端对服务端编程有更进一步的认识。比如 network/tcp、PE 等知识的掌握。
  2. Node 层与 Java 层的高效通信。Node 模式下,都在服务器端,RESTful HTTP 通信未必高效,通过 SOAP 等方式通信更高效。一切需要在验证中前行。
  3. 对部署、运维层面的熟练了解,需要更多知识点和实操经验。
  4. 大量历史遗留问题如何过渡。这可能是最大最大的阻力。

一次构建,跨平台运行

  • 在我们需要学习C语言的时候,GCC就有了这样的跨平台编译。
  • 在我们开发桌面应用的时候,QT有就这样的跨平台能力。
  • 在我们构建Web应用的时候,Java有这样的跨平台能力。
  • 在我们需要开发跨平台应用的时候,Cordova有这样的跨平台能力。
  • 现在,React这样的跨平台框架又出现了,而响应式设计也是跨平台式的设计。
  • 响应式设计不得不提到的一个缺点是:他只是将原本在模板层做的事,放到了样式(CSS)层。你还是在针对着不同的设备进行设计,两种没有什么多大的不同。复杂度不会消失,也不会凭空产生,它只会从一个物体转移到另一个物体或一种形式转为另一种形式。
  • React,将一小部分复杂度交由人来消化,将另外一部分交给了React自己来消化。在用Spring MVC之前,也许我们还在用CGI编程,而Spring降低了这部分复杂度,但是这和React一样降低的只是新手的复杂度。在我们不能以某种语言的方式写某相关的代码时,这会带来诸多麻烦。

MVC MVP MVVM 介绍

用户界面的层次称为View,应用程序的模型表示业务规则为Model(也可以理解为Java中的对象),Controller是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处

MVC模式

MVC模式图

  • View 传送指令到 Controller,它的职责为进行Model和View之间的协作(路由、输入预处理等)的应用逻辑(application logic),通常视图是依据模型数据创建的
  • Controller 完成业务逻辑后,要求 Model 改变状态,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据
  • Model 将新的数据发送到 View,用户得到反馈,通常模型对象负责在数据库中存取数据,(模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器)

例子一:

  • JavaBean作为模型,既可以作为数据模型来封装业务数据,又可以作为业务逻辑模型来包含应用的业务操作。其中,数据模型用来存储或传递业务数据,而业务逻辑模型接收到控制器传过来的模型更新请求后,执行特定的业务逻辑处理,然后返回相应的执行结果。
  • JSP作为表现层,负责提供页面为用户展示数据,提供相应的表单(Form)来用于用户的请求,并在适当的时候(点击按钮)向控制器发出请求来请求模型进行更新。
  • Serlvet作为控制器,用来接收用户提交的请求,然后获取请求中的数据,将之转换为业务模型需要的数据模型,然后调用业务模型相应的业务方法进行更新,同时根据业务执行结果来选择要返回的视图。

例子二(Struts2中):

  • 控制器——filterdispatcher
  • 动作(Model)——Action
  • 视图——Result

优点:

  • 耦合性低
  • 重用性高
  • 部署快速,生命周期成本低,分工职责现对明确
  • 可维护性高

缺点:

  • 完全理解MVC比较复杂
  • 调试困难
  • 增加系统结构复杂性
  • 视图与控制器间的过于紧密的连接并且降低了视图对模型数据的访问。视图与控制器是相互分离,但却是联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。

MVC调用图

看似没有什么特别的地方,但是由几个需要特别关注的关键点:

  • View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。
  • Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。
  • View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。

MVC2

MVC2调用图
服务端接收到来自客户端的请求,服务端通过路由规则把这个请求交由给特定的Controller进行处理,Controller执行相应的应用逻辑,对Model进行操作,Model执行业务逻辑以后;然后用数据去渲染特定的模版,返回给客户端。

MVC2.0

JSP Model 1应用管理的混乱诱发了JSP参考了客户端MVC模式,催生了Model 2

MVP模式

MVP模式有两种:

  • Passive View
  • Supervising Controller

分离view 与 model 层的功能,使得程序更加清晰 提高分离 解耦性,减轻activity的工作量 将业务代码单独抽取出来,各自负责各自层应该做的事情。简单一句话说就是基于MVC模式演变而来,提高代码结构清晰度,提高程序解耦性可维护性的模式。

接下来我们具体梳理下MVP究竟是什么

  • M 对应的Model 数据层,负责操作、获取数据
  • V 对应的是View 也就是Activity层,负责UI、与用户进行交互
  • P Presenter 就是我们要分离的业务逻辑层,连接view和model层,处理业务逻辑。

为什么要有MVP

Android开发不同于JavaWeb开发的是利用MVC模式 使得控制器和展示层完全分离,但是Android中activity有控制器也有view做的事情,如果把布局文件单独分成view层,几乎就没有做什么事情,而activity还承担了部分view层的工作,到最后需求越来越多,activity也会越来也胖。所以在Android开发中可以使用MVP模式来清晰的分离各自层的工作。同样也利于单元测试。

MVP模型图
MVP打破了View原来对于Model的依赖,其余的依赖关系和MVC模式一致

MVP(Passive View)

MVP调用图
和MVC模式一样,用户对View的操作都会从View交移给Presenter。Presenter会执行相应的应用程序逻辑,并且对Model进行相应的操作;而这时候Model执行完业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面

MVP(Passive View)的优缺点

优点:

  • 便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性
  • View可以组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件

缺点:

  • Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难

MVP(Supervising Controller)

上面讲的是MVP的Passive View模式,该模式下View非常Passive,它几乎什么都不知道,Presenter让它干什么它就干什么。而Supervising Controller模式中,Presenter会把一部分简单的同步逻辑交给View自己去做,Presenter只负责比较复杂的、高层次的UI操作,所以可以把它看成一个Supervising Controller。

Supervising Controller模式下的依赖和调用关系:
MVPSC调用图

MVVM

MVVM可以看作是一种特殊的MVP(Passive View)模式,或者说是对MVP模式的一种改良

ViewModel

MVVM代表的是Model-View-ViewModel,这里需要解释一下什么是ViewModel。ViewModel的含义就是 “Model of View”,视图的模型。它的含义包含了领域模型(Domain Model)和视图的状态(State)。 在图形界面应用程序当中,界面所提供的信息可能不仅仅包含应用程序的领域模型。还可能包含一些领域模型不包含的视图状态,例如电子表格程序上需要显示当前排序的状态是顺序的还是逆序的,而这是Domain Model所不包含的,但也是需要显示的信息。

可以简单把ViewModel理解为页面上所显示内容的数据抽象,和Domain Model不一样,ViewModel更适合用来描述View。

MVVM模型图

MVVM调用关系

MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。

也就是说,MVVM把View和Model的同步逻辑自动化了。以前Presenter负责的View和Model同步不再手动地进行操作,而是交由框架所提供的Binder进行负责。只需要告诉Binder,View显示的数据对应的是Model哪一部分即可

MVVM调用关系

MVVM的优缺点

优点:

  • 提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。
  • 简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。

缺点:

  • 过于简单的图形界面不适用,或说牛刀杀鸡。
  • 对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。
  • 数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。

小结

回顾历史总是让人感慨,展望未来则让人兴奋。上面讲到的研发模式,除了最后一种还在探索期,其他各种在各大公司都已有大量实践。几点小结:

  1. 模式没有好坏高下之分,只有合不合适。
  2. Ajax 给前端开发带来了一次质的飞跃,Node 很可能是第二次。
  3. SoC(关注度分离) 是一条伟大的原则。上面种种模式,都是让前后端的职责更清晰,分工更合理高效。
  4. 还有个原则,让合适的人做合适的事。比如 Web Server 层的 UI Layer 开发,前端是更合适的人选。

历史有时候会打转,咋一看以为是回去了,实际上是螺旋转了一圈,站在了一个新的起点。

参考