论坛首页 Java企业应用论坛

基于虚拟单一环境的Web编程模型

浏览 2262 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-03-15  
OO
本文来自 http://hi.baidu.com/yuxichu/blog

1. Web开发技术的困境
1.1 JSF/ASP.net的缺陷

JSF/ASP.net在Web编程技术的发展中是一个了不起的进步,无数赞美的文章以及成功的案例足以说明这一点。但遗憾的是,JSF/ASP.net虽然实现了面向对象的Web编程,却在提供高效的Web应用系统方面做了倒退。在典型的JSF/ASP.net程序中,每一次Web事件的处理都需要将整个表单的数据发送到服务器端,由Web服务器重建该表单的服务器端视图,再调用相应的事件处理函数进行处理。这种处理方式利于实现代码与界面的分离和面向对象编程, 但却对系统性能造成了严重的影响,主要的缺陷有:

   1. 所有的表单数据(包括表单的状态视图数据)都要发送到服务器上,整个页面需要刷新, 无法做到按需传递数据,在客户端与服务器之间频繁进行这种大量数据的传送将严重占用网络带宽;
   2. 在当前客户机的性能越来越好的环境下,却将所有的业务逻辑都放在服务器端执行, 甚至简单的调整显示界面的代码都放到服务器端执行,这是对客户端资源的严重浪费, 简直可以用“暴殄天物”来形容;
   3. 虽然经历了种种努力,在JSF/ASP.net编写的稍为复杂的Web程序中其界面描述部分(如JSF中的jsp文件和ASP.net中的aspx文件)中仍然做不到完全消除JavaScript代码(这些代码出现在<script></script>或html标签的onEVENT属性中)。这样,JSF/ASP.net实际上没有完全实现界面与代码分离的目标。
   4. 必须使用客户端脚本时,很难以直观的方法使 JavaScript代码也能以面向对象的方式进行编程。 虽然,某些JSF实现方案可以与AJAX结合起来使用,但这只在一定程度上解决了客户端与服务器之间数据交换的问题, 却使得整个编程模型更加晦涩难懂,而且AJAX并不是一个真正的面向对象编程模型。

1.2 一个常见的小“错误”

在分析了JSF/ASP.net技术的缺陷之后,为了便于描述本文即将提出的单一环境Web编程模型,在这里还得讲述一下我们很多人在刚学Web编程时都可能犯过的一个小“错误”──偶尔误解了代码的运行环境。在一个ASP/JSP文件中,其内容常常是一团各种内容的混杂体:各种HTML标签用于描述界面、客户端<script></script>中包含着在浏览器中执行的代码、 <% %>之间则嵌着服务器端代码。这些内容混杂在一起,还经常必须通过服务器端代码来生成客户端代码。由于它们是如此的混乱,以致于很多人都犯过混淆代码运行环境的小“错误”,如例1-1所示。

例1-1  在ASP中显示错误消息(错误的形式)

<script language="JavaScript">
var msg;
<% msg = Err.Description %>
alert(msg);
</script>

在例1-1中,其意图是通过alert函数将服务器端的错误消息显示给用户, 却没有注意到无法在服务器端直接修改客户端变量的值。其正确形式应该是这样的:

例1-2 在ASP中显示错误消息(正确的形式)

<script language="JavaScript">
var msg = '<% =Err.Description %>';
alert(msg);
</script>

