Jesse James Garrett 2005 年明确提出了 Ajax 的概念,无疑对 web 客户端的展现效果产生了巨大的革新,对 Template 整页刷新技术形成了巨大的挑战,也为客户端展现逻辑和服务端逻辑分离开启了曙光。REST 的风格则为服务端程序的利用 URL 的天然特性进行了分布服务化,并将之前的事务型 Controller(一个 action 对应一个操作)的服务端逻辑模型拆散成资源性逻辑。这两个技术概念则为客户端展现逻辑和服务端逻辑的彻底分离奠定了基础。
在没有 Ajax 之前,客户端与服务端交互技术普遍采用的是 Template 整页刷新技术。客户端每次提交数据到服务端,服务端进行事务处理之后,都会进行 Template 整页刷新。之前客户操作引起的界面展现模型状态是需要传递到后台,否则就无法给用户界面操作以连贯性的支持。如果页面的展现状态不是很繁复多变的情况下,Template 整页刷新交互方式问题不大。但在如今这个特别强调人机交互的时代里,这种方法在设计上来说是会造成界面展现逻辑和应用逻辑层层次不清,在工作量上来说也增加了不少传递界面展现模型的重复逻辑。Ajax 交互则不然,它可以在维持当前界面状态的同时,往服务端提交数据,服务端应用逻辑处理完之后,将处理结果返回,客户端按照处理结果,进行客户端的界面的改变。明显的,维持界面状态连贯性的工作就被省却了,服务端代码可以只需关注商业逻辑即可,层次也变得清晰起来。
按照这种思路 ,客户端逻辑维持展现,服务端只提供对 model 的操作,等于之前的服务端展现逻辑被移植到客户端去,这涉及到展现逻辑的语言使用 Java 语言变成了 JavaScript 语言。这种变化对于提出概念的人说,可能也只是一种语言的转换,可从实际的编程角度来说,从有着丰富 lib 支持的静态语言 Java 到 JavaScript 这缺乏完整函数库支持的动态语言,是一个巨大的鸿沟。JavaScript 以前只被作为一种简单的界面表现以及验错的语言(部分得导致其内涵完全被低估)一下子无法承受起支撑整个展现逻辑的重任。更为重要的是,培养一个 JavaScript 程序员的成本非常高。也许 AJAX+REST 这种设计对代码是整体最优的,但从项目管理开发人员资源分配来说,成本更为低廉 (JavaScript 开发人员少,而要其懂业务逻辑成本就更大了 )。
但事物正在慢慢地变化,JS 的框架层出不穷的涌现,prototype、Dojo、Ext JS、jQuery 等让代码大为简化 , 这个因素开始慢慢弱化。现在应用 REST+Ajax 架构的技术难度也越来越小了。
还有一个产业发展的因素,现在不再只是浏览器才能访问 web 网站了,还有许多如移动设备 app 根本就不需要 HTML 的展现形式,一个是费流量,另一个是展现是客户端控件完成,不需要 HTML 给其以做渲染指导。
在外部和内部共同作用下,这种架构的发展结果导致未来有这种可能性:所谓的 web 服务端程序就是一堆 Web service, 然后客户端就第一次下载一堆基本的网页结构到本地,然后每次在本地网页上去运行 JS 去组织调用 Web service 得到数据进行用户输入处理和返回数据的页面渲染。由于网页在本地运行,其安全级别较高,可以访问很多本地资源,功能会很强大(如 chrome 的 extension)。
但仍然有此 Ajax( 交互方式)+REST 模型解决不了的问题:
从应用场景来说,当需要从一个应用场景跳转到另一个完全不同的应用场景,界面状态的保留就不是必要的;而且在跳转后往往需要渲染大量的展现数据和相应控件的结构代码(HTML),这时 Template 整页刷新的优势就体现出来,劣势也得到了避免。
从性能上考虑,一个复杂的事务型逻辑操作从逻辑上来说可以拆散成多个资源性操作,但这必然导致交互的增加和性能下降。
从架构风格的适用性考虑,REST 是以资源 +CRUD 的方式看待世界,那的确在信息化系统中大多数场景都是围绕这个主题,但也有部分逻辑确实是事务性很强(相当于 job),比如以 excel 形式导入大量的数据到数据库,涉及许多对象的创建,包含删除(覆盖导入),修改等,那此时使用 REST 就未必适宜。
所以综合考虑是将 Ajax+REST 和 Template 整页刷新综合 应用于各业务场景,从各业务场景的不同来应用。
下面以典型页面布局结合层次图来说明此种方法的应用。
这是一个典型的信息化系统页面布局:
- header:头部标题说明系统主题还有相当于使用者在本次会话中的登陆全局信息,在所有页面中都会被包含。
- footer:关于本系统的一些介绍,帮助,地图等,所有页面都会包含。
- navigator: 导航,对于具体功能页的一个索引,导航控件会维持控件的操作和功能页入口页面的访问,登陆成功后页面一直存在,直到登出 (log out)。
- content: 功能页,实现具体的商业逻辑,一般情况下是对 VO(View Object 即展现层直接可以使用的渲染对象 ) 的增删改查。很少被其他页面使用。
客户端页面逻辑层
图上看很小,其实很大,所有的展现逻辑被从原来的服务端展现层移动到这一层次,原来的服务端被退化成 REST Web service 这层。
对于一般的业务系统,为了实现业务逻辑和展现逻辑分离,保证业务逻辑的一定可重用性和移植性。采用以下原则:
- 让 JavaScript 维持页面的展现状态模型(如目前在哪个功能页面中,再如按钮的选中状态是选中还是没选中等页面元素的状态信息),把服务器逻辑从这些繁琐的展现状态模型中不具备重用性的地方给解耦。
- 根据页面存在的生存周期来拆分相应的展现状态模型,让页面模块化。
比如 navigator 在登陆后一直包含,相当于对话生存域,可以将对话中所有的全局变量保存在对话生存域(将当前用户可访问到的功能定位 (url) 和相应菜单的绑定以及用户自身会对功能存在印象的信息存入,按需访问)
Header | 全局 (global),访问到结束一直存在 |
|
Navigator | 会话 (session), 用户登陆后一直存在 |
|
Content | 局部(local), 在功能中一直存在 | 功能页使用控件的配置信息; |
Footer | 全局,访问到结束一直存在 |
一般情况下,请将页面展现逻辑只放在其相应可以控制的 HTML 元素内。
- 从具体的功能入口 URL 进入到展现 VO 列表页的访问,在此列表页中也有 JS 展现状态局部模型维持,其会初始化相应的展现控件,并去后台访问 web service 进行业务 VO 获取,将网页渲染出来,同时把控件中的事件和相关业务操作进行逻辑绑定。
- 进入列表后进行的操作,主要由 JavaScript 控件逻辑进行增、删、改、查、通信协议一般为 JSON,调用的服务为 REST 服务,根据结果调整页面上的展现。
- 对于大量数据的展现和大量不变的展现元素(沿用 HTML 的叫法称之为块元素 (block) 最好在服务端进行渲染,客户端只负责少量 HTML 元素变动,和控件内部的 HTML 变动)
- 对于页面的展现状态模型最好在 JavaScript 以 JSON 形式维护 ,HTML 中的相应元素来维护或者 xml 文件维护,以 JavaScript 方便的能够能加载即可。由于其各客户的偏好差别太大重用性较小,基本没必要将其放入业务逻辑层如数据库,对页面人员来说修改半格式化文件比修改数据库要容易得多。
- 对于工作形式的操作比如前面提到的例子中的 excel 导入,使用 REST+Ajax 就不适宜,本身工作形式的操作往往交互比较少,但消耗系统资源如 CPU、I/O 会比较多,这种情况还是应该使用传统的事务型逻辑处理为宜。
REST Web Services
里面根据页面列表的需要将 VO 生成,并传递给页面。VO 可以跨越多个 PO(Persistent Object, 持久对象 ),对于 VO 的增加和修改要注意的是如果是某 PO 关联另外 PO 情况必须可以提供让用户根据外键来选择用户的关联对象展示体。
客户端页面逻辑层和 REST Web service 的错误信息传递
有服务端根据错误的情况返回错误码和错误信息给前台(最好是利用 HTTP 的 status code)客户端根据错误码来对错误信息进行展现。
我们以一个实际的系统例子来开始描述这个问题
在按照图 2 的布局的首页中,上方是全局头部区域,左侧是菜单区域,有 5 个菜单项,右边是功能区域,通过点击菜单区域的菜单项可以改变功能区域的显示。
在首页的菜单区域会放置全局的菜单控制配置形如:
// 菜单 <div class="menu"> <ul id="submenu1"> <li><a href="/xxx/a.do"> 信息管理 </a></li> <li><a href="/xxx/b.do"> 报表 </a></li> </ul> </div> // 导航区 <div class="crumb"> <div class="crumb-left" id="crumb"> <a href="#"> 导航条 </a> </div> <div class="crumb-right"></div> </div> |
然后菜单区域引用一个 JS 文件,放置菜单导航等页面展现逻辑,维护菜单和导航条的状态,基本功能为菜单系统导航条内部事件建立,如鼠标移动到某个菜单,菜单会高亮;点击菜单会进行对应的菜单绑定功能页在功能区域的刷新等。
右边的功能区域中就是实现具体功能逻辑,比如展示所有公司的属性列表,可以在其中使用列表控件,里面初始采用 Template 刷新将整个控件的大量数据模型对象和控件的基础结构渲染出来,然后运行页面逻辑层的初始化代码(在 Template 中编写)以及控件的初始化配置进行列表控件本身的消息机制的建立,然后用户可以对列表进行操作,页面逻辑层控制本地界面的变化,如需与服务器交互则根据用户需要往后台发送 Ajax 请求或者进行场景的跳离(Template 整页刷新)。
显而易见的,菜单区域、导航区域、全局头部区域会贯穿所有页面,生存区域相当于 global,而功能区域随着菜单项的选择会变化,生存区域相当于 local。我们可以采用分层的思想根据区域的生存域不同,放置不同的页面逻辑代码 (jQuery),从而达到代码逻辑的清晰简化目的(省却了 global 状态的服务器同步)。
由于涉及到功能页中大量控件和模型对象的产生和销毁,功能页的切换使用 Template 整页刷新;而在不跳离功能页时控件状态的改变(需要与后台交互部分)则使用 Ajax 调用。服务端逻辑除了初始化公司查询逻辑以 Template 整页刷新提供,其他对公司得操作以 REST 形式将公司资源开放 CRUD 功能。
- jQuery 可以方便的发出 Ajax 的 REST 风格的 GET,POST,PUT,DELETE HTTP 请求,支持根据 statuscode 为异常时的绑定。
采用 selector 可以方便的对 HTML 元素进行展现操作,并具有很好的跨浏览器支持。
非侵入式的 JavaScript 框架,性能好,扩展方便。
- Spring 3:通过 RequestMapping 可以方便的对 REST 形式的 GET,POST,PUT,DELETE request 进行方法绑定,并自然支持 Java 对象到 JSON 格式的转换。更为有利的是,允许 Template 模板刷新和 REST 的形式数据直接返回同时存在。
由于大部分展现逻辑都在 JS 客户端完成,因此不同于以往服务器生成所有的页面引用的变量,许多逻辑用服务端程序调试工具 (eclipse debug) 调试不到,因此需要一些工具的支持:
- JS 调试器:(firefox)firebug,(IE8)dev tool
可以对 JS 代码进行调试跟踪,并可以观察变量的值。
- HTTP 抓包器:(IE)fiddler2,(firefox)firebug
可以跟踪客户端提交的 HTTP request 和 response.
- DOM 查看器:IE,firefox(firebug),chrome
可以对控件生成的 HTML 元素进行跟踪,以对封装良好的控件做展现研究。
需支持 REST, 特别是 PUT、DELETE 内支持 body 中填充数据解析的服务器 .但是过去的应用服务器如:Tomcat 6 会直接丢弃 PUT、DELETE 的 HTTP request 的 Body。
- Jetty7:直接支持
- Tomcat7:需要在 HTTPconnector 配置属性 parseBodyMethods 将 PUT、DELETE 加入
此种设计的目的在于利用 Ajax+REST 和 Template 整页刷新的优点,将业务展现层逻辑隔绝于页面并进行分层,提升真正可以长久使用的业务对象逻辑可重用性。在行业信息化系统中,展现层可以千变万化,业务逻辑层却基本不变,以此能够更好更快的满足用户千变万化的需求。
<!-- CMA ID: 854846 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-document-html-6.0.xsl -->
(转自DW)
相关推荐
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 ...
数字证书:从文件中读取数字证书,生成文件输入流,输入文件为c:/mycert.cer,获取一个处理X.509证书的证书工厂…… Java+ajax写的登录实例 1个目标文件 内容索引:Java源码,初学实例,ajax,登录 一个Java+ajax写的...