但例1-2中的表示方式却不符合我们的思维习惯,如果错误描述中包含有单引号(')或回车换行符(\r\n), 就特别容易发生错误。当这种要求反复出现时,常常会给程序员带来沉重的负担。

在ASP/JSP程序中经常发生的这类“错误”说明,我们的大脑习惯于连续的思考同一个问题。在理想的情况下,一次业务或事件处理过程应该视为一个连续过程,而在传统的Web编程环境中,程序员必须将这个过程划分为多个阶段,其中一部分发生在客户端、而另一部分发生在服务器端,同时还得考虑客户端与服务器端之间的连接过程,于是一个连续的过程就被人为切割为多个片断,同时还得考虑各个片断的运行环境以及片断之间的影响。由于这类划分不是合乎逻辑的自然划分,从而对程序员的大脑提出了严重的挑战。当任务繁重时,人们很容易因为疏忽发生不易查觉的错误, 从而为系统的安全与稳定埋下严重的隐患。
2. 单一环境Web编程模型
2.1 概述

由于JSF/ASP.net在性能方面存在重大缺陷,AJAX则无法实现真正的面向对象编程(关于AJAX的不足之处已有很多文章予以描述,为免累赘本文不再重述),而在Web程序运行环境中,由于客户端与服务器的区分迫使程序员必须对一个业务处理过程进行人为的划分,同时这种划分导致各部分代码之间的存在相当微妙的影响,这就极易击中我们的思维盲区,因此本文顺应例1-2中描述的“小错误”,将错就错,提出单一环境Web编程模型。 该模型的基本思想是,为应用程序员营造一个虚拟的单一运行时环境,使得程序员可以将用户界面的事件处理逻辑编写为一个单一的处理过程, 不需区分哪些是客户端脚本,哪些是服务器端代码;然后由编译器将这个单一处理过程编译为适当的客户端脚本和服务器端代码, 并在适当的位置自动添加处理客户端脚本与服务器端代码之间交互的代码。

为便于理解,下面将以一个简单的示例来演示单一环境Web编程模型的实现机制。 该示例的功能为:点击页面上的一个按钮,则弹出一个模态对话框,向用户报告服务器上的当前时间。为便于与已有的Web编程技术进行比较, 该示例分别用ASP.net和单一环境Web编程模型描述一遍。

首先,以ASP.net描述,由两个文件组成:ServerTime.aspx和ServerTime.aspx.cs,分别如下:

例2-1  以ASP.net编写的显示服务器时间的用户界面部分

-----------------------------------ServerTime.aspx---------------------------------------
<%@ Page language="c#" Codebehind="ServerTime.aspx.cs"
AutoEventWireup="false" Inherits="test.ServerTime" %>
<html>
  <head>
    <title>ServerTime</title>
  </head>
  <body>
    <form id="Form1" method="post" runat="server">
    <asp:Button Text="显示" Runat=server ID="BtnShowServerTime"></asp:Button>
    </form>
  </body>
</html>

例2-2  以ASP.net编写的显示服务器时间的代码部分

-----------------------------------ServerTime.aspx.cs---------------------------------------
namespace test
{
  public class ServerTime : System.Web.UI.Page
  {
    protected System.Web.UI.WebControls.Button BtnShowServerTime;
    override protected void OnInit(EventArgs e)
    {
      InitializeComponent();
      base.OnInit(e);
    }
    private void InitializeComponent()
    {
      this.BtnShowServerTime.Click += new System.EventHandler(this.BtnShowServerTime_Click);
    }

    private void BtnShowServerTime_Click(object sender, System.EventArgs e)
    {
      //生成消息
      String msg=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
      //生成在客户端显示消息的脚本,其中脚本内容在浏览器上执行
      this.RegisterClientScriptBlock("X","<script>alert('"+msg+"');</script>");
    }

  }
}

接下来,以单一环境Web编程模型描述,同样也由两个文件组成:ServerTime.uw和ServerTime.uw.code,其中ServerTime.uw.code使用类C#语法编写, 其内容分别如下:

例3-1  以单一环境Web编程模型编写的显示服务器时间的用户界面部分

------------------------ServerTime.uw-------------------------------------------
<HTML>
  <HEAD>
    <title>ServerTime</title>
  </HEAD>
  <body>
    <form id="Form1" method="post" runat="server">
      <input type="button" name="BtnShowServerTime" value="显示">
    </form>
  </body>
</HTML>

例3-2  以单一环境Web编程模型编写的显示服务器时间的代码部分

------------------------ServerTime.uw.code-------------------------------------------
namespace test
{
  public class ServerTime
  {
    //为表单Form1声明一个按钮对象
    Button Form1.BtnShowServerTime;

    //注册对象的事件处理函数
    public ServerTime()
    {
      Form1.BtnShowServerTime.onclick=BtnShowServerTime_Click;
    }

    private void BtnShowServerTime_Click()
    {
      //生成消息,该部分实际上在服务器上执行
      string msg=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

      //显示消息,该部分实际上在浏览器上执行
      window.alert(msg);
    }

  }
}

比较例2与例3中的事件处理过程,可以看到二者之间的明显差异:

   1. 在ASP.net中,表单中的所有数据都必须提交给服务器,当表单中数据项特别多或状态视图(StateView)数据量很大时,这种多余的数据传输将多消耗大量网络资源;而在单一环境Web编程模型中,编译器通过分析代码,将其分离为服务器执行部分与客户端执行部分,通过类似于AJAX技术的方式将服务器端代码必需的数据提交给服务器,并获得服务器生成的消息,在这过程中,没有非必需数据的传输,可以极大的节省网络资源。
   2. 在ASP.net中,客户端代码是通过字符串连接方式生成的,实际上编译只将其作为普通的字符串处理, 这样实际上就不能使用编译时的语法检查,失去了一个发现、修改错误的环节; 而在单一环境Web编程模型中,编译器可以对代码进行全面的语法检查。
   3. 在ASP.net 中,显示消息的方式不符合人类的思维习惯, 该过程被人为划分为客户端与服务器两个阶段,并且这种个阶段的代码交织在一起,另一方面当消息格式复杂时,生成正确语法脚本的难度也大大增加,降低了系统的健壮性;而在单一环境Web编程模型中,消息以一种自然的方式(类似于桌面编程的方式)向用户显示, 消息的内容也不会影响代码的正确性。
   4. 在单一环境Web编程模型中,界面描述部分是一个纯HTML页面,其中不含任何脚本代码, 实现了代码与界面的完全分离。

2.2 实现机制

通过上面示例的演示,本文设想单一环境Web编程模型的实现机制如下: 每一个Web表单由两部分构成,其中界面描述部分是一个纯HTML文件,该文件中通常不包含任何脚本代码(实际上允许嵌入脚本代码,但不鼓励这么做); 事件处理的代码部分全部位于代码文件中,该文件内容以类C#语法编写,并且将客户端与服务器的运行时环境视为一个整体,在代码中不用明确处理二者之间的差异。

为了更好的解释单一环境Web编程模型的实现机制,本文给出代码文件的设计示例, 后面的讨论基于这种文件格式进行。为了便于理解与对比,采用类C#语法描述,其基本结构如下:

import JavaScript.js;//被引用的JavaScript.js文件
using PackageName;//被引用的服务器端的命名空间或包

namespace Namespace
{
  class ClassName
  {
    //页面级变量声明段
    client data_type1 client_variable; //客户端变量
    server data_type2 server_variable; //服务器变量
    media data_type3 media_variable;  //中介变量

    client Button FormName.BtnOK; //用户界面元素

    //初始化函数
    Init()
    {
      //完成显示界面对象的事件与处理逻辑的映射关系
      FormName.BtnOK.onclick=BtnOK_OnClick; //映身一个点击事件处理逻辑
    }

    //事件处理函数
    void BtnOK_OnClick()
    {
      //完成访问数据库、调用其他服务器端程序、以及改变显示界面等工作
    }

    //其他辅助函数
    data_type function_name(data_type1 var1, … )
    {
    }

  }
}

   1. 关键词import

      导入一个脚本文件,其功能类似于< script language="JavaScript" src="path.js"></script>,从而在该代码文件中可以直接调用该脚本文件中声明的函数。每一个import语句都被编译为一个脚本引用块,形如:<script language="JavaScript " src="path.js"></script>。
   2. 关键词using

      允许在命名空间中使用类型,以便在命名空间中使用类型而不必指定命名空间, 其意义与C#中的using指令及JAVA中的import相同。
   3. 关键词namespace

      声明该类所属的命名空间或包,其意义与C#中的namespace及JAVA中的package相同。
   4. 关键词class

      声明一个类,其含义与C#及Java中的class类似。不同之处在于,此处声明的“类”可分为四种类型:一、类中没有服务器端变量与方法,且有客户端变量或方法,则该类为客户端类, 这种类型的class被编译为客户端脚本;二、类中没有客户端变量与方法,且有服务器端变量或方法,则该类为服务器端类, 这种类型的“类”被编译为服务器端类,如C#类或Java类等;三、类中既有服务器端变量或方法,也有客户端变量或方法,则该类属于混合类,这种类型的类中的一部分内容被编译为客户端脚本,另一部分内容被编译为Web服务,并且由编译器自动生成客户端脚本调用Web服务的代码以处理二者之间的交互。四、类中既没有服务器端变量或方法,也没有客户端变量或方法,则该类属于中性类, 这种类型的类被同时编译为客户端脚本和服务器端的代码。
   5. 关键词client、server、media

      这三个关键词均用于指定变量的性质,其中: client——声明一个仅用于客户端的变量,这类变量通常是显示界面的元素,也可能是ActiveX对象或简单数据类型变量,另外还包括默认的window、event、document、location、status等页面对象; server——声明一个仅用于服务器端的变量,这类变量通常是数据库连接对象、业务处理逻辑对象实例等, 也可能是简单数据类型变量,另外还包括默认的Request、Response、Session、Application、Server等服务器端对象; media——声明一个中介变量,这类变量用于在服务器与客户端变量之间交换信息, 其数据类型必须是简单数据类型,即char、integer、number、float、boolean、date-time等数据类型及其数组。

      为了减轻程序员的负担,这三个关键词是可以省略的。 在这种情况下,编译器根据变量的数据类型确定其性质,其中: 数据类型为界面对象或ActiveX对象的变量为client变量; 简单数据类型及其数组为media变量;其他均为server变量。
   6. 函数

      类中的函数根据其引用的变量和调用的函数的性质可以分为四类:一、客户端函数,引用了客户端变量或调用了客户端函数,但没有引用服务器变量且没有调用任何服务器函数,这类函数被编译为JavaScript脚本函数;二、服务器函数,引用了服务器变量或调用了服务器函数,但没有引用客户端变量且没有调用任何客户端函数, 这类函数被调编译为服务器端代码表示的函数;三、中性函数,没有引用服务器变量且没有调用服务器函数,也没有引用客户端变量且没有调用任何客户端函数,这类函数被同时编译为等价的JavaScript脚本函数和服务器端代码。 四、混合函数,除前面三类以外的其他函数,这类函数被编译为一个客户端的JavaScript函数和一个或多个的Web服务方法, 并由编译器自动添加代码处理它们之间的交互。

      6.1 初始化函数Init()

      该函数属于混合函数,其主要功能是完成显示界面元素的事件与处理逻辑的映射关系, 被编译器编译为一段直接执行的客户端脚本,插入在编译后的页面末尾, 即</body></html>之前。

      根据混合函数的定义,初始化函数也可以引用服务器端变量或调用服务器对象的方法, 但这种使用方式是不被鼓励的。

      6.2 事件处理函数

      该类函数属于混合函数,其主要功能是对显示界面上产生的事件做出响应, 完成界面的改变、业务逻辑的调用等。 该类函数总是被编译为一个客户端的JavaScript函数和一组Web服务方法(0个或多个)。

      6.3 辅助函数

      该类函数的性质根据函数分类规则,由编译器决定,并按照其性质进行编译。

处理完代码文件后,只需将生成的JavaScript块插入到界面描述文件的末尾,即</body></html>之前,其顺序为:import语句引入的脚本文件、页面全局变量的声明、辅助函数、事件处理函数、根据初始化函数生成的脚本代码, 最后将转换后生成的界面描述页面保存为一个静态html文件。
2.3 混合函数的编译规则

由于JavaScript、Java、C#语法的相似性,根据前面对单一环境Web编程模型中代码文件格式的描述, 可以发现中性函数、服务器端函数、以及客户端函数是非常容易编译的, 因此下面只讨论难度最大的任务,即对混合函数的编译。

根据定义可知,混合函数的调用总是由客户端发起的 (页面加载时、某个键盘/鼠标事件的发生、定时器到时等),且只能被客户端函数或混合函数调用。为了完成对混合函数的编译,首先需要对函数语句进行分段,使得每一段内都不同时包含客户端变量或函数和服务器端变量或方法。如例3-2中的事件处理函数BtnShowServerTime_Click为例,可以如下分段:

表1  混合函数分段
段类型 代码
客户端段(1) //空
服务器段 //生成消息,该部分实际上在服务器上执行
string msg=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
客户端段(2) //显示消息,该部分实际上在浏览器上执行
window.alert(msg);

为便于处理,假设函数中每一个语句都不同时包含客户端对象(变量与函数)和服务器端对象 (指变量与方法)── 通过引入更多的中间变量,总可以使函数满足这一假设。 这样我们总能如表1所示将一个函数划分为多个互不重迭的段,然后对混合函数进行以下处理:

   1. 生成一个与混合函数同名的JavaScript函数,其开始部分是所有media和client变量的声明;
   2. 将客户端段的代码直接复制到JavaScript函数中;并分析该段代码,获得被修改过的media变量清单, 在函数中加入将该清单中的变量“序列化”为一个字符串s的代码;
   3. 如果没有下一段,则结束,否则下一步;
   4. 在JavaScript函数插入调用Web服务的代码,其中参数为第2步生成的字符串s;
   5. 为服务器段生成一个Web服务的方法M,其接受的参数即为第2步生成的字符串s;
   6. 为Web服务的方法M生成还原media和server变量的代码,以及根据输入参数修改的media变量的代码;
   7. 将服务器段的代码复制到方法M中;并分析该段代码,获得被修改过的media变量清单, 在方法中加入将该清单中的变量“序列化”为一个字符串s的代码和暂存该方法中media和server变量的代码, 并将字符串s作为方法M的返回值;
   8. 在客户端JavaScript函数中加入Web服务调用结果处理的代码, 生成根据返回结果修改media变量值的代码;
   9. 如果没有下一段,则结束,否则转步骤2,继续处理后续的客户端段和服务器段。

在编译混合函数时,其他需要处理的问题主要有两个:

   1. 页面级的服务器端变量的暂存问题。一种可行的方案是,根据这些变量是否可序列化分别进行处理, 其中可序列化的变量暂存到在持久的存储介质(如磁盘)中,不可序列化的变量暂存到Session中。
   2. 过程级的服务器端变量的暂存问题。一种可行的方案是,仿照Session的管理方式,建立一个Transact管理机制,当一个事件的处理函数包含多于一个服务器段时,为了在段与段之间保持变量的值,可以将服务器端变量暂存到Transact中,该Tranact在第一个服务器段执行前建立,并在最后一个服务器段结束后销毁。

3. 总结

根据前文的描述可以知道,建立一个单一环境Web编程模型在技术上是可行的。 以该编程模型进行Web开发的优点主要有:

   1. 与桌面编程模型一致

      从前面的事件处理示例的描述可以看出,编译系统通过屏蔽客户端与服务器之间的差异, 为程序员建立了一个单一的“运行时环境”,因此采用单一环境Web编程模型进行Web开发时,其形式与用VB、C#、Java等进行桌面系统开发非常相似,程序员不用处理客户端与服务器环境的差异, 大大减轻了程序员的负担。
   2. 与传统的网页设计技术一致

      在单一环境Web编程模型中,界面描述文件是一个纯HTML文件, 开发人员只需掌握HTML页面设计技术即可,无需学习其他附加的东西;而代码文件实际上是JavaScript代码与C#/JAVA代码的混合体, 由于JAVA、JavaScript、C#三者语法的相似性,采用JavaScript、JavaScript、C#的开发人员无需学习新的语法规则就可直接以该模型进行编程,编写事件处理函数与编写一个常规的JavaScript函数一样容易。
   3. 实现了代码与页面的完全分离

      在单一环境Web编程模型中,界面描述文件是一个纯HTML文件,可以做到完全不包含任何脚本内容,所有的事件处理代码(无论是完全在客户端执行的还是需要客户端与服务器协同执行的) 都置于同一个代码文件中,从而做到了代码与页面的完全分离。这样做的好处是,页面设计人员与代码编写人员可以互不干扰的进行各自的设计,并且都可以完全控制自己处理的那部分工作,不用彼此担心无意中破坏了对方的工作。
   4. 真正的面向对象编程

      现在主流的浏览器都支持DHTML技术,而DHTML技术所使用的DOM模型(文档对象模型)本身就是一个非常好的对象体系。单一环境Web编程模型利用DOM作为其用户界面描述的对象模型, 从而完全继承了DOM的面向对象特性。基于此,每一个熟悉DHTML的编程人员不用学习新知识即已掌握单一环境Web编程模型中使用的对象模型。
   5. 融合Ajax与Web服务技术于无形

      Ajax 技术的优点在于可以只向服务器提交必要的信息,并且避免了页面的刷新; 而Web服务技术的优点在于只接收必要的输入参数,并返回处理的结果。于是,二者的结合可以最大限度的降低浏览器与服务器之间的数据转输量,并且服务器不用执行与显示相关的处理工作,从而最大限度的利用了浏览器的计算能力。在单一环境Web编程模型中,事件的处理函数通过编译器进行重新组织后,每次向服务器只提交必要的数据,并且将更新页面显示效果的代码完全移到了浏览器上执行,这样就以一种对应用程序员透明的方式实现了Ajax与Web服务的优点, 充分利用了客户端机器的计算能力。
   6. 支持一个页面上提供多个表单

      在ASP.net中,由于技术上的限制,一个页面只能包含一个服务器端的表单, 这种人为的限制非常不利于程序员对用户界面进行逻辑划分;但在单一环境Web编程模型中不存在类似的限制,一个页面上可以包含多个表单, 并且在一次事件处理过程中,可以同时访问多个表单中的对象。
   7. 学习代价小

      单一环境Web编程模型对应用程序员没有引入额外的编程规范,同时由于JavaScript、C#、Java语法的相似性,其代码语法可以采用类C#语法。因此,在这样一个环境中进行Web开发,编程人员不用学习其他额外的知识。 由于DHTML、JavaScript、C#(对.Net程序员)、 JAVA(对Java程序员)对每一个Web编程人员来说都是必须掌握的,与JSF/ASP.net相比较,采用单一环境Web编程模型进行开发,所必须学习的知识是最少的, 并且所学习的知识都是Web开发的基石 ── 变化少且是必须的, 而JSF/ASP.net中的那些服务器端控件和自定义控件或标签都是最终要还原为DHTML、JavaScript、C# (或Java)代码的,并不是Web开发中必不可少的。 Web开发人员花在学习非必要知识上的时间越少,用在真正有价值工作的时间才会越多。
   8. 提高了开发效率

      与已有的Web编程技术相比,在单一环境Web编程模型中, 客户端变量与服务器变量之间可以“直接”交换信息,服务器端代码不必通过串接字符串生成动态的JavaScript脚本(如例3-2所示),因此可以大大降低代码的编写难度,从而降低了代码中出现BUG的机会,提高了开发效率。

正所谓寸长尺短,单一环境Web编程模型在为应用程序员营造了一个虚拟的单一环境的同时也潜伏了一个“诱人”的陷阱,如果应用程序员在进行Web开发时没有意识到这个单一环境只是“虚拟的”而非与真正象桌面编程的运行环境一样,不清楚代码的实际运行环境和系统在后台完成的辅助工作, 也很有可能编写出如例4所示的低效代码。

例4  一段低效的单一环境Web编程模型代码

//table是通过数据库获取一个数据表单System.Data.DataTable变量
int j=table.rows.Count;
for ( i = 0; i < j; i++)
{
  //获得代码与名称
  string code = table.rows[i][0].ToString();
  string name = table.rows[i][1].ToString();

  //向下拉列表添加代码与名称对
  Option o = document.createElement("OPTION");
  Form1.ProductsList.options.add(o);
  o.value = code;
  o.text = code;
}

对例4中的代码,单一环境Web编程模型的编译器实际上会如表2所示对它进行分割。

表2  例4中代码的分段方式
段类型 代码
客户端段(1) //空
服务器段(1) //查询数据库获得DataTable
//获得DataTable中记录的行数
int j=table.rows.Count;
客户端段(2) for ( i = 0; i < j; i++)
{
服务器段(2) //获得代码与名称
string code = table.rows[i][0].ToString();
string name = table.rows[i][1].ToString();
客户端段(3) //向下拉列表添加代码与名称对
Option o = document.createElement("OPTION");
Form1.ProductsList.options.add(o);
o.value = code;
o.text = code;
}
服务器段(3) //空
客户端段(4) //空

其中最糟糕的部分在于服务器段(2)被嵌入在一个循环中,这在实际执行时将导致多次的Web服务调用,消耗大量的网络资源,并可能造成用户界面失去响应相当长一段时间。 因此为了提高系统效率,在一个事件处理函数中只应包含0或1个服务器段,即在一次事件处理中最多只发生一次Web服务调用。

涉及网络的编程从来不是一件轻松事,即使在单一环境Web编程模型中, 虽然程序员可以不处理客户端环境与服务器环境的区别及二者之间的交互,但仍然需要考虑网络对程序性能的影响。虽然良好的工具可以降低我们的工作强度,但复杂的事情永远都是复杂的事情,它所涉及的某些因素可以因为工具的进步被隐藏起来, 却永远不会消失。
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics