2006年03月25日

        1. 开发WAP软件需要哪些工具?
   为了开发WAP应用程序,需要一个WAP网关(注意:这里的网关可能是指支持WML的服务器。可以通过配置WWW服务器达到这个目的)和WAP工具包。工具包应当包括模拟器和能让开发者浏览WML网页。WML页面的开发和HTML页面的开发一样,可以使用Notepad或者其他文本编辑器来进行编辑。
  2. 有哪些公司现在提供这样的开发环境?
  Nokia、Ericsson、UpPhone和Motorola都提供免费的WAP网关和工具包。
  Nokia:Nokia Toolkit和Nokia WAP Server。
  Ericsson:Ericsson R320和WapIDE。
  UpPhone:UpPhone SDK。
  Motorola:Motorola ADK。
  3. 开发WAP应用一定要有WAP手机吗?
   不是,当开发WAP应用的时候,不一定需要WAP手机。模拟器可以帮助开发者解决大部分的问题。但是如果是开发商业网站,特别是想知道各种移动电话在显示WML页面上的差别的时候,最好是配备一个。目前各种手机对WML标记的支持和中文的支持状况大不相同,因此WAP手机还是必要的。
  4. 开发者需要一个WAP网关吗?
   不是很必要。如果只想进行简单的WAP内容服务,可以使用现有的Web服务器(只需要修改MIME类型)。移动电话会通过坐落在本地的网关连接到你的服务器上。
   但是在网关上驻留开发者的程序有很多好处。既然开发者的程序是网关的一个部分,开发者就可以知道呼叫号码、身份、位置等等。
  5. 可以看到WML的源代码么?
   如果开发者使用SDK浏览的时候将能够看到WML的代码。如果只有一个HTML浏览器,可以访问“Fetch Page”服务(
http://www.webcab.de)来取得代码。这个可以显示在Internet上的任何WML页面中。
  6. 可能在WML中加入applets吗?
   不能。
  7. 可以使用HTML开发工具来开发WAP应用吗?
   在大多数情况下开发工具是使用基于PC的浏览器。HTML、JavaScript和Java对于WAP开发来说都没有用处。但是,越来越多的开发工具在加入对WML的支持。
   Allair的Cold Fusion 4.5 和 HomeSite已经有WML支持,虽然Allair也许需要清除一些BUG。另外PHP和ASP在Coldfusion/HomeSite也能支持。
   可以到 Marjolei Katsma的 HomeSite Help site 上得到更多的信息。
  8. 可以通过WML页面来操作数据库吗?
   可以,与创建HTML页面相同。任何相关的服务器端的技术都可以用来生成WML页面。
  9. 可以使用CGI生成WML页面吗?
   当然。可以用创建HTML同样的方法来创建WML。如果想书写一个CGI来创建WML,只要记住在页面的开头正确设置MIME类型。具体的形式根据所使用的语言不同而不同。例如在Perl中:
  print ("Content-type:application/vnd.wap.wml \n\n\n"); 
   注意至少要使用2个换行。
  10. 如何使用Cold Fusion来生成页面?
   使用Cold Fusion只需要加上:
   <CFCONTENT type="text/vnd.wap.wml">
  11. 如何使用PHP来书写动态的WML页面?
   PHP(和大多数其他服务端脚本语言一样)可以被用来书写动态的WML内容。只需要将输出的标记限制在WML微型浏览器可接受的范围内。
   注意PHP有很多内建的HTML功能,特别是错误功能,这些功能WML微型浏览器可能无法识别。
   PHP同样可以在一个HTML文件中编写出既适合于HTML,也适合于WML的内容。PHP的源代码对于客户端来说是不可见的。因此可以针对HTML浏览器输出HTML页面,针对WML浏览器输出WML页面。
   可以在开发PHP编写的WML页面的时候把以下代码加在开头:
  <?
  // header("Content-type: text/vnd.wap.wml");
   echo("<?xml version=\"1.0\"?>\n");
  echo("<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\"
  //"
http://www.wapforum.org/DTD/wml_1.1.xml\">\n\n");
  ?>
   基于PC的浏览器将忽略这些无法理解的WML标记。但是如果想在WAP设备或者模拟器上测试的时候,只需要将"//"去掉,页面自动变成WML页面。
  12. 使用PHP动态输出WML
   这些例子生成一个非常有用的应用叫做:PizzaCalc。它将输入所有的pizza的帐单和人的数目,可以算出每个人的花费。
   应用生成一个动态的页面叫做“calc”或者“input”。注意到所有的转义字符例如双引号。该页显示了一个简单的变量处理,和如何传递参数到另外的卡片:
   使用WML浏览器就可以测试应用程序:
  
http://wap.colorline.no/wap-faq/apps/pizzacalc.html
   或者输入:
  
http://wap.colorline.no/demos.html选择应用。
  <?
  header("Content-type: text/vnd.wap.wml");
  echo("<?xml version=\"1.0\"?>\n");
  echo("<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\"
  \"
http://www.wapforum.org/DTD/wml_1.1.xml\">\n\n");
  echo("<!–The application PizzaCalc was originally made by The Crusaders
  
www.crusaders.no on the Commodore Amiga –>\n");
  echo("<!– It was unfortunately not possible to emulate the crap interger handling of the
  original program –>\n");
  ?>
  <wml>
  <?
   if($action == "calc") {
   echo("<card id=\"result\" title=\"PizzaCalc\">\n");
   echo("<do type=\"prev\" label=\"Back\">\n");
   echo("<go href=\"pizzacalc.html#input\"/>\n");
   echo("</do>\n");
   echo("<p>\n");
   echo("The cost per eater will be ".$total / $eaters."<br/>\n");
   }
   else {
   echo("<card id=\"input\" title=\"PizzaCalc\">\n");
   echo("<p>\n");
  echo("<anchor>Split Pizza bill
  <go href=\"pizzacalc.html?total=\$(total)&eaters=\$(eaters)&action=calc\"/>
  </anchor>\n");
   echo("<br/>\n");
   echo("Total cost: <input type=\"text\" name=\"total\" format=\"*N\"/>\n");
   echo("Eaters: <input type=\"text\" name=\"eaters\" format=\"*N\"/>\n");
   }
  ?>
  </p>
  </card>
  </wml>
  13. 可以使用Java Servlet来生成WML页面吗?
   当然。可以使用创建HTML同样的方法来创建WML。如果想书写一个CGI来创建WML,只要记住在页面的开头正确设置MIME类型:
   response.setContentType("text/vnd.wap.wml");
  14. 可以使用ASP、Perl等生成动态的应用吗?
   是的。可以使用任何服务器端的脚本语言来生成WAP应用。
  15. 如何使用ASP书写WML内容?
   ASP(Active Server Pages)可以做到和PHP一样,也可以用来书写动态的WML。如果需要一些好的例子请参考Luca Passani’s WAP and ASP articles。或者查看Jean-Luc Praz’s (
jeanluc@corobori.com)。更多的ASP例子在:http://www.corobori.com/wap/
  16. 在使用ASP动态输出WML页面的时候,已经设置了Content-type,但是浏览器返回的仍然是text/html,有什么问题吗?
   如果在ASP脚本中有一个错误,那么诊断程序会发还一个HTML页面,请检查脚本。
  17. 在使用ASP生成WML页面的时候出现了错误: <MIME type "text/html" is not supported>,会是什么问题?
   这个问题是Web浏览器不知道WML的正确类型,修改ASP的第一行,加入:
   <Response.ContentType = "text/vnd.wap.wml"> 
   后就可以工作了。
  18. 下面的代码有什么问题吗?
  <%Response.ContentType = "text/vnd.WAP.WML"%>
  <?xml version="1.0"?>
   去掉<?xml version="1.0"?>之前的空格。XML解释器希望在这行中没有其他字符,甚至是空行。
  19. ASP代码可以在模拟器上工作,在真正的浏览器上怎么不行?
   在很多模拟器上没有像真正的WML浏览器那么严格。这些对于那些没有使用网关的模拟器(Nokia SDK/Toolkit)来说更是这样,有些就根本没有使用网关(WinWAP、WapMAN)。
   一个真正的WML浏览器应该只读取二进制的数据(从WML编码得来的)WMLC,对于网关应该将文本WML转换/编译成WMLC。语法是非常严格的。ASP是为HTML浏览器设置的,但是HTML没有WML那么严格。
   这里在ASP生成动态页面的时候有一个微小的“bug”。它在WML浏览器上不允许有任何地方输出白行(例如:空格,回车,换行)。注意到有些网关可能会修正这些问题,但有的则不管(例如:CMG网关)。
   下面是一个常见的ASP代码用来输出WML页面开头的MIME类型:
  <%Response.ContentType = "text/vnd.wap.wml"%>
  <?xml version="1.0"?>
   问题就在ASP将会在 .wml"%> 和 <?xml vers 之间输出换行和回车。这两行就被分割了。这将打乱WML代码的内容。WML必须以“<”开头,而且第一行是<?xml version="1.0"?>。就上面的WML页面回车/换行将会出现问题。
   最简单的解决办法是:
   <%Response.ContentType = "text/vnd.wap.wml"%><?xml version="1.0"?>
   在XML定义正确的格式化以后,后面的部分WML对空格就没有那么严格的要求。
   要注意的是有些网关在输出ASP的时候会有问题,因此在WML代码中最好使用 Response.Write 而不是<%=MyVar%>。
  20. 如何使用Perl来生成WML内容?
   和其他Server端程序一样。Perl也可以用来书写漂亮的WAP应用程序。
   最常见的就是如何使用Perl输出正确的MIME类型,下面的例子说明了这一点:
  print "Content-type: text/vnd.wap.wml\n\n";
  print "<?xml version=\"1.0\"?>\n";
  print "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\"
  \"
http://www.wapforum.org/DTD/wml_1.1.xml\">\n";
  print "<wml>\n";
  ……
  21. 应当如何下手书写WAP应用程序?
   其实需要的只是Text编辑器。但是使用一个开发工具可以节约很多时间。
   在这之前应该浏览一下WAP的权威站点:
www.wapforum.com
   在Nokia WAP 开发论坛中进行注册,并且下载Nokia WAP Developer Toolkit 。Toolkit中的PDF文件可以给出一定的WML和WMLScript指导。Nokia Toolkit需要JRE (Java Runtime Environment) v.1.2.2 或者更高版本。
   虽然工具可以用来为WAP设备设计应用,但是不是为专门的移动电话。在WAP开发工具上所看到的并不代表用户在手机上所看到的。为了确定想看到的事情,最好需要一个WAP设备,例如移动电话,或者模拟器。
   Nokia WAP SDK 2有一个7110的模拟器。模拟器是一个有效的检测方式,能检测程序中的bug。 Nokia SDK 同样还包括一个小的WAP server让开发者可以从本地或者HTTP服务器上下载WML页面。
   到 Phone.com 开发站点注册后,Phone.com 提供UP.browser。这是最流行的浏览器,特别是在美国,Phone.com 提供UP.SDK。 在注册之后就可以下载。
   对于Ericsson R320 和 R380是最近的事情。应该注册并查看Ericsson’s Developer’s Zone 来得到开发工具。R380是一个非常好的模拟器,在 Symbian 不需要注册就可以下载。Ericsson 没有公开的为R320的模拟器。
   Motorola 有一个平台叫做 Mobile Internet eXchange 或者 MIX 。Mobile Application Development Kit 已经开发出一个开发平台,即为WAP也为Motorola的 VoxML。在注册后,可以在下面的网址找到数据包。
  
http://www.motorola.com/MIMS/MSPG/cgi-bin/spn_madk.cgi. 
   WAPmine 是一个独立的应用,叫做 WAPPage 是一个所见即所得的编辑工具。而且有一个XML树型控件来编辑WML标签。
   如果在开发公共应用程序时,想在很多设备上测试你的程序,就像在不同的浏览器上测试HTML页面一样。注意在不同的WML浏览器上的差别,可能比在不同的HTML浏览器上的差别要大。
  22. 如何编写和测试WML页面?
   现在有很多SDK。AnywhereYouGo.com有WAP SDK和IDE列表,可以下载一个来用。任何文本编辑器都可以书写一个简单的WML页面,当然HTML编辑器也可以(特别是那些支持个人定义标签的),例如:Allaire Homesite (
http://www.allaire.com )。可以使用SDK来做简单的测试,但是对于大的项目可能要困难些。AnywhereYouGo.com已经建立一套基于Web的工具来帮助WAP测试。
  23. 哪儿可以在找到WML的测试工具?
   首先确定WML代码是正确的,然后再使用WML测试工具。
   有一个非常好的测试工具在Zygo Communications(
http://wap.z-y-g-o.com/tools/),测试工具是用Perl写的。里面还有其他的工具可供下载。
  24. 如何操作WML页面?
   操作WML页面或者卡片,最简单的办法是通过现有的网关。大多数移动电话提供者将功能都放在主页上,在上面可以通过WAP设备操作。网关的链接一般叫做“Go to URL”。当选择以后,WAP设备将通过网关操作指定的普通IP或者URL。在这种情况下,网关读取从WAP设备发送给网关的WML内容,就像PC浏览器读取内容的过程一样。
   有些营运商选择不让他们的用户操作其他的站点。这个就像Internet Service Provider只允许用户操作ISP自己的站点。像这样的做法是不明智的,这样会发现自己的用户去其他地方了。
   如果要坚持这种方法,可以通过ISP拨号或者使用一个公共的网关来取得其他的WAP资源。
  25. 有没有一个友好的方式来管理WML内容?
   还没有。虽然Oracale正在开发数据库驱动的文档服务,被称为Panama,可以支持WAP分发。
  26. 如何防止用户代理cache页面?
   如果用户使用ASP,应该加入一行<%Response.expires=-1%> ,这个将阻止Cache。
  27. 怎样防止从Cache中读取WML页面?
   当WML页面下载到WAP设备后,它将保存在WAP设备内存中一段时间,直到这个时间过期。在这之后,页面将从服务器下载,而不是从WAP设备的缓存读取。这个过程被称做Cache。
   但是有些时候不想让页面从缓存中读取,而是从服务器端读取。一个典型的例子就是当服务器的内容不断在更新的时候,通过在HTTP头中加入一定的cache信息,来告诉WAP设备该页面将不存储在缓存中。
   可以在服务器端生成HTTP头,或者使用PHP、ASP、Perl或者其他服务端开发语言。这一行不能被包括在页面里,既然是HTTP的信息头,就不是WML元素。
   对于静态页面,或许没有使用服务器端脚本语言,许多浏览器支持META标签来控制浏览器的Cache。看本部分的最后的例子。
   将下面代码加入到HTTP头中,页面将马上过期:
  Expires: Mon, 26 Jul 1997 05:00:00 GMT
  Last-Modified: DD. month YYYY HH:MM:SS GMT
  Cache-Control: no-cache, must-revalidate
  Pragma: no-cache
   第一行告诉微型浏览器,页面已经过期一段时间了。第二行告诉浏览器页面最后一次修改的时间。DD应该换成当天的日期,month YY HH MM SS等等类推。第三行和第四行有同样的效果。告诉浏览器页面不被Cache(第三行适用于 HTTP 1.1,第四行适用于HTTP 1.0)。
   下面的是PHP的一个例子:
  <?
  // set the correct MIME type
   header("Content-type: text/vnd.wap.wml");
  // expires in the past
   header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  // Last modified, right now
   header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); 
  // Prevent caching, HTTP/1.1
   header("Cache-Control: no-cache, must-revalidate");
  // Prevent caching, HTTP/1.0
   header("Pragma: no-cache");
   ?>
   下面是使用WebClasses(VB)的例子。使用"Response.Expires=-1",防止Cache。
  Private Sub WebClass_Start()
   ‘Set correct MIME type
   Response.ContentType = "text/vnd.wap.wml"
  
   ‘Make sure no caching
   Response.Expires = -1
   Response.AddHeader "Pragma", "no-cache"
   Response.AddHeader "Cache-Control", "no-cache, must-revalidate"
  
   ‘Use basicwml(my own) as template
   Set NextItem = basicwml
   End Sub 
   这里有一个ASP的例子,同样使用“Response.Expires=-1”防止Cache。
  <%
   Response.ContentType = "text/vnd.wap.wml"
   Response.Expires = -1
   Response.AddHeader "Pragma", "no-cache"
   Response.AddHeader "Cache-Control", "no-cache, must-revalidate"
  %> 
   最后是使用META的例子:
  <?xml version="1.0"?>
  <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
  "
http://www.wapforum.org/DTD/wml_1.1.xml">
   <wml>
   <head>
   <meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
   </head>
   <card id="alwaysexpire">
   <p>This deck will never be stored in the cache</p>
   </card>
   </wml>
   下面的页面是在经过86400秒(24 hours)后过期。
  <?xml version="1.0"?>
  <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
  "
http://www.wapforum.org/DTD/wml_1.1.xml">
   <wml>
   <head>
   <meta forua="true" http-equiv="Cache-Control" content="max-age=86400"/>
   </head>
   <card id="expire1day">
   <p>This card will live in the cache for a day</p>
   </card>
   </wml>
   有些浏览器例如:UP.Simulator如果可以通过“返回”达到另外一个卡片,那么它将不会重新装载卡片。为了强制这个更新动作,用户必须在META标签中使用must-revalidate 参数。
   <meta forua="true" http-equiv="Cache-Control" content="must-revalidate"/>
  28. 如何防止变量被保存在Cache中?
   变量保存在Cache中,这样变量还可以再利用。例如当用户返回到上一个输入卡片,他不需要重新输入,只需要改变需要改变的地方。但是在某些情况下这会造成一些问题。例如以WAP聊天系统,有些变量用了一遍又一遍,但是需要不同的内容。有些浏览器,例如:Nokia 7110,就会存在类似的在该清除的时候无法清除的问题。
   在WML中,<card>标签有一个参数叫做newcontext。
   当newcontext="true" 时清除所有的变量。但是这样也清除了所有导航的历史记录,这意味着back按钮不再工作。
   为了清除变量,可以告诉浏览器将变量设为空:
  <setvar name="one_variable" value=""/>
  <setvar name="another_variable" value=""/>
   但是,不是每个时候都有效果。在某些情况下必须使用一个难以想象的方法来清空变量。就是使用 onenterforward 事件。
  <onevent type="onenterforward">
   <refresh>
   <setvar name="one_variable" value=""/>
   <setvar name="another_variable" value=""/>
   </refresh>
  </onevent>
  29. 怎么能够知道请求是从WML浏览器来的还是HTML浏览器来的?
   既然要利用已经存在的为HTML浏览器编写的代码,就需要知道请求是从HTML浏览器还是从WML浏览器过来的。同样地,如果想重新引导的HTML浏览器直接到相应的HTML文档上,WML浏览器到WML页面上,以下的PHP代码就可以做到这些。
  <?
  // Because this script sends out HTTP header information,
  // the first characters in the file must be the <? PHP tag.
  // relative URL to your HTML file
   $htmlredirect = "/html/my_htmlpage.html";
  // ABSOLUTE URL to your WML file 
   $wmlredirect = "
http://wap.mysite.com/wml/my_wmldeck.wml";
   if(strpos(strtoupper($HTTP_ACCEPT),"VND.WAP.WML") > 0)
  {// Check whether the browser/gateway says it accepts WML.
   $br = "WML";
   }
   else {
   $browser=substr(trim($HTTP_USER_AGENT),0,4);
   if($browser=="Noki" || // Nokia phones and emulators
   $browser=="Eric" || // Ericsson WAP phones and emulators
   $browser=="WapI" || // Ericsson WapIDE 2.0
   $browser=="MC21" || // Ericsson MC218
   $browser=="AUR " || // Ericsson R320
   $browser=="R380" || // Ericsson R380
   $browser=="UP.B" || // UP.Browser
   $browser=="WinW" || // WinWAP browser
   $browser=="UPG1" || // UP.SDK 4.0
   $browser=="upsi" || // another kind of UP.Browser ??
   $browser=="QWAP" || // unknown QWAPPER browser
   $browser=="Jigs" || // unknown JigSaw browser
   $browser=="Java" || // unknown Java based browser
   $browser=="Alca" || // unknown Alcatel-BE3 browser (UP based?)
   $browser=="MITS" || // unknown Mitsubishi browser
   $browser=="MOT-" || // unknown browser (UP based?)
   $browser=="My S" || // unknown Ericsson devkit browser ?
  $browser=="WAPJ" || // Virtual WAPJAG
www.wapjag.de
  $browser=="fetc" || // fetchpage.cgi Perl script from www.wapcab.de
  $browser=="ALAV" || // yet another unknown UP based browser ?
   $browser=="Wapa") // another unknown browser (Web based "Wapalyzer"?)
   {
   $br = "WML";
   }
   else {
   $br = "HTML";
   }
   }
   if($br == "WML") {
  // Force the browser to load the WML file instead
   header("302 Moved Temporarily");
   header("Location: ".$wmlredirect);
   exit;
   }
   else {
  // Force the browser to load the HTML file instead
   header("302 Moved Temporarily");
   header("Location: ".$htmlredirect);
   exit;
   }
   ?> 
   这个判断是在服务端完成的, PHP代码将首先查看网关是否接收text/vnd.wap.vml MIME类型。如果不是,将检测前面的字符,查看是否为WML浏览器。如果不符合,那么就假设为HTML浏览器。如果有新的WML浏览器,那么ID字符串也要增加。
   这个代码基于Robert Whitinger(
robert@wapsight.com)的代码,使用了Don Amaro(donamaro.concepcion@nl.unisys.com)提供的列表。
   注意:由于只需要四个字符串就可以辨别,因此例如:"WapIDE-SDK/2.0;(R320s(Arial))" 可以使用“WapI”来代替是可行的做法,也是足够的。
   同样的功能也可以通过ASP来解决。先判断请求的是“/index.wml” 或者 “/index.html” 和所需要的MIME类型。另外以下的脚本辨别的方式和上面不一样。另外还需要网关告诉服务器它能接收 的text/vnd.wap.wml MIME类型。该例子如下所示:
  <%
  Response.Buffer = TRUE
   Dim IsWap
   httpAccept = LCase(Request.ServerVariables("HTTP_ACCEPT"))
   if Instr(httpAccept,"wap") then
   IsWap=1
   Else Response.Redirect "/index.html" : Response.Flush : Response.End
  End if
  %>
  <%Response.ContentType = "text/vnd.wap.wml"%><?xml version="1.0"?>
  <%Response.Flush%>
  <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
  "
http://www.wapforum.org/DTD/wml_1.1.xml">
   <wml>
   <card id="redirect">
   <onevent type="onenterforward">
   <go href="/index.wml"/>
   </onevent>
   <p>
   <a href="/index.wml">enter</a>
   </p>
   </card>
   </wml>
   <%Response.Flush:Response.End%>
  30. 如何判断访问者是来自哪个浏览器或者移动电话?
   可以通过检查HTTP_USER_AGENT标签来判断。例如试着使用Microsoft Internet Explorer访问一个站点的时候,HTTP_USER_AGENT将返回:Mozilla/4.0 (compatible;MSIE 5.0; Windows 98);在同样的情况下使用Nokia 7110访问这个站点,HTTP_USER_AGENT就会是:Nokia7110/1.0(04.73)。据此可以判断用户代理是什么类型的。
  31. 可以得到用户代理的电话号码吗?
   不可以,除非网关支持这个特点,WAP没有办法知道用户的电话号码。
  32. 可以通过WML使得可以用WAP设备进行拨号吗?
   WAP的电话功能可以使用Wireless Telephony Application Interface(WTAI)。
   例如:
   WMLScript: WTAPublic.MakeCall("9287787"); 
   但是第一代的WAP设备不支持这个功能。
  33. 能够从WAP设备中读取数据吗,例如:电话号码?
   这里有一些通过HTTP的信息,但是十分有限。既然只有网关发送过来少量的信息,像WAP设备的号码可能无法读取。同时,在某些国家这还涉及到个人隐私的问题。
   基本上丢弃的内容就是WAP网关传送给HTTP服务器的内容。这不同于WAP网关到网关。Phone.com的UP.Link网关是一个最好的例子。因为它在HTTP头中返回一个字符串叫做 UP_X_SUBNO,里面包含了电话号码。Ericsson网关将传送一个辨别设备用的字符串,但是在明文中没有电话号码。
   每次WAP设备向HTTP服务器请求一个URL,WAP网关就会将信息传送给HTTP服务器。
   以下的PHP脚本显示了从网关过来的所有HTTP头的信息。可以使用WML浏览器进行测试。(
http://wap.colorline.no/clientinfo.html)。其他的例子也可以在下面的UTL中找到:http://wap.colorline.no/demos.html
   第一个部分是取得所有的标准HTTP头信息。第二个部分是提取一个内容。
  <?
   header("Content-type: text/vnd.wap.wml");
   echo("<?xml version=\"1.0\"?>\n");
  echo("<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\"
  \"
http://www.wapforum.org/DTD/wml_1.1.xml\">\n\n");
   echo("<!—Code written in Microsoft NOTEPAD.EXE à \n");
  ?>
  <wml>
   <card id="init" title="Client Info">
   <p>
   <?
   // First part – standard HTTP stuff
   $headers = getallheaders();
   while (list($header, $value) = each($headers)) {
   echo strtoupper($header). ": ". $value. "<br/>\n";       
   }
   // Second part
  // IP address of the client side
   echo("REMOTE_ADDR: ".$REMOTE_ADDR. "<br/>\n");
  // Port at the client side
   echo("REMOTE_PORT: ".$REMOTE_PORT. "<br/>\n");
  // Name of authenticated user
   echo("REMOTE_USER: ".$REMOTE_USER. "<br/>\n");
  // Gateway Interface type
   echo("GATEWAY_INTERFACE: ".$GATEWAY_INTERFACE. "<br/>\n");
  // Protocol used by the server
   echo("SERVER_PROTOCOL: ".$SERVER_PROTOCOL. "<br/>\n");
  // Request Method
   echo("REQUEST_METHOD: ".$REQUEST_METHOD. "<br/>\n");
  // Connection type
   echo("HTTP_CONNECTION: ".$HTTP_CONNECTION. "<br/>\n");
  // Host it connected via (proxy)
   echo("HTTP_VIA: ".$HTTP_VIA. "<br/>\n");
   ?>
   </p>
   </card>
  </wml> 
   Henrik Gemal (
gemal@dk.net)也有一个在线的基于WML的工具BrowserSpy,来显示更多关于HTTP头的信息、服务器环境和用户的浏览器等等。有关这个工具的详细情况可以浏览http://wap.gemal.dk/
   Werner Forkel 提交了一个Perl的脚本,可以显示电话号码(如果有)。可以在以下位置测试:http://wap.colorline.no/wap-faq/apps/subnotest.wml,同样也收集在:http://wap.colorline.no/demos.html.
   这些程序只适合某个网关。如果要测试其他的网关,可能就显示不出电话号码。因此电话号码不能作为ID号来处理。至少因为不是一个全球的标准。
  34. 有没有办法连接到电话号码?
   在某些情况下,当在显示了一连串的号码之后,需要中断功能连接到一个电话号码上并拨号。例如:执行一个 dial:12345678 就非常像 mailto: 标签。
   越来越多的浏览器都支持这个功能,但还不是所有。Phone.com, Mitsubishi 和 Ericsson 已经在浏览器中集成了这个基于Wireless Telephony Interface specifications (WTAI)的功能。 WTAI将允许以下的URL将关闭连接并且拨号:
   <go href="wtai://wp/mc;+4712345678">Make a call to +47-12345678</go> 
   Nokia 7110 已经有个功能叫做“Use Number”。它可以通过WML卡片查找一个类似于电话号码的列表,然后用户可以选择进行呼叫。注意用户必须分离这些数字以便它能正常工作。
  35. 使用GET或者POST方式能传送多少字符?
   使用GET或者POST方式所能传送的字符数目,不同的设备有不同的限制。一个GET通过UTL传送变量,能传送的数据总量比使用POST方式所能传送的数据要小。例如,Nokia 7110似乎对每个GET 限制在512个字节左右,但是POST最大可以达到一个编译后卡片的大小(约1300字节)。UP.SDK 4.0将GET请求限制在970左右,最大可以达到一个编译后卡片的大小。
   显然,卡片有时候保存了要发送给服务器的参数的内容,既然编译后的卡片大小有限制,那么肯定要影响到整个所能传输的数据。
   在POST和GET之间没有太多的区别。比如这个没有很好地使用GET的例子。
  <input type="text" name="var1" format="*N"/>
   <p>
   <anchor>Send it
   <go href="somescript.cgi?variable=$(var1)" method="get"/>
   </anchor>
   </p>
   下面仍然是一个使用GET的请求,但是使用了<postfield>来传送参数。这个代码就漂亮多了。既然可以定义为GET,同样也很容易转成POST。
  <input type="text" name="var1" format="*N"/>
   <p>
   <anchor>Send it
   <go href="somescript.cgi" method="get">
   <postfield name="variable" value="$(var1)"/>
   </go>
   </anchor>
   </p>
   直接改为POST:
  <input type="text" name="var1" format="*N"/>
   <p>
   <anchor>Send it
   <go href="somescript.cgi" method="post">
   <postfield name="variable" value="$(var1)"/>
   </go>
   </anchor>
   </p>
   最好是做测试找到到底能传输多少数据。这里有个测试程序:
  
http://wap.colorline.no/wap-faq/apps/putsize.php3
   这个程序也可以在下面的URL中找到:
http://wap.colorline.no/demos.html
   该程序将产生一个卡片包含一个变量,里面包含了一定数量的字符X。用户可以选择传输是使用GET还是POST。在传输之后,脚本将要显示接收到的字符个数。
   脚本生成一个页面来测试使用GET或者POST方式到底能发送多少个字符:
  <?xml version="1.0"?>
  <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
  "
http://www.wapforum.org/DTD/wml_1.1.xml">
  <wml>
   <head>
   <meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
   <meta forua="true" http-equiv="Cache-Control" content="must-revalidate"/>
   </head>
   <card>
   <do type="prev" label="Back">
   <go href="putsize.php3"/>
   </do>
   <p>
   <anchor>GET data
   <go method="get" href="putsize.php3">
   <postfield name=\"a\"
  value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
   </go>
   </anchor>
   </p>
   </card>
  </wml>
  36. 如何同HTML站点一样POST/CGI,返回表单数据到服务器?
   如果使用:
   <go method="post" href="mycgi.cgi"> 
   并且使用:
   <postfield name="fieldname" value="$NameOfInputField"/> 
   就可以POST数据给CGI程序了。
  37. POST无法工作是怎么回事?
   有很多说POST参数将会丢失的流言,特别是在Nokia 7110。就笔者所知,还没有哪个Nokia 7110有这样的问题。这个问题主要是存在于网关或接收方。
   测试显示Nokia SDK 2.18,当使用内建网关的时候,使用POST会出现问题。甚至当method 设置成“POST”的时候,服务器那边还是将POST请求作为 GET。
   当使用POST的URL时 ,Nokia SDK 将会崩溃。在某些情况下URL的最后的字符将被删除。
   POST Test页面将简单的POST的两个变量叫做“var1”和“var2”来显示整个变量的内容和HTTP头的内容。如果不能看到正确的变量内容,肯定有问题。检查HTTP头中的application/x-www-form-urlencoded(注意!需要在变量中输入一些内容)。
   这个方法解决了Nokia SDK 2.18的问题,可以把它配置到任意的公共网关来测试。笔者推荐使用 wapHQ 网关。
   在其他的情况下,POST确实不工作,问题可能是HTTP头在服务器端解释的时候有问题。脚本语言,例如:ASP、Java或是CGI等等都是通过查看在HTTP头中的 application/x-www-form-urlencoded 完全匹配的字符串。在某些情况下字符串可能有附加的数据,例如:charset="utf8" 。既然服务器端不是精确的匹配,它就不会查看HTTP头,因此POST就变量丢失了。
   注意这不是浏览器的问题,在HTTP头加入字符集描述,将造成脚本语言方面的错误。
   为了检测有关网关或浏览器的问题,仍使用上面的POST Test页面来测试。同样查看application/x-www-form-urlencoded 的输出,检查有没有附加的字符在结尾部分,如果有,那么这就是服务器端的问题。
   解决这个问题的方案很复杂,它随用户使用的脚本描述语言不同而不同,而且需要操作原代码。简单地说,解决方案就是需要人工读取HTTP头,不要使用脚本语言已经写好的读取方式。
   这里有一个用ASP编写的解决方法。它显示了如何在POST中抓取数据。用户需要从二进制数据中发现需要的变量。
  Dim lngToalByteCount
  Dim vntRequestData
   lngTotalByteCount = Request.TotalBytes
   vntRequestData = Request.BinaryRead(lngTotalByteCount)
   全部的代码,就应该像下面的代码:
  <%@ Language=VBScript %>
  <%
   Dim Temp, i, sPost, sWMLDeck
   ‘Converts the binary data to a string.
   For i = 1 To Request.TotalBytes
   Temp = Request.BinaryRead(1)
   sPost = sPost & Chr(AscB(Temp))
   Next
   ‘Parses out the values of the POSTED variables (in this
   ‘example myvar1 and myvar2).
   Dim sVar1, sVar2
   sVar1 = getVar("myvar1", sPost)
   sVar2 = getVar("myvar2", sPost)
   ‘Writes the WML Deck displaying the POSTED Variables
   sWMLDeck = "<?xml version=""1.0""?>" & vbCrLf
   sWMLDeck = sWMLDeck & "<!DOCTYPE wml PUBLIC ""-//WAPFORUM//DTD WML 1.1//EN"" "
   sWMLDeck = sWMLDeck & """
http://www.wapforum.org/DTD/wml_1.1.xml"">" & vbCrLf
   sWMLDeck = sWMLDeck & vbCrLf & "<wml>" & vbCrLf & vbTab
   sWMLDeck = sWMLDeck & "<card id=""main"" title=""POST TEST"">" & vbCrLf
   sWMLDeck = sWMLDeck & vbTab & vbTab & "<p>" & vbCrLf
   sWMLDeck = sWMLDeck & vbTab & vbTab & vbTab & "myVar1: " & sVar1 & "<br/>" & vbCrLf
   sWMLDeck = sWMLDeck & vbTab & vbTab & vbTab & "myVar2: " & sVar2 & vbCrLf
   sWMLDeck = sWMLDeck & vbTab & vbTab & "</p>" & vbCrLf & vbTab
   sWMLDeck = sWMLDeck & "</card>" & vbCrLf & ">/wml>"
   Response.ContentType = "text/vnd.wap.wml"
   Response.Write(sWMLDeck)
   ‘Quick function for picking out the values of the POSTed variables.
   ’sKey is the variable name, sRaw is the POST string.
   Private Function getVar(sKey, sRaw)
   Dim sRetVal
  If InStr(sRaw, sKey) Then
  sRetVal = Mid(sRaw, InStr(sRaw, sKey) + Len(sKey) + 1)
  If InStr(sRetVal, "&") Then
  sRetVal = Mid(sRetVal, 1, InStr(sRetVal, "&") – 1)
  End If
  End If
  getVar = sRetVal
   End Function
  %>
  38. 为什么META标签不工作?
   浏览器不支持默认的meta标签,例如:
   <meta http-equiv="refresh" content="1;http://somewhere.com/"> 
   虽然有少量网关支持非常有限的META标记。但是测试显示,如果使用了它们,网关就会出问题。例如某网关不支持普通的HTTP Cache控制,如果要实现Cache控制只好使用特殊的META标记。显然从其他网关来的用户就可能不支持这个META。注意:不要使用META tags。肯定有其他的方式来完成你的想法。
   最常使用的META是:
   <meta http-equiv="refresh" content="1;http://somewhere.com/"> 
   这个告诉浏览器重新装入指定的WML页面。WML中已经包含了一个<ontimer>。
  39. 为什么服务器接收不到用户发送的参数?
   用户输入的参数或者其他参数可以像在HTML中一样通过提交方式发送到服务器。在HTML中这个是<FORM>,POST或者GET。
   首先知道要知道POST和GET的区别。对于POST浏览器将生成一个数据包将变量名和它们的内容捆绑在一起,并发送到服务器。对于GET,它其实是一个URL请求,变量名和内容都包含在URL中。
   对于WAP环境,要求是非常严格的,必须要根据协议来操作。虽然以下的URL
   "/cgi-bin/somescript?username=john&telephone=123-123-1234&occupation=banana+bender"
   可以在HTML环境中工作,但是在WAP环境中无法工作。以上的部分编码将使得保护的变量内容被误解。特殊的空格(在 banana 和 bender )被转成 “+”。 URL就根本没有空格。
   以上的URL在WAP中无法工作的主要原因是用来分割每个变量和变量内容的 & (与号)没有转义。正确的格式应该是:
   "/cgi-bin/somescript?username=john&amp;telephone=123-123-1234&amp;occupation=banana+bender"
   在这里 & 被名字实体所替换。为了解释更清楚些,请看下面的代码:
  <card id="input" title="Gimme some data">
   <p><input type="text" name="username" format="M*m"/></p>
   <p><input type="text" name="occupation" format="M*m"/></p>
  <p>
  <anchor>Send this
  <go href="/cgi-bin/somescript?username=$(username)
  &amp;occupation=$(occupation)"/>
  </anchor>
  </p>
   注意这不是真正的WAP协议,专门的字符应该转义,否则将得到不可预料的结果。
  40. 为什么在HTTP中的Referer看不见?
   当HTML浏览器从一个URL到另外一个URL的时候,它默认地会发送一个叫做 Referer的 HTTP头给服务器,告诉它在浏览这个页面之前的那个页面。这是一个非常有用的特点,在WAP环境中同样也有。但是既然这个信息来自用户代理(浏览器)、WAP设备,通常为了节约带宽和时间,就被省略了。
   为了使用 Referer ,应该使用新的URL标签例如: <a>,<go>等等,并且加入参数:sendreferer。
   <go href="/somedir/somedeck.wml" sendreferer="true"/> 
   这样就会把参考的URL发送到服务器。
  41. 如果没有找到URL,有可能重新将用户引导到另外一个WML卡片或者页面吗?
   是的。但这是服务器端的特点,与客户端没有关系。
  42. 为什么普通的HTTP 302重新导向不好使?
   这的确是一个事实。核心的问题是在服务端的脚本语言,而不是在服务端语言和服务器之间。
   所谓的302 Found HTTP反应是指服务器告诉用户代理,它所需要的资源在另外的地方可以找到。302反应可能包括一个人们可理解的信息,如果在这种情况下“ Content-type: ”就被设置了。笔者所测试过的服务器,即使没有内容也都加了“Content-type:”。默认的 “Content-type:” 是text/html。当然有些网关不喜欢这个类型。
   以下的例子已经经过测试,在Apache和Microsoft Internet Information Server都可以工作。如果使用其他的Web Server,或者其他的脚本语言,需要能转换这些简单的脚本。关键的工作是十分简单的,除非需要,不用告诉服务器整个HTTP头。大多数Web Server将自动完成这个HTTP头,使得用户代理可以理解。
   所有的代码例子可以在线测试。如果它们能够工作,用户将被重新引导到
http://wap.colorline.no/clientinfo.html ,在那儿将产生一个WML页面来显示所有的HTTP头。
   PHP 代码测试可以在
http://wap.colorline.no/wap-faq/apps/302test.php3中找到。
  <?
   header("Location:
http://wap.colorline.no/clientinfo.html");
   header("Content-type: text/vnd.wap.wml";
  ?> 
   Perl测试代码可以在
http://wap.colorline.no/cgi-bin/302test.pl中找到。 
  print "Location:
http://wap.colorline.no/clientinfo.html\n";
  print "Content-type: text/vnd.wap.wml\n"; 
   ASP测试代码可以在
http://www.colorline.no/302test.asp中找到。 (注意不同的URL): 
  <%
   Response.Redirect = "
http://wap.colorline.no/clientinfo.html";
   Response.ContentType = "text/vnd.wap.wml";
   Response.Flush
   Response.End
  %>
  43. 可能在WML中实现ASP Session吗?
   不可能。可以把信息存储在临时变量中模拟Session。Session是保存在用户PC上的“cookies”中。目前的WAP设备不支持“cookies”。不过下一代的设备和WML可能支持“cookies”。
  44. WAP支持Session吗?
   在HTML中,一个十分普遍的“处理”用户的方法就是为每个用户分配一个“session”。这个有时候是通过指定一个独一无二的cookies来实现的。然而WAP的资源非常有限,因此session的处理必须以不同的方式来处理。
   Alex Kriegel 提供了一个安装在 WAPlinks的Custom Session Object包。这个zip文件中包含了VB类的文件和编译过的Dll文件,还有相关的文档。这些可以在
http://www.waplinks.com/customsessionobject.zip下载。
   另外一种方法是使用 PHPlib ,它是使用 PHP 编写的。
   Tarique (
tarique@nagpur.dot.net.in) 提供了如何使用PHPlib来验证和处理每个WAP用户。有相关的文件和注释可以到下面地址下载:
  
http://wap.colorline.no/wap-faq/archive/phplib_wml.zip
  45. 可以在WAP中使用Cookies吗?
   在理论上是可以的,但不是所有的WAP设备都支持。另一个方法来管理会话是使用隐藏的fields(包含会话标识,无论是POST或者GET方式)。
  46. WAP支持Cookies吗?
   普通的HTTP Cookies是作为WAP的扩展来实现的。无论你以前听到什么,Cookies的支持将越来越好。实际上Phone.com的 UP.Link网关已经支持这个功能有一段时间了。
   可以使用以下的脚本语言检测Cookie-support,:
  
http://wap.colorline.no/wap-faq/apps/cookietest.php3
   脚本在http://wap.colorline.no/demos.html也可以得到。
   当第一次看见卡片的时候,记数器应该为0。所有的Cache都被关闭。卡片通过在URL中随机地加入变量来强制自己加载(笔者不推荐这种强制加载办法)。当点击增加计数连接,页面将重新加载,卡片就再次出现,并且记数器变成1。
   在脚本中,Cookie的名字被称做 TestCookie,它有很长的生命期,因此可以隔好几天再来查看记数器,它将是上一次的数值。这要求你能使用与上一次访问所使用的WAP环境一样,否则将把你的数值清0。
   如果记数装置一直都是0,那么cookie 就没有能传送到你的Web Server。这个脚本也能表示Cookie是否被传送。
   另外,这个脚本同样还显示HTTP头中的HTTP_VIA 和 HTTP_USER_AGENT 。这些能够得到所使用的网关和模式。一些网关使用HTTP_VIS标识自己,而另外一些使用HTTP_USER_AGENT,还有一些则让程序无法知道。
   下面是它的PHP代码。一个标准的 PHP setcookie() 函数只有在这种脚本语言中才会出现。函数只是简单地设置cookie,并且PHP变量 $HTTP_COOKIE_VARS 用来读取cookie。
  <?
  if(isset($HTTP_COOKIE_VARS["TestCookie"]))
  {// Check if TestCookie is set
   $cookieset = "set";
  // Read the Cookie
   $cookieid = $HTTP_COOKIE_VARS["TestCookie"];
  // and increase its value
   $cookieid++;
    }
   else {
  // cookie was not set
   $cookieset = "not set";
  // start counter at zero
   $cookieid = 0;
   }
  // apply the Cookie to the HTTP header
  setcookie("TestCookie",$cookieid);
  // set the content type for WML
   header("Content-type: text/vnd.wap.wml");
  // disable ALL caching
   header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
   header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
   header("Cache-Control: no-cache, must-revalidate");          
   header("Pragma: no-cache");                                  
   echo("<?xml version=\"1.0\"?>\n");
  echo("<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\"
  \"
http://www.wapforum.org/DTD/wml_1.1.xml\">\n\n");
  echo("<!– This application attempts to test the capabilities of a WAP gateway to support     cookies –>\n");
   echo("<!– App by
Espen.Lyngaas@colorline.no (c) 2000 –>\n");
  // Generate random value for reload forcing
   $random = mt_rand(100000,999999);
  ?>
   <wml>
   <head>
  // Even more cache disabling
  <meta forua="true" http-equiv="Cache-Control" content="must-revalidate"/> 
   </head>
   <card id="init" title="CookieTest">
   <p>
   Cookie "TestCookie" was <?echo($cookieset)?>.
   Value is currently "<?echo($cookieid)?>"
   </p>
   <p>
  <anchor>
  Increase value
  <go method="get" href="<?echo($PHP_SELF)?>?random=<?echo($random)?>"/>
  </anchor>
  </p>
  <p>Gateway: 
   <?
   if(isset($HTTP_VIA))
  { // Is there something in the HTTP_VIA variable?
   echo($HTTP_VIA);
   }
   else {
   if(isset($HTTP_USER_AGENT))
  { // Is there something in the HTTP_USER_AGENT variable?
   echo($HTTP_USER_AGENT);
   }
   else {
  // Absolutely no identifier was found
   echo("Unknown");
   }
   }
   ?>
   </p>
   </card>
   </wml>
  47. 如何使用WAP设备发送E-Mail?
   在HTML中有一个默认的E-Mail机制:“ mailto:” 。但在WML中不好使,因此E-Mails必须通过WML表单来解决。例如:
  <wml>
    <card id="edit" title="Email Editor">
   <p>From: <input type="text" name="from" format="*M"/></p>
   <p>To: <input type="text" name="to" format="*M"/></p>
   <p>Subject: <input type="text" name="subject" format="*M"/></p>
   <p>Message body: <input type="text" name="body" format="*M"/></p>
   <p>
   <anchor>Send this mail
   <go method="post" href="
http://some.host/mailhandler"?action=send/">
   <postfield name="from" value="$(from)"/>
   <postfield name="to" value="$(to)"/>
   <postfield name="subject" value="$(subject)"/>
   <postfield name="body" value="$(body)"/>
   </go>
   </anchor>
   </p>
   </card>
  </wml> 
   在代码中的
http://some.host/mailhandler是一个CGI程序,它是服务端的脚本程序,将提交的表单转换成E-Mail格式并发送出去。
   如果想使用一个类似于发信的过程,就需要编辑变量名。另外发送的数据是有限的,长信息可能需要打断。
   为了演示它是如何工作的,下面的 PHP 脚本可以用来处理Mail。它将格式化WML页面,告诉用户是否发出信件。在真实的应用中,应该加入检测,例如:E-Mail的合法格式。
  <?
  // Tell the client that this is a WML deck
   header("Content-type: text/vnd.wap.wml");
   echo("<?xml version=\"1.0\"?>\n");
   echo("<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\"
  \"
http://www.wapforum.org/DTD/wml_1.1.xml\">\n");
  // The name of your mail server
   $mailer = "wap.colorline.no";
  // Format the from field
   $from = $from." (WAP user at ".$mailer.")";
  // Add the from field and some character handling to the extra headers
   $extraheaders = $from."\nContent-Type: text/plain;
  charset=iso-8859-1\nContent-Transfer-Encoding: 8bit";
  // Start sending out the WML deck
   echo("<wml>\n");
   if(mail($to,$subject,$body,$extraheaders))
  {// Use PHP’s internal mail functionality
  // Mail was successfully sent
   echo("<card id=\"sent\" title=\"Mail sent\">\n");
   echo("<p>Mail was sent successfully</p>\n");
   echo("</card>\n");
   }
   else {
  // The mail could not be sent
   echo("<card id=\"notsent\" title=\"Mail failed\">\n"); 
   echo("<p>Unable to send mail</p>\n");
   echo("</card>\n");
   }
   echo("</wml>\n");
  ?>
   因为安全性的原因,以上的代码没有演示程序。
  48. 可以在模拟器上操作本地的页面,却没有办法访问Internet上的,有什么问题吗?
   大多数模拟器和工具都可以浏览Internet、Intranet和本机的页面。但是在访问一些大公司页面的时候,必须通过代理服务器来取得进入Internet的权限。如果必须通过这个代理服务器来取得HTML页面,那么你的WAP模拟器也会取得权限来访问Internet。
   注意到有些模拟器不支持代理服务器,但是大多数是支持的。在模拟器里面设置这些是很简单的。用户所做的只需要将代理主机的名字、IP地址和端口号输入就可以了。如果没有找到,你可以在 systems/network 管理器里面设置这些,也可以检查 Netscape/IE的设置。
   在某些情况下,代理服务器使用 userid 和 password 来取得进入Internet的权限。有些模拟器支持用户代理服务器,用户应该能告诉模拟器相关的代理配置。
   在极少的情况下,使用代理服务器(如Microsoft Proxy Server,)的用户会遇到更多的问题。这个代理服务器只接受一种验证方式(userid/password)。这种验证被称做 NTLM ,并且是某种 MS 的验证方式。几乎很少有模拟器支持这种方式。因此最好是避免使用它,或者让管理员使用“Basic Authentication”方式以避免更多的麻烦。
  49. 什么是PUSHing,它是如何工作的?
   PUSH被加入到WAP 1.2,而且只在WAP 1.2中才存在。简单地来说,PUSH提供了另外一种从服务器向用户发送数据的方式。PULLing是从客户端请求信息,然后接收它;PUSH意味着服务器可以向用户发送数据,而不需要用户来请求。
   内容或者应用服务器无法向用户代理直接发送数据,必须使用一种叫做Push Proxy 的网关。PPG 是基于Internet的Push Initiator (内容或者应用服务器) 与移动用户之间的。在Internet一边,使用Push Access Protocol,在移动网络一边使用Push Over-the-Air Protocol。
   当前只有 WAP 1.2 开发平台支持 PUSH, 例如 Nokia Toolkit 2.0。 Nokia Toolkit 2.0 only 内部支持PUSHing,意味着用户可以从工具包的界面将消息推送到模拟器。如果想试着到一个外部的Push Proxy Gateway, 工具包就崩溃了。从readme文件中知道,PUSHing 还没有经过完整的测试。
  50. WAP模拟器说text/html不支持,但是用户的MIME设置是正确的,为什么?
   当使用服务端的脚本语言,例如ASP、PHP或者Perl,来生成WML输出,或者从HTTP服务器提供WML页面的时候。记住HTTP一般默认的显示是HTML,其MIME类型是text/html。
   如果HTTP服务器或者服务器脚本有错误,错误的信息将使用HTML显示,因此微型浏览器是不能显示错误信息的。
   一个开发工具/模拟器可以让用户看到从HTTP服务器过来的代码。例如,在Nokia SDK中,这个功能被称做View Source。通过看代码可以知道HTTP服务器到底发送了些什么内容。也可以使用普通的浏览器来查看任何HTML格式的错误信息。
  51. 在哪儿有Visio移动电话的模板库?
   目前唯一知道的就是它包含在 Nokia 7110 中。
  52. 有没有其他有用的WML内容服务列表?
   这里至少有一个。
   对于Unix用户,
http://pwot.co.uk/wml/中有Thomas Neill (ponder@pwot.co.uk)提供的WML工具,包括WML二进制编译和反编译。
   Angus 和 Zygo WAP(
angus@z-y-g-o.com)已经开发出了一个Perl工具包。它还在为管道式的WML编译器工作。
  53. XML到XSL的转换可以应用到WML和WAP吗?
   既然WML实际上是XML,并且XSL将WML转换成其他不同的XML文档,那么问题的答案是显然的:XSL也可以应用到WML。可以参考Luca Passani的文章《WebTechniques》。这个文章在网络上的地址是:
  
http://www.webtechniques.com/archives/2000/03/passani/
   它推荐看一下叫做《在 Apache下Cocoon计划的实现》这篇文章。“Cocoon 是一个依赖于新的W3C技术(例如DOM,XML,和XSL)框架。Cocoon计划在于改变Wen信息创建,生成和提供的方式。文档内容、风格和逻辑经常因为个人或者工作组的不同而不同。 Cocoon目标在于将这三层分离,允许三层次之间进行独立的设计,创建和管理,减少相互之间的影响,增加工作的可复用性以及缩短上市的时间。Web内容的产生大多数是基于HTML的,但是HTML并不能将三者分离开来,混合着各种格式标签,程序逻辑等等。而Cocoon计划将要改变这种情况,允许内容,逻辑和风格相互分离。使用XML来保存,但是使用XSL来将它们混合。”
   基本上来说,Cocoon将解读HTTP头,判断使用的是什么浏览器,然后使用不同的风格来选择正确的页面,使用XSL进行混合。
  54. 想让用户只要简单地按下一个按钮就能够转跳到其他卡片而不是通过选择URL,这个可能吗?
   不,不可能。
  55. 如何避免一个行的中断以便可以在一行中输入多个链接?
   在Nokia 7110中,不可能做到这一点,每个链接都占据自己的一行。

2006年03月21日

一. 引言

  性能测试与分析是软件开发过程中介于架构和调整的一个广泛并比较不容易理解的领域,更是一项较为复杂的活动。就像下棋游戏一样,有效的性能测试和分析只能在一个良好的计划策略和具备了对不可预料事件的处理能力的条件下顺利地完成。一个下棋高手赢得比赛靠的不仅仅是对游戏规则的认识,更是靠他的自己的能力和不断地专注于分析自己对手的实力来更加有效地利用和发挥规则的作用。同样一个优秀的性能测试和分析人员将要面对的是来自一个全新的应用程序和环境下带来的整个项目的挑战。本文中作者结合自己的使用经验和参考文档,对Tomcat性能方面的调整做一简要的介绍,并给出Tomcat性能的测试、分析和调整优化的一些方法。


二. 测量Web服务器的性能

  测量web服务器的性能是一项让人感到畏缩的任务,但是我们在这里将给出一些需要注意的地方并且指点你了解其中更多的细节性的内容。它不像一些简单的任务,如测量CPU的速率或者是测量程序占用CPU的比例,web服务器的性能优化中包括许调整许多变量来达到目标。许多的测量策略中都包含了一个看似简单的浏览实际上是在向服务器发送大量的请求,我们称之为客户端的程序,来测量响应时间。客户端和服务器端是在同一台机器上吗?服务器在测试的时候还运行着其它的什么程序吗?客户端和服务器端的通讯是通过局域网,100baseT,10baseT还是使用调制解调器?客户端是否一直重复请求相同的页面,还是随机地访问不同的页面?(这些影响到了服务缓存的性能)客户端发送请求的有规律的还是突发的?你是在最终的配置环境下运行服务的还是在调试的配置环境下运行服务的?客户端请求中包含图片还是只有HTML页面?是否有请求是通过servlets和JSP的,CGI程序,服务端包含(Server-Side Includes ,SSI是一个可以让你使用动态HTML文件的技术)?所有这些都将是我们要关心的,并且几乎我们不可能精确地把所有的问题都清楚地列出来。

  1.压力测试工具

  “工欲善其事,必先利其器”,压力测试只有借助于一些工具才可得以实施。

  大多数web压力测试工具的实现原理都是通过重复的大量的页面请求来模拟多用户对被测系统的并发访问,以此达到产生压力的目的。产生压力的手段都是通过录制或者是编写压力脚本,这些脚本以多个进程或者线程的形式在客户端运行,这样通过人为制造各种类型的压力,我们可以观察被测系统在各种压力状况下的表现,从而定位系统瓶颈,作为系统调优的基础。目前已经存在的性能测试工具林林总总,数量不下一百种,从单一的开放源码的免费小工具如 Aapache 自带的 web 性能测试工具 Apache Benchmark、开源的Jmeter 到大而全的商业性能测试软件如 Mercury 的 LoadRunner 等等。任何性能测试工具都有其优缺点,我们可以根据实际情况挑选用最合适的工具。您可以在这里找到一些web压力测试工具http://www.softwareqatest.com/qatweb1.html#LOAD

  这里我们所使用的工具要支持web应用服务认证才可以,要支持接收发送cookies,不仅如此Tomcat支持多种认证方式,比如基本认证、基于表单的认证、相互认证和客户端认证,而一些工具仅仅支持HTTP基本认证。真实地模拟用户认证是性能测试工具的一个重要的部分,因为认证机制将对一个web站点的性能特征产生重要的影响。基于你在产品中使用的不同的认证方式,你需要从上面的工具列表中选择使用这种特性的测试工具。

  Apache Benchmark和http_load是命令行形式的工具,非常易于使用。Apache Benchmark可以模仿单独的URL请求并且重复地执行,可以使用不同的命令行参数来控制执行迭代的次数,并发用户数等等。它的一个特点是可以周期性地打印出处理过程的信息,而其它工具只能给出一个全局的报告。

  2.压力测试工具介绍

  1) Apache Benchmark

  下面是运行Apache Benchmark的例子,响应时间非常长是因为它运行在一个配置非常低的系统上(Pentium 233)。在这里我们用它来访问一个URL,模拟127个并发用户重复执行1000次。


Root$ ab -k -n 1000 -c 127 -k http://tomcathost:8080/examples/date/date.jsp
This is ApacheBench, Version 2.0.36 <$Revision: 1.1 $> apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
Benchmarking tomcathost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Finished 1000 requests
Server Software: Apache
Server Hostname: tomcathost
Server Port: 8080
Document Path: /examples/date/date.jsp
Document Length: 701 bytes
Concurrency Level: 127
Time taken for tests: 53.162315 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Non-2xx responses: 1000
Keep-Alive requests: 0
Total transferred: 861000 bytes
HTML transferred: 701000 bytes
Requests per second: 18.81[#/sec] (mean)
Time per request: 6.752(mean)
Time per request: 0.053(mean, across all concurrent requests)
Transfer rate: 15.80>Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 51 387.5 0 2999
Processing: 63 6228 2058.4 6208 12072
Waiting: 17 4236 1855.2 3283 9193
Total: 64 6280 2065.0 6285 12072
Percentage of the requests served within a certain time (ms)
50% 6285
66% 6397
75% 6580
80% 9076
90% 9080
95% 9089
98% 9265
99% 12071
100% 12072 (longest request)

  2) Apache JMeter 中请求响应时间(图略)
  3) Mercury LoadRunner测试实时监控(图略)

  这三个工具是所有类似性能测试工具的典型代表,可以根据你自己的需要选择不同的测试工具。这里不对以上工具做详细的介绍,如果您对这些测试工具感兴趣的话可以参阅附加资料。

  3.性能评测技巧

  1) 由于评测时要用到系统时钟,所以当进行测试时不要运行无关的进程或程序,以免影响测试结果;

  2) 如果对自己的程序进行了修改,并试图改善它的性能,那么在修改前后应分别测试一下代码的执行时间;

  3) 尽量在完全一致的环境中进行每一次测试;

  4) 如果可能,应设计一个不依赖于任何用户输入的测试,测试中使用的数据应完全一致,避免用户的不同的反应或者数据的问题导致测试结果出现误差;

  5) 有可能您还需要考虑是在运行着的系统(包括操作系统和被测试的系统)上继续进行测试还是重新启动系统后再进行测试;

  6) 对于有些系统第一次使用可能要进行初始化,这种工作在系统使用过程中仅进行一次,所以有必要在重启系统后先进行一次初始化工作,然后进行性能测试。

三. 外部环境的调整

  在Tomcat和应用程序进行了压力测试后,如果您对应用程序的性能结果不太满意,就可以采取一些性能调整措施了,当然了前提是应用程序没有问题,我们这里只讲Tomcat的调整。由于Tomcat的运行依赖于JVM,所以在这里我们把Tomcat的调整可以分为两类来详细描述:

  外部环境调整

  调整非Tomcat组件,例如Tomcat运行的操作系统和运行Tomcat的java虚拟机。

  自身调整

  修改Tomcat自身的参数,调整Tomcat配置文件中的参数。

  下面我们将详细讲解外部环境调整的有关内容,Tomcat自身调整的内容将在第2部分中阐述。

  1.JAVA虚拟机性能优化

  Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个java虚拟机。您可以选择自己的需要选择不同的操作系统和对应的JDK的版本(只要是符合Sun发布的Java规范的),但我们推荐您使用Sun公司发布的JDK。确保您所使用的版本是最新的,因为Sun公司和其它一些公司一直在为提高性能而对java虚拟机做一些升级改进。一些报告显示JDK1.4在性能上比JDK1.3提高了将近10%到20%。

  可以给Java虚拟机设置使用的内存,但是如果你的选择不对的话,虚拟机不会补偿。可通过命令行的方式改变虚拟机使用内存的大小。如下表所示有两个参数用来设置虚拟机使用内存的大小。
参数
描述

-Xms<size>
JVM初始化堆的大小

-Xmx<size>
JVM堆的最大值


  这两个值的大小一般根据需要进行设置。初始化堆的大小执行了虚拟机在启动时向系统申请的内存的大小。一般而言,这个参数不重要。但是有的应用程序在大负载的情况下会急剧地占用更多的内存,此时这个参数就是显得非常重要,如果虚拟机启动时设置使用的内存比较小而在这种情况下有许多对象进行初始化,虚拟机就必须重复地增加内存来满足使用。由于这种原因,我们一般把-Xms和-Xmx设为一样大,而堆的最大值受限于系统使用的物理内存。一般使用数据量较大的应用程序会使用持久对象,内存使用有可能迅速地增长。当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃。因此一般建议堆的最大值设置为可用内存的最大值的80%。

  Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,需要调大。

  Windows下,在文件{tomcat_home}/bin/catalina.bat,Unix下,在文件{tomcat_home}/bin/catalina.sh的前面,增加如下设置:

  JAVA_OPTS=’-Xms【初始化内存大小】 -Xmx【可以使用的最大内存】’

  需要把这个两个参数值调大。例如:

  JAVA_OPTS=’-Xms256m -Xmx512m’

  表示初始化内存为256MB,可以使用的最大内存为512MB。

  另外需要考虑的是Java提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾可以接受的速度与应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。如果堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。如果你把堆的大小和内存的需要一致,完全收集就很快,但是会更加频繁。调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为保证最好的性能,要把堆的大小设大,保证垃圾收集不在整个基准测试的过程中出现。

  如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过 3-5 秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究 垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的 80% 作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。

  2.操作系统性能优化

  这里说的操作系统是指运行web服务器的系统软件,当然,不同的操作系统是为不同的目的而设计的。比如OpenBSD是面向安全的,因此在它的内核中有许多的限制来防止不同形式的服务攻击(OpenBSD的一句座右铭是“默认是最安全的”)。这些限制或许更多地用来运行活跃的web服务器。

  而我们常用的Linux操作系统的目标是易用使用,因此它有着更高的限制。使用BSD内核的系统都带有一个名为“Generic”的内核,表明所有的驱动器都静态地与之相连。这样就使系统易于使用,但是如果你要创建一个自定义的内核来加强其中某些限制,那就需要排除不需要的设备。Linux内核中的许多驱动都是动态地加载的。但是换而言之,内存现在变得越来越便宜,所以因为加载额外的设备驱动就显得不是很重要的。重要的是要有更多的内存,并且在服务器上腾出更多的可用内存。

  小提示:虽然现在内存已经相当的便宜,但还是尽量不要购买便宜的内存。那些有牌子的内存虽然是贵一点,但是从可靠性上来说,性价比会更高一些。

  如果是在Windows操作系统上使用Tomcat,那么最好选择服务器版本。因为在非服务器版本上,最终用户授权数或者操作系统本身所能承受的用户数、可用的网络连接数或其它方面的一些方面都是有限制的。并且基于安全性的考虑,必须经常给操作系统打上最新的补丁。

  3.Tomcat与其它web服务器整合使用

  虽然tomcat也可以作web服务器,但其处理静态html的速度比不上apache,且其作为web服务器的功能远不如apache,因此我们想把apache和tomcat集成起来,将html与jsp的功能部分进行明确分工,让tomcat只处理jsp部分,其它的由apache,IIS等这些web服务器处理,由此大大节省了tomcat有限的工作“线程”。

  4.负载均衡

  在负载均衡的思路下,多台服务器为对称方式,每台服务器都具有同等的地位,可以单独对外提供服务而无须其他服务器的辅助。通过负载分担技术,将外部发送来的请求按一定规则分配到对称结构中的某一台服务器上,而接收到请求的服务器都独立回应客户机的请求。

  提供服务的一组服务器组成了一个应用服务器集群(cluster),并对外提供一个统一的地址。当一个服务请求被发至该集群时,根据一定规则选择一台服务器,并将服务转定向给该服务器承担,即将负载进行均衡分摊。

  通过应用负载均衡技术,使应用服务超过了一台服务器只能为有限用户提供服务的限制,可以利用多台服务器同时为大量用户提供服务。当某台服务器出现故障时,负载均衡服务器会自动进行检测并停止将服务请求分发至该服务器,而由其他工作正常的服务器继续提供服务,从而保证了服务的可靠性。

  负载均衡实现的方式大概有四种:第一是通过DNS,但只能实现简单的轮流分配,不能处理故障,第二如果是基于MS IIS,Windows 2003 server本身就带了负载均衡服务,第三是硬件方式,通过交换机的功能或专门的负载均衡设备可以实现,第四种是软件方式,通过一台负载均衡服务器进行,上面安装软件。使用Apache Httpd Server做负载平衡器,Tomcat集群节点使用Tomcat就可以做到以上第四种方式。这种方式比较灵活,成本相对也较低。另外一个很大的优点就是可以根据应用的情况和服务器的情况采取一些策略。

四. 自身调整

  本节将向您详细介绍一些加速可使Tomcat实例加速运行的技巧和方法,无论是在什么操作系统或者何种Java虚拟机上。在有些情况下,您可能没有控制部署环境上的操作系统或者Java虚拟机。在这种情况下,您就需要逐行了解以下的的一些建议,然而你应该在修改后使之生效。我认为以下方法是Tomcat性能自身调整的最佳方式。

  1.禁用DNS查询

  当web应用程序向要记录客户端的信息时,它也会记录客户端的IP地址或者通过域名服务器查找机器名转换为IP地址。DNS查询需要占用网络,并且包括可能从很多很远的服务器或者不起作用的服务器上去获取对应的IP的过程,这样会消耗一定的时间。为了消除DNS查询对性能的影响我们可以关闭DNS查询,方式是修改server.xml文件中的enableLookups参数值:
Tomcat4

<Connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="80" minProcessors="5" maxProcessors="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" useURIValidationHack="false" disableUploadTimeout="true" />

Tomcat5

<Connector port="80" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" disableUploadTimeout="true"/>

  除非你需要连接到站点的每个HTTP客户端的机器名,否则我们建议在生产环境上关闭DNS查询功能。可以通过Tomcat以外的方式来获取机器名。这样不仅节省了网络带宽、查询时间和内存,而且更小的流量会使日志数据也会变得更少,显而易见也节省了硬盘空间。对流量较小的站点来说禁用DNS查询可能没有大流量站点的效果明显,但是此举仍不失为一良策。谁又见到一个低流量的网站一夜之间就流量大增呢?

  2.调整线程数

  另外一个可通过应用程序的连接器(Connector)进行性能控制的的参数是创建的处理请求的线程数。Tomcat使用线程池加速响应速度来处理请求。在Java中线程是程序运行时的路径,是在一个程序中与其它控制线程无关的、能够独立运行的代码段。它们共享相同的地址空间。多线程帮助程序员写出CPU最大利用率的高效程序,使空闲时间保持最低,从而接受更多的请求。

  Tomcat4中可以通过修改minProcessors和maxProcessors的值来控制线程数。这些值在安装后就已经设定为默认值并且是足够使用的,但是随着站点的扩容而改大这些值。minProcessors服务器启动时创建的处理请求的线程数应该足够处理一个小量的负载。也就是说,如果一天内每秒仅发生5次单击事件,并且每个请求任务处理需要1秒钟,那么预先设置线程数为5就足够了。但在你的站点访问量较大时就需要设置更大的线程数,指定为参数maxProcessors的值。maxProcessors的值也是有上限的,应防止流量不可控制(或者恶意的服务攻击),从而导致超出了虚拟机使用内存的大小。如果要加大并发连接数,应同时加大这两个参数。web server允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000个左右,Linux是1000个左右。

  在Tomcat5对这些参数进行了调整,请看下表:
属性名
描述

maxThreads
Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数。

acceptCount
指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。

connnectionTimeout
网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。

minSpareThreads
Tomcat初始化时创建的线程数。

maxSpareThreads
一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程。


  最好的方式是多设置几次并且进行测试,观察响应时间和内存使用情况。在不同的机器、操作系统或虚拟机组合的情况下可能会不同,而且并不是所有人的web站点的流量都是一样的,因此没有一刀切的方案来确定线程数的值。

  3.加速JSP编译速度

  当第一次访问一个JSP文件时,它会被转换为Java serverlet源码,接着被编译成Java字节码。你可以控制使用哪个编译器,默认情况下,Tomcat使用使用命令行javac进行使用的编译器。也可以使用更快的编译器,但是这里我们将介绍如何优化它们。

  另外一种方法是不要把所有的实现都使用JSP页面,而是使用一些不同的java模板引擎变量。显然这是一个跨越很大的决定,但是事实证明至少这种方法是只得研究的。如果你想了解更多有关在Tomcat可使用的模板语言,你可以参考Jason Hunter和William Crawford合著的《Java Servlet Programming 》一书(O’Reilly公司出版)。

  在Tomcat 4.0中可以使用流行而且免费的Jikes编译器。Jikes编译器的速度要由于Sun的Java编译器。首先要安装Jikes(可访问http://oss.software.ibm.com/pub/jikes 获得更多的信息),接着需要在环境变量中设置JIKESPATH包含系统运行时所需的JAR文件。装好Jikes以后还需要设置让JSP编译servlet使用Jikes,需要修改web.xml文件中jspCompilerPlugin的值:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>
org.apache.jasper.servlet.JspServlet
</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>WARNING</param-value>
</init-param>
<init-param>
<param-name>jspCompilerPlugin</param-name>
<param-value>
org.apache.jasper.compiler.JikesJavaCompiler
</param-value>
</init-param>
<init-param>
<!– <param-name>
org.apache.catalina.jsp_classpath
</param-name> –>
<param-name>classpath</param-name>
<param-value>
/usr/local/jdk1.3.1-linux/jre/lib/rt.jar:
/usr/local/lib/java/servletapi/servlet.ja
r</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>

  在Tomcat 4.1(或更高版本),JSP的编译由包含在Tomcat里面的Ant程序控制器直接执行。这听起来有一点点奇怪,但这正是Ant有意为之的一部分,有一个API文档指导开发者在没有启动一个新的JVM的情况下,使用Ant。这是使用Ant进行Java开发的一大优势。另外,这也意味着你现在能够在Ant中使用任何javac支持的编译方式,这里有一个关于Apache Ant使用手册的javac page列表。使用起来是容易的,因为你只需要在 元素中定义一个名字叫“compiler”,并且在value中有一个支持编译的编译器名字,示例如下:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>
org.apache.jasper.servlet.JspServlet
</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>WARNING</param-value>
</init-param>
<init-param>
<param-name>compiler</param-name>
<param-value>jikes</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>


Ant可用的编译器
名称
别名
调用的编译器

classic
javac1.1, javac1.2
Standard JDK 1.1/1.2 compiler

modern
javac1.3, javac1.4
Standard JDK 1.3/1.4 compiler

jikes
   The Jikes compiler

JVC Microsoft
Microsoft command-line compiler from the Microsoft SDK for Java/Visual J++

KJC    The kopi compiler

GCJ    The gcj compiler (included as part of gcc)

SJ Symantec
Symantec’s Java compiler

extJavac
   Runs either the modern or classic compiler in a JVM of its own


  由于JSP页面在第一次使用时已经被编译,那么你可能希望在更新新的jsp页面后马上对它进行编译。实际上,这个过程完全可以自动化,因为可以确认的是新的JSP页面在生产服务器和在测试服务器上的运行效果是一样的。

  在Tomcat4的bin目录下有一个名为jspc的脚本。它仅仅是运行翻译阶段,而不是编译阶段,使用它可以在当前目录生成Java源文件。它是调试JSP页面的一种有力的手段。

  可以通过浏览器访问再确认一下编译的结果。这样就确保了文件被转换成serverlet,被编译了可直接执行。这样也准确地模仿了真实用户访问JSP页面,可以看到给用户提供的功能。也抓紧这最后一刻修改出现的bug并且修改它J

  Tomcat提供了一种通过请求来编译JSP页面的功能。例如,你可以在浏览器地址栏中输入http://localhost:8080/examples/jsp/dates/date.jsp?jsp_precompile=true,这样Tomcat就会编译data.jsp而不是执行它。此举唾手可得,不失为一种检验页面正确性的捷径。

  4. 其它

  前面我们提到过操作系统通过一些限制手段来防止恶意的服务攻击,同样Tomcat也提供了防止恶意攻击或禁止某些机器访问的设置。

  Tomcat提供了两个参数供你配置:RemoteHostValve 和RemoteAddrValve。

  通过配置这两个参数,可以让你过滤来自请求的主机或IP地址,并允许或拒绝哪些主机/IP。与之类似的,在Apache的httpd文件里有对每个目录的允许/拒绝指定。

  例如你可以把Admin Web application设置成只允许本地访问,设置如下:
<Context path="/path/to/secret_files" …>
<Valve className="org.apache.catalina.valves.RemoteAddrValve"

allow="127.0.0.1" deny=""/>
</Context>

  如果没有给出允许主机的指定,那么与拒绝主机匹配的主机就会被拒绝,除此之外的都是允许的。与之类似,如果没有给出拒绝主机的指定,那么与允许主机匹配的主机就会被允许,除此之外的都是拒绝的。

五. 容量计划

  容量计划是在生产环境中使用Tomcat不得不提的提高性能的另一个重要的话题。如果你没有对预期的网络流量下的硬件和带宽做考虑的话那么无论你如何做配置修改和测试都无济于事。

  这里先对提及的容量计划作一个简要的定义:容量计划是指评估硬件、操作系统和网络带宽,确定应用服务的服务范围,寻求适合需求和软件特性的软硬件的一项活动。因此这里所说的软件不仅包括Tomcat,也包括与Tomcat结合使用的任何第三方web服务器软件。

  如果在购买软硬件或部署系统前你对容量计划一无所知,不知道现有的软硬件环境能够支撑多少的访问量,甚至更糟直到你已经交付并且在生产环境上部署产品后才意识到配置有问题时再进行变更可能为时已晚。此时只能增加硬件投入,增加硬盘容量甚至购买更好的服务器。如果事先做了容量计划那么就不会搞的如此焦头烂额了。

  我们这里只介绍与Tomcat相关的内容。

  首先为了确定Tomcat使用机器的容量计划,你应该从一下列表项目种着手研究和计划:

  1. 硬件

  采用什么样的硬件体系?需要多少台计算机?使用一个大型的,还是使用多台小型机?每个计算机上使用几个CPU?使用多少内存?使用什么样的存储设备,I/O的处理速度有什么要求?怎样维护这些计算机?不同的JVM在这些硬件上运行的效果如何(比如IBM AIX系统只能在其设计的硬件系统上运行)?

  2. 网络带宽

  带宽的使用极限是多少?web应用程序如何处理过多的请求?

  3. 服务端操作系统

  采用哪种操作系统作为站点服务器最好?在确定的操作系统上使用哪个JVM最好?例如,JVM在这种系统上是否支持本地多线程,对称多处理?哪种系统可使web服务器更快、更稳定,并且更便宜。是否支持多CPU?

  4. Tomcat容量计划

  以下介绍针对Tomcat做容量计划的步骤:

  1) 量化负载。如果站点已经建立并运行,可以使用前面介绍的工具模仿用户访问,确定资源的需求量。

  2) 针对测试结果或测试过程中进行分析。需要知道那些请求造成了负载过重或者使用过多的资源,并与其它请求做比较,这样就确定了系统的瓶颈所在。例如:如果servlet在查询数据库的步骤上耗用较长的时间,那么就需要考虑使用缓冲池来降低响应时间。

  3) 确定性能最低标准。例如,你不想让用户花20秒来等待结果页面的返回,也就是说甚至在达到访问量的极限时,用户等待的时间也不能超过20秒种(从点击链接到看到返第一条返回数据)。这个时间中包含了数据库查询时间和文件访问时间。同类产品性能在不同的公司可能有不同的标准,一般最好采取同行中的最低标准或对这个标准做出评估。

  4) 确定如何合理使用底层资源,并逐一进行测试。底层资源包括CPU、内存、存储器、带宽、操作系统、JVM等等。在各种生产环境上都按顺序进行部署和测试,观察是否符合需求。在测试Tomcat时尽量多采用几种JVM,并且调整JVM使用内存和Tomcat线程池的大小进行测试。同时为了达到资源充分合理稳定地使用的效果,还需针对测试过程中出现的硬件系统瓶颈进行处理确定合理的资源配置。这个过程最为复杂,而且一般由于没有可参考的值所以只能靠理论推断和经验总结。

  5) 如果通过第4步的反复测试如果达到了最优的组合,就可以在相同的生产环境上部署产品了。

  此外应牢记一定要文档化你的测试过程和结果,因为此后可能还会进行测试,这样就可以拿以前的测试结果做为参考。另外测试过程要反复多次进行,每次的条件可能都不一样,因此只有记录下来才能进行结果比较和最佳条件的选择。

  这样我们通过测试找到了最好的组合方式,各种资源得到了合理的配置,系统的性能得到了极大的提升。


六. 附加资料

  很显然本文也很难全面而详尽地阐述性能优化过程。如果你进行更多研究的话可能会把性能调优做的更好,比如Java程序的性能调整、操作系统的调整、各种复杂环境与应用系统和其它所有与应用程序相关的东西。在这里提供一些文中提到的一些资源、文中提到的相关内容的链接以及本文的一些参考资料。

  1. Web性能测试资料及工具

  1) Jmeter Wiki首页,Jmeter为一个开源的100%Java开发的性能测试工具
  http://wiki.apache.org/jakarta-jmeter/

  2) Apache Benchmark使用说明
  http://httpd.apache.org/docs-2.0/programs/ab.html

  3) 一些Java相关测试工具的介绍,包含可以与Tomcat集成进行测试的工具
  http://blog.csdn.net/wyingquan/

  4) LoadRunner® 是一种预测系统行为和性能的工业标准级负载测试工具。它通过模拟数据以千万计用户来实施并发负载来对整个企业架构进行测试,来帮助您更快的查找和发现问题。
  http://www.mercury.com/us/products/performance-center/loadrunner/


  2. 文中介绍的相关内容的介绍

  1) Apache 2.x + Tomcat 4.x做负载均衡,描述了如何利用jk配置集群的负载均衡。
  http://raibledesigns.com/tomcat/index.html

  2) 容量计划的制定,收集了许多有关制定web站点容量计划的例子:
  http://www.capacityplanning.com/

  3) 评测Tomcat5负载平衡与集群,
  http://www.javaresearch.org/article/showarticle.jsp?column=556&thread=19777

  4) Apache与Tomcat的安装与整合之整合篇
  http://www.javaresearch.org/article/showarticle.jsp?column=23&thread=18139

  5) 性能测试工具之研究,介绍了性能测试工具的原理与思路
  http://www.51testing.com/emagzine/no2_2.htm

  6) Java的内存泄漏
  http://www.matrix.org.cn/resource/article/409.html

  7) Web服务器和应用程序服务器有什么区别?
  http://www.matrix.org.cn/resource/article/1429.html

  8) 详细讲解性能中数据库集群的问题
  http://www.theserverside.com/articles/article.tss?l=db_break

2006年03月19日

Java(TM) Logging API 簡介


Cheng-Yu Chang

Medical Image Processing and Neural Network Lab

EE, NCKU, Taiwan 70101, ROC.

2002/09/17


寫在前面

一般我們在撰寫程式或開發系統時,debug 往往是免不了需要的。網路上也常常有人詢問到,哪種 debug 工具會比較好。通常,在傳統上我們都會選擇使用 System.out.println() 來幫助我們顯示一些 debug 用的資訊。不過在 JavaTM 2, Standard Edition (J2SETM) 1.4 版本中,java.util.logging 這個新加入的 package 為我們提供了一些 class interface 來協助我們產生 debug 用的資訊,方便我們診斷出不管是在系統開發或是系統管理上所發生的問題。


Logging API 的基本架構

那麼到底 Java Logging API 有何神奇的功能可以協助我們 debug 呢?讓我們先來看看傳統 debug 用的一個範例。

/**
 * @(#)TraditionalDebugDemo.java 2002/09/17
 *
 * 傳統 debug 方式的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */  

import java.io.*;

public class TraditionalDebugDemo {
       public static void main (String[] args) {
              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            System.out.println(args[i]);
                    }
              }
              else {
                   System.err.println("沒有輸入參數!");
              }
       }
}

從上面的範例可以看到,我們執行程式若是沒有輸入參數的話就透過 System.err.println("沒有輸入參數!");把錯誤的訊息產生,而若是有輸入參數的話就透過 System.out.println(args[i]);依序把內容印出來。其結果如下所示:

C:\>java TraditionalDebugDemo
沒有輸入參數!

C:\>java TraditionalDebugDemo 1 2 3 4
1
2
3
4

 

接下來,讓我們使用 Logging API 將這個範例重新改寫一下。


Logging API 中的 Logger

/**
 * @(#)LoggingDebugDemo.java 2002/09/17
 *
 * 使用 Logging API debug 的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */

import java.io.*;
import java.util.logging.*;

public class LoggingDebugDemo {
       private static Logger myLogger = Logger.getLogger("LoggingDebugDemo");

       public static void main (String[] args) {
              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            myLogger.info(args[i]);
                    }
              }
              else {
                   myLogger.warning("沒有輸入參數!");
              }
       }
}

當我們要使用 Logging API 的時候,我們必須要 import java.util.logging.*;這個套件。接著可以看到我們建立了一個 Logger 物件,這個物件主要是用來紀錄我們應用程式或是系統所發出來的訊息用的。接著可以看到我們使用 myLogger.info(args[i]);來顯示我們輸入的參數,使用 myLogger.warning("沒有輸入參數!");來顯示沒有輸入參數時候的錯誤訊息。倘若我們執行這個程式的話,我們可以看到以下的輸出畫面:

C:\>java LoggingDebugDemo
2002/9/17
上午 11:54:17 LoggingDebugDemo main
警告: 沒有輸入參數!

C:\>java LoggingDebugDemo 1 2 3 4
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 1
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 2
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 3
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 4

 

從上面的結果我們可以發現,當我們使用傳統的 System.out.println() 方式的話,除了程式開法者本身之外,並沒有辦法分辨目前訊息真正的涵義,也就是到底只是一般的訊息顯示,亦或是某個部分發生了錯誤。但是當我們使用 Logging API 來幫我們產生紀錄訊息的話,我們可以明顯的看到發生的時間、在哪個類別發生、在類別中的哪個 method 當中產生、以及產生什麼樣子的訊息類型都可以一目了然。

那麼,大家也許會問,為什麼我們想要顯示的訊息,前面會有「警告」或是「資訊」的字眼出現呢?其關鍵在於我們使用的 myLogger.info(args[i]);還有 myLogger.warning("沒有輸入參數!");這兩部分。在 Logging APILevel 類別當中定義了許許多多的不同程度的紀錄方式,分別如下列所示:


SEVERE 這是最高程度的紀錄方式,紀錄著非常重要的訊息(例如程式的錯誤)。
WARNING 表示一些警告訊息。
INFO 表示執行期間的一些訊息。
CONFIG 關於設定檔的一些訊息。
FINE 紀錄一些 debug 專用的詳細訊息。
FINER 再更進一步的詳細訊息。
FINEST 這是最低程度的紀錄方式,表示紀錄最詳細的訊息。
ALL 特殊情況,表示所有程度的訊息都應該被紀錄下來。
OFF 特殊情況,表示取消訊息的紀錄。

為什麼要定義這麼多種程度的訊息紀錄方式呢?其實這表示這些訊息的重要性,通常我們在開發系統,撰寫程式上,無庸置疑最重要的訊息一定就是有危險的錯誤訊息了,一旦發生錯誤訊息,可能程式執行到這個地方就因為某些原因就停止了,那麼這種程度的訊息不管如何就一定要顯示出來告訴我們。而其他例如 Level.FINE 這種程度的訊息,就表示它可有可無,並沒有代表很深刻的涵義,在這部分我們可以用下面的例子來說明:


Logging API 中的 Level

/**
 * @(#)LevelDebugDemo.java 2002/09/17
 *
 * 使用 Logging API LEVEL debug 的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */

import java.io.*;
import java.util.logging.*;

public class LevelDebugDemo {
       private static Logger myLogger = Logger.getLogger("LevelDebugDemo");

       public static void main (String[] args) {
              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            int j = i % 6;
                            switch(j) {
                                      case 0:
                                           myLogger.warning("WARNING Log!");
                                           break;
                                      case 1:
                                           myLogger.info("INFO Log!");
                                           break;
                                      case 2:
                                           myLogger.config("CONFIG Log!");
                                           break;
                                      case 3:
                                           myLogger.fine("FINE Log!");
                                           break;
                                      case 4:
                                           myLogger.finer("FINER Log!");
                                           break;
                                      case 5:
                                           myLogger.finest("FINEST Log!");
                                           break;
                                      default:
                                              break;
                            }
                    }
              }
              else {
                   myLogger.severe("沒有輸入參數!");
              }
       }
}

其執行結果如下所示:

C:\>java LevelDebugDemo
2002/9/17
下午 03:34:47 LevelDebugDemo main
嚴重的: 沒有輸入參數!

C:\>java LevelDebugDemo 1 2 3 4 5 6
2002/9/17
下午 03:35:02 LevelDebugDemo main
警告: WARNING Log!
2002/9/17
下午 03:35:03 LevelDebugDemo main
資訊: INFO Log!

 

相信大家看到上面的結果一定會覺得很奇怪,明明輸入了 6 個參數,照道理來說應該會有 6 項紀錄顯示出來才對,為什麼只有顯示兩種呢?其原因在於我們之前所提到的-「程度的不同」。在預設值當中,低於 Level.INFO 的值是不會被顯示出來的。所以我們只能看到 Level.INFO(資訊)程度的訊息。那麼,我們要怎麼改變程度呢?這個時候我們就要來看看 Logger API 實際上運作的情形了。當我們建立一個 Logger 物件之後,其最後的輸出是透過 Handler 類別來輸出的。

J2SE 提供了下列五種 Handler 類別讓我們來使用:


StreamHandler 將紀錄以 OutputStream 方式輸出。
ConsoleHandler 將紀錄以 System.err 方式輸出。
FileHandler 將紀錄導向一個檔案輸出。
SocketHandler 將紀錄傳送到遠端 TCP 連線輸出。
MemoryHandler 將紀錄暫存在記憶體當中。

現在我們將 LevelDebugDemo.java 改寫一下,加入 FileHandler 變成下面這樣:


Logging API 中的  Handler

 

/**
 * @(#)LevelDebugDemo2.java 2002/09/17
 *
 * 使用 Logging API LEVEL debug 的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */

import java.io.*;
import java.util.logging.*;

public class LevelDebugDemo2 {
       private static Logger myLogger = Logger.getLogger("LevelDebugDemo2");       

       public static void main (String[] args) {
              try {
                  FileHandler h = new FileHandler("%h/myLogger.txt");
                  myLogger.addHandler(h);
                  h.setFormatter(new SimpleFormatter());
              }
              catch(IOException e) {myLogger.severe("FileHandler Error!");}

              myLogger.setLevel(Level.ALL);

              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            int j = i % 6;
                            switch(j) {
                                      case 0:
                                           myLogger.warning("WARNING Log!");
                                           break;
                                      case 1:
                                           myLogger.info("INFO Log!");
                                           break;
                                      case 2:
                                           myLogger.config("CONFIG Log!");
                                           break;
                                      case 3:
                                           myLogger.fine("FINE Log!");
                                           break;
                                      case 4:
                                           myLogger.finer("FINER Log!");
                                           break;
                                      case 5:
                                           myLogger.finest("FINEST Log!");
                                           break;
                                      default:
                                              break;
                            }
                    }
              }
              else {
                   myLogger.severe("沒有輸入參數!");
              }
       }
}

其中 FileHandler %h 參數是表示 user.home(使用者根目錄)這項系統項目,其他常用的參數還有 %t%g 等等,分別表示「系統暫存目錄」與「檔案依序編號」的意思。舉例來說 "%t/myLogger%g.txt" 的話就表示會依序產生 myLogger0.txt, myLogger1.txt等檔案,其位置位於系統暫存目錄當中。當執行完這個程式之後,會發現在我們使用者的根目錄之下會多出一個 myLogger.txt的檔案,其內容如下所示:

2002/9/17 下午 04:38:31 LevelDebugDemo2 main
警告: WARNING Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
資訊: INFO Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
配置: CONFIG Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
細緻: FINE Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
更細緻: FINER Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
最細緻: FINEST Log!

這時候我們可以發現所有程度的訊息都完整的顯示出來了,這是因為我們把訊息顯示程度設定成為全部顯示myLogger.setLevel(Level.ALL);的緣故。在這邊我們要另外注意到的一件事情是,若是我們把 h.setFormatter(new SimpleFormatter()); 這行註解掉的話,我們可以發現檔案輸出變成下面這個樣子:

<?xml version="1.0" encoding="MS950" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2002-09-17T16:51:19</date>
<millis>1032252679457</millis>
<sequence>0</sequence>
<logger>LoggingDebugDemo</logger>
<level>WARNING</level>
<class>LevelDebugDemo2</class>
<method>main</method>
<thread>10</thread>
<message>WARNING Log!</message>
</record>
˙

˙[中間省略]

˙

<date>2002-09-17T16:51:19</date>
<millis>1032252679697</millis>
<sequence>5</sequence>
<logger>LoggingDebugDemo</logger>
<level>FINEST</level>
<class>LevelDebugDemo2</class>
<method>main</method>
<thread>10</thread>
<message>FINEST Log!</message>
</record>
</log>

這是因為 FileHandler 預設的輸出模式是 XML 的格式,J2SE Logging API 提供了兩種輸出模式,一種是 SimpleFormatter,也就是之前我們看到的那種模式。另外一種 XMLFotmatter的話就是像我們後來看到的這種 XML 架構樣式了。


總結

到目前為止我們簡單介紹了如何使用 JavaTM 2, Standard Edition (J2SETM) 1.4 版本中新增加的 Logging API,明白 Logging API 當中 Logger, Handler, Formatter所代表的意義(如下圖所示)。往後我們要紀錄系統開發資訊的話,可以紀錄的更加明確、有意義。

2006年03月11日

前段时间因为要参加一个笔试,在准备期间在网上找到了两条关于笔试题目的文章,其中一篇为<<有感:应聘Java笔试时可能出现问题>>,还有一篇忘了名字,读后深受启发。
在寻找这些答案的过程中,我将相关答案记录下来,就形成了以下这些东西。需要说明的是以下答案肯定有很多不完整甚至错误的地方,需要各位来更正与完善它,千万不要扔我的鸡蛋啊。
希望本文能够给即将奔赴笔试考场的同仁些许帮助,更希望更多的人加入到收集整理笔试题与完善答案的这些工作中来,为大家更好的获得工作机会做一点贡献。
在此感谢前面两文的作者的对笔试题目的收集与整理。
如有任何意见与建议请通过QQ:6045306,Mail:huijunzi@21cn.com与我联系。
Java基础方面:

1、作用域public,private,protected,以及不写时的区别
答:区别如下:
作用域 当前类 同一package 子孙类 其他package
public √ √ √ √
protected √ √ √ ×
friendly √ √ × ×
private √ × × ×
不写时默认为friendly

2、ArrayList和Vector的区别,HashMap和Hashtable的区别
答:就ArrayList与Vector主要从二方面来说.
一.同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的
二.数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
就HashMap与HashTable主要从三方面来说。
一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现
二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的
三.值:只有HashMap可以让你将空值作为一个表的条目的key或value

3、char型变量中能不能存贮一个中文汉字?为什么?
答:是能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的

4、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?
答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口
同步的实现方面有两种,分别是synchronized,wait与notify

5、继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么?
答:父类:
package test;
public class FatherClass
{
public FatherClass()
{
System.out.println("FatherClass Create");
}
}
子类:
package test;
import test.FatherClass;
public class ChildClass extends FatherClass
{
public ChildClass()
{
System.out.println("ChildClass Create");
}
public static void main(String[] args)
{
FatherClass fc = new FatherClass();
ChildClass cc = new ChildClass();
}
}
输出结果:
C:\>java test.ChildClass
FatherClass Create
FatherClass Create
ChildClass Create

6、内部类的实现方式?
答:示例代码如下:
package test;
public class OuterClass
{
private class InterClass
{
public InterClass()
{
System.out.println("InterClass Create");
}
}
public OuterClass()
{
InterClass ic = new InterClass();
System.out.println("OuterClass Create");
}
public static void main(String[] args)
{
OuterClass oc = new OuterClass();
}
}
输出结果:
C:\>java test/OuterClass
InterClass Create
OuterClass Create
再一个例题:
public class OuterClass {
private double d1 = 1.0;
//insert code here
}
You need to insert an inner class declaration at line 3. Which two inner class declarations are

valid?(Choose two.)
A. class InnerOne{
public static double methoda() {return d1;}
}
B. public class InnerOne{
static double methoda() {return d1;}
}
C. private class InnerOne{
double methoda() {return d1;}
}
D. static class InnerOne{
protected double methoda() {return d1;}
}
E. abstract class InnerOne{
public abstract double methoda();
}
说明如下:
一.静态内部类可以有静态成员,而非静态内部类则不能有静态成员。 故 A、B 错
二.静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;return d1 出错。

故 D 错
三.非静态内部类的非静态成员可以访问外部类的非静态变量。 故 C 正确
四.答案为C、E

7、垃圾回收机制,如何优化程序?
希望大家补上,谢谢

8、float型float f=3.4是否正确?
答:不正确。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4

9、介绍JAVA中的Collection FrameWork(包括如何写自己的数据结构)?
答:Collection FrameWork如下:
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)
Map提供key到value的映射

10、Java中异常处理机制,事件机制?

11、JAVA中的多形与继承?
希望大家补上,谢谢

12、抽象类与接口?
答:抽象类与接口都用于抽象,但是抽象类(JAVA中)可以有自己的部分实现,而接口则完全是一个标识(同时有多重继承的功能)。

13、Java 的通信编程,编程题(或问答),用JAVA SOCKET编程,读服务器几个字符,再写入本地显示?
答:Server端程序:
package test;
import java.net.*;
import java.io.*;

public class Server
{
private ServerSocket ss;
private Socket socket;
private BufferedReader in;
private PrintWriter out;
public Server()
{
try
{
ss=new ServerSocket(10000);
while(true)
{
socket = ss.accept();
String RemoteIP = socket.getInetAddress().getHostAddress();
String RemotePort = ":"+socket.getLocalPort();
System.out.println("A client come in!IP:"+RemoteIP+RemotePort);
in = new BufferedReader(new

InputStreamReader(socket.getInputStream()));
String line = in.readLine();
System.out.println("Cleint send is :" + line);
out = new PrintWriter(socket.getOutputStream(),true);
out.println("Your Message Received!");
out.close();
in.close();
socket.close();
}
}catch (IOException e)
{
out.println("wrong");
}
}
public static void main(String[] args)
{
new Server();
}
};
Client端程序:
package test;
import java.io.*;
import java.net.*;

public class Client
{
Socket socket;
BufferedReader in;
PrintWriter out;
public Client()
{
try
{
System.out.println("Try to Connect to 127.0.0.1:10000");
socket = new Socket("127.0.0.1",10000);
System.out.println("The Server Connected!");
System.out.println("Please enter some Character:");
BufferedReader line = new BufferedReader(new

InputStreamReader(System.in));
out = new PrintWriter(socket.getOutputStream(),true);
out.println(line.readLine());
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(in.readLine());
out.close();
in.close();
socket.close();
}catch(IOException e)
{
out.println("Wrong");
}
}
public static void main(String[] args)
{
new Client();
}
};

14、用JAVA实现一种排序,JAVA类实现序列化的方法(二种)? 如在COLLECTION框架中,实现比较要实现什么样的接口?
答:用插入法进行排序代码如下
package test;
import java.util.*;
class InsertSort
{
ArrayList al;
public InsertSort(int num,int mod)
{
al = new ArrayList(num);
Random rand = new Random();
System.out.println("The ArrayList Sort Before:");
for (int i=0;i<num ;i++ )
{
al.add(new Integer(Math.abs(rand.nextInt()) % mod + 1));
System.out.println("al["+i+"]="+al.get(i));
}
}
public void SortIt()
{
Integer tempInt;
int MaxSize=1;
for(int i=1;i<al.size();i++)
{
tempInt = (Integer)al.remove(i);
if(tempInt.intValue()>=((Integer)al.get(MaxSize-1)).intValue())
{
al.add(MaxSize,tempInt);
MaxSize++;
System.out.println(al.toString());
} else {
for (int j=0;j<MaxSize ;j++ )
{
if

(((Integer)al.get(j)).intValue()>=tempInt.intValue())
{
al.add(j,tempInt);
MaxSize++;
System.out.println(al.toString());
break;
}
}
}
}
System.out.println("The ArrayList Sort After:");
for(int i=0;i<al.size();i++)
{
System.out.println("al["+i+"]="+al.get(i));
}
}
public static void main(String[] args)
{
InsertSort is = new InsertSort(10,100);
is.SortIt();
}
}
JAVA类实现序例化的方法是实现java.io.Serializable接口
Collection框架中实现比较要实现Comparable 接口和 Comparator 接口

15、编程:编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。
答:代码如下:
package test;

class SplitString
{
String SplitStr;
int SplitByte;
public SplitString(String str,int bytes)
{
SplitStr=str;
SplitByte=bytes;
System.out.println("The String is:´"+SplitStr+"´;SplitBytes="+SplitByte);
}
public void SplitIt()
{
int loopCount;


loopCount=(SplitStr.length()%SplitByte==0)?(SplitStr.length()/SplitByte):(SplitStr.length()/Split

Byte+1);
System.out.println("Will Split into "+loopCount);
for (int i=1;i<=loopCount ;i++ )
{
if (i==loopCount){


System.out.println(SplitStr.substring((i-1)*SplitByte,SplitStr.length()));
} else {


System.out.println(SplitStr.substring((i-1)*SplitByte,(i*SplitByte)));
}
}
}
public static void main(String[] args)
{
SplitString ss = new SplitString("test中dd文dsaf中男大3443n中国43中国人

0ewldfls=103",4);
ss.SplitIt();
}
}

16、JAVA多线程编程。 用JAVA写一个多线程程序,如写四个线程,二个加1,二个对一个变量减一,输出。
希望大家补上,谢谢

17、STRING与STRINGBUFFER的区别。
答:STRING的长度是不可变的,STRINGBUFFER的长度是可变的。如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法

Jsp方面

1、jsp有哪些内置对象?作用分别是什么?
答:JSP共有以下9种基本内置组件(可与ASP的6种内部组件相对应):
 request 用户端请求,此请求会包含来自GET/POST请求的参数
response 网页传回用户端的回应
pageContext 网页的属性是在这里管理
session 与请求有关的会话期
application servlet 正在执行的内容
out 用来传送回应的输出
config servlet的构架部件
page JSP网页本身
exception 针对错误网页,未捕捉的例外

2、jsp有哪些动作?作用分别是什么?
答:JSP共有以下6种基本动作
jsp:include:在页面被请求的时候引入一个文件。
jsp:useBean:寻找或者实例化一个JavaBean。
jsp:setProperty:设置JavaBean的属性。
jsp:getProperty:输出某个JavaBean的属性。
jsp:forward:把请求转到一个新的页面。
jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记

3、JSP中动态INCLUDE与静态INCLUDE的区别?
答:动态INCLUDE用jsp:include动作实现
<jsp:include page="included.jsp" flush="true" />它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数
静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面
<%@ include file="included.htm" %>

4、两种跳转方式分别是什么?有什么区别?
答:有两种,分别为:
<jsp:include page="included.jsp" flush="true">
<jsp:forward page= "nextpage.jsp"/>
前者页面不会转向include所指的页面,只是显示该页的结果,主页面还是原来的页面。执行完后还会回来,相当于函数调用。并且可以带参数.后者完全转向新页面,不会再回来。相当于go to 语句。

Servlet方面

1、说一说Servlet的生命周期?
答:servlet有良好的生存期的定义,包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。

2、Servlet版本间(忘了问的是哪两个版本了)的不同?
希望大家补上,谢谢

3、JAVA SERVLET API中forward() 与redirect()的区别?
答:前者仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;后者则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。所以,前者更加高效,在前者可以满足需要时,尽量使用forward()方法,并且,这样也有助于隐藏实际的链接。在有些情况下,比如,需要跳转到一个其它服务器上的资源,则必须使用sendRedirect()方法。

4、Servlet的基本架构
public class ServletName extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
}
}

Jdbc、Jdo方面

1、可能会让你写一段Jdbc连Oracle的程序,并实现数据查询.
答:程序如下:
package hello.ant;
import java.sql.*;
public class jdbc
{
String dbUrl="jdbc:oracle:thin:@127.0.0.1:1521:orcl";
String theUser="admin";
String thePw="manager";
Connection c=null;
Statement conn;
ResultSet rs=null;
public jdbc()
{
try{
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
c = DriverManager.getConnection(dbUrl,theUser,thePw);
conn=c.createStatement();
}catch(Exception e){
e.printStackTrace();
}
}
public boolean executeUpdate(String sql)
{
try
{
conn.executeUpdate(sql);
return true;
}
catch (SQLException e)
{
e.printStackTrace();
return false;
}
}
public ResultSet executeQuery(String sql)
{
rs=null;
try
{
rs=conn.executeQuery(sql);
}
catch (SQLException e)
{
e.printStackTrace();
}
return rs;
}
public void close()
{
try
{
conn.close();
c.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
ResultSet rs;
jdbc conn = new jdbc();
rs=conn.executeQuery("select * from test");
try{
while (rs.next())
{
System.out.println(rs.getString("id"));
System.out.println(rs.getString("name"));
}
}catch(Exception e)
{
e.printStackTrace();
}
}
}

2、Class.forName的作用?为什么要用?
答:调用该访问返回一个以字符串指定类名的类的对象。

3、Jdo是什么?
答:JDO是Java对象持久化的新的规范,为java data object的简称,也是一个用于存取某种数据仓库中的对象的标准化API。JDO提供了透明的对象存储,因此对开发人员来说,存储数据对象完全不需要额外的代码(如JDBC API的使用)。这些繁琐的例行工作已经转移到JDO产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,JDO很灵活,因为它可以在任何数据底层上运行。JDBC只是面向关系数据库(RDBMS)JDO更通用,提供到任何数据底层的存储功能,比如关系数据库、文件、XML以及对象数据库(ODBMS)等等,使得应用可移植性更强。

4、在ORACLE大数据量下的分页解决方法。一般用截取ID方法,还有是三层嵌套方法。
答:一种分页方法
<%
int i=1;
int numPages=14;
String pages = request.getParameter("page") ;
int currentPage = 1;
currentPage=(pages==null)?(1):{Integer.parseInt(pages)}
sql = "select count(*) from tables";
ResultSet rs = DBLink.executeQuery(sql) ;
while(rs.next()) i = rs.getInt(1) ;
int intPageCount=1;
intPageCount=(i%numPages==0)?(i/numPages):(i/numPages+1);
int nextPage ;
int upPage;
nextPage = currentPage+1;
if (nextPage>=intPageCount) nextPage=intPageCount;
upPage = currentPage-1;
if (upPage<=1) upPage=1;
rs.close();
sql="select * from tables";
rs=DBLink.executeQuery(sql);
i=0;
while((i<numPages*(currentPage-1))&&rs.next()){i++;}
%>
//输出内容
//输出翻页连接
合计:<%=currentPage%>/<%=intPageCount%><a href="List.jsp?page=1">第一页</a><a

href="List.jsp?page=<%=upPage%>">上一页</a>
<%
for(int j=1;j<=intPageCount;j++){
if(currentPage!=j){
%>
<a href="list.jsp?page=<%=j%>">[<%=j%>]</a>
<%
}else{
out.println(j);
}
}
%>
<a href="List.jsp?page=<%=nextPage%>">下一页</a><a href="List.jsp?page=<%=intPageCount%>">最后页

</a>


Xml方面

1、xml有哪些解析技术?区别是什么?
答:有DOM,SAX,STAX等
DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问
STAX:Streaming API for XML (StAX)

2、你在项目中用到了xml技术的哪些方面?如何实现的?
答:用到了数据存贮,信息配置两方面。在做数据交换平台时,将不能数据源的数据组装成XML文件,然后将XML文件压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再同XML文件中还原相关信息进行处理。在做软件配置时,利用XML可以很方便的进行,软件的各种配置参数都存贮在XML文件中。

3、用jdom解析xml文件时如何解决中文问题?如何解析?
答:看如下代码,用编码方式加以解决
package test;
import java.io.*;
public class DOMTest
{
private String inFile = "c:\\people.xml";
private String outFile = "c:\\people.xml";
public static void main(String args[])
{
new DOMTest();
}
public DOMTest()
{
try
{
javax.xml.parsers.DocumentBuilder builder =


javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder();
org.w3c.dom.Document doc = builder.newDocument();
org.w3c.dom.Element root = doc.createElement("老师");
org.w3c.dom.Element wang = doc.createElement("王");
org.w3c.dom.Element liu = doc.createElement("刘");
wang.appendChild(doc.createTextNode("我是王老师"));
root.appendChild(wang);
doc.appendChild(root);
javax.xml.transform.Transformer transformer =
javax.xml.transform.TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "gb2312");
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");


transformer.transform(new javax.xml.transform.dom.DOMSource(doc),
new

javax.xml.transform.stream.StreamResult(outFile));
}
catch (Exception e)
{
System.out.println (e.getMessage());
}
}
}

4、编程用JAVA解析XML的方式.
答:用SAX方式解析XML,XML文件如下:
<?xml version="1.0" encoding="gb2312"?>
<person>
<name>王小明</name>
<college>信息学院</college>
<telephone>6258113</telephone>
<notes>男,1955年生,博士,95年调入海南大学</notes>
</person>
事件回调类SAXHandler.java
import java.io.*;
import java.util.Hashtable;
import org.xml.sax.*;
public class SAXHandler extends HandlerBase
{
private Hashtable table = new Hashtable();
private String currentElement = null;
private String currentValue = null;
public void setTable(Hashtable table)
{
this.table = table;
}
public Hashtable getTable()
{
return table;
}
public void startElement(String tag, AttributeList attrs)
throws SAXException
{
currentElement = tag;
}
public void characters(char[] ch, int start, int length)
throws SAXException
{
currentValue = new String(ch, start, length);
}
public void endElement(String name) throws SAXException
{
if (currentElement.equals(name))
table.put(currentElement, currentValue);
}
}
JSP内容显示源码,SaxXml.jsp:
<HTML>
<HEAD>
<TITLE>剖析XML文件people.xml</TITLE>
</HEAD>
<BODY>
<%@ page errorPage="ErrPage.jsp"
contentType="text/html;charset=GB2312" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Hashtable" %>
<%@ page import="org.w3c.dom.*" %>
<%@ page import="org.xml.sax.*" %>
<%@ page import="javax.xml.parsers.SAXParserFactory" %>
<%@ page import="javax.xml.parsers.SAXParser" %>
<%@ page import="SAXHandler" %>
<%
File file = new File("c:\\people.xml");
FileReader reader = new FileReader(file);
Parser parser;
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
SAXHandler handler = new SAXHandler();
sp.parse(new InputSource(reader), handler);
Hashtable hashTable = handler.getTable();
out.println("<TABLE BORDER=2><CAPTION>教师信息表</CAPTION>");
out.println("<TR><TD>姓名</TD>" + "<TD>" +
(String)hashTable.get(new String("name")) + "</TD></TR>");
out.println("<TR><TD>学院</TD>" + "<TD>" +
(String)hashTable.get(new String("college"))+"</TD></TR>");
out.println("<TR><TD>电话</TD>" + "<TD>" +
(String)hashTable.get(new String("telephone")) + "</TD></TR>");
out.println("<TR><TD>备注</TD>" + "<TD>" +
(String)hashTable.get(new String("notes")) + "</TD></TR>");
out.println("</TABLE>");
%>
</BODY>
</HTML>

EJB方面

1、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别?
答:规范内容包括Bean提供者,应用程序装配者,EJB容器,EJB配置工具,EJB服务提供者,系统管理员。这里面,EJB容器是EJB之所以能够运行的核心。EJB容器管理着EJB的创建,撤消,激活,去活,与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC,JMS…..

2、EJB与JAVA BEAN的区别?
答:Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。

3、EJB的基本架构
答:一个EJB包括三个部分:
Remote Interface 接口的代码
package Beans;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Add extends EJBObject
{
//some method declare
}
Home Interface 接口的代码
package Beans;
import java.rmi.RemoteException;
import jaax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface AddHome extends EJBHome
{
//some method declare
}
EJB类的代码
package Beans;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javx.ejb.SessionContext;
public class AddBean Implements SessionBean
{
//some method declare
}

J2EE,MVC方面

1、MVC的各个部分都有那些技术来实现?如何实现?
答:MVC是Model-View-Controller的简写。"Model" 代表的是应用的业务逻辑(通过JavaBean,EJB组件实现), "View" 是应用的表示面(由JSP页面产生),"Controller" 是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。

2、应用服务器与WEB SERVER的区别?
希望大家补上,谢谢


3、J2EE是什么?
答:Je22是Sun公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企业级应用模型(enterpriese application model).在这样的一个应用系统中,可按照功能划分为不同的组件,这些组件又可在不同计算机上,并且处于相应的层次(tier)中。所属层次包括客户层(clietn tier)组件,web层和组件,Business层和组件,企业信息系统(EIS)层。

4、WEB SERVICE名词解释。JSWDL开发包的介绍。JAXP、JAXM的解释。SOAP、UDDI,WSDL解释。
答:Web Service描述语言WSDL
SOAP即简单对象访问协议(Simple Object Access Protocol),它是用于交换XML编码信息的轻量级协议。
UDDI 的目的是为电子商务建立标准;UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web Service注册,以使别的企业能够发现的访问协议的实现标准。


5、BS与CS的联系与区别。
希望大家补上,谢谢

6、STRUTS的应用(如STRUTS架构)
答:Struts是采用Java Servlet/JavaServer Pages技术,开发Web应用程序的开放源码的framework。 采用Struts能开发出基于MVC(Model-View-Controller)设计模式的应用构架。 Struts有如下的主要功能:
一.包含一个controller servlet,能将用户的请求发送到相应的Action对象。
二.JSP自由tag库,并且在controller servlet中提供关联支持,帮助开发员创建交互式表单应用。
三.提供了一系列实用对象:XML处理、通过Java reflection APIs自动处理JavaBeans属性、国际化的提示和消息。

设计模式方面

1、开发中都用到了那些设计模式?用在什么场合?
答:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。


2、UML方面
答:标准建模语言UML。用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图,合作图),实现图,

JavaScript方面

1、如何校验数字型?
var re=/^\d{1,8}$|\.\d{1,2}$/;
var str=document.form1.all(i).value;
var r=str.match(re);
if (r==null)
{
sign=-4;
break;
}
else{
document.form1.all(i).value=parseFloat(str);
}


CORBA方面

1、CORBA是什么?用途是什么?
答:CORBA 标准是公共对象请求代理结构(Common Object Request Broker Architecture),由对象管理组织 (Object Management Group,缩写为 OMG)标准化。它的组成是接口定义语言(IDL), 语言绑定(binding:也译为联编)和允许应用程序间互操作的协议。 其目的为:
用不同的程序设计语言书写
在不同的进程中运行
为不同的操作系统开发


LINUX方面

1、LINUX下线程,GDI类的解释。
答:LINUX实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。
GDI类为图像设备编程接口类库。

2005年09月20日

1990-1994:Java缘起
文/孟岩

Larry Wall说,优秀程序员应有的三个特点:懒惰、急躁和傲慢。Java就是诞生在一群懒惰
、急躁而傲慢的程序天才之中。
1990年12月,Sun的工程师Patrick Naughton被当时糟糕的Sun C++工具折磨的快疯了。他
大声抱怨,并威胁要离开Sun转投当时在Steve Jobs领导之下的NeXT公司。领导层为了留住
他,给他一个机会,启动了一个叫做Stealth(秘密行动)的项目。随着James Gosling等
人的加入,这个项目更名为Green。其目标是使用C++为嵌入式设备开发一种新的基础平台
技术,James Gosling本人负责开发一个SGML编辑器。正如人们事后分析的那样,这位天才
的程序员太懒惰,所以没有把C++学好,开发中碰了一头包;太急躁——所以不愿意停下来
读读Scott Meyers的新书《Effective C++》;太傲慢——所以轻易地决定开发一中新的编
程语言。他把这种语言命名为C++++–,意思是C++“加上一些好东西,减去一些坏东西”
。显然这个糟糕的名字不可能长命百岁,很快这种颇受同伴喜爱的小语言被命名为Oak。

到了1992年9月,Oak语言连同Green OS和一些应用程序一起发布在称做Start 7的小设备上
,从而使之有了第一次精彩的亮相。随后,Sun开了一家名为FirstPerson的公司,整个团
队被转移到这家公司里研发机顶盒,以投标时代华纳公司的一个项目。这帮天才被技术狂
热所鼓舞,开发出了一个高交互性的设备,结果没想到时代华纳公司和有线电视服务商并
不愿意用户拥有那么大的控制权,从而在竞标之战中败给了SGI。Oak的锋芒之锐,竟然把
客户都给吓懵了。Sun沮丧地关闭了FirstPerson,召回了整个团队。事实证明,传统行业
中那些脑满肥肠的保守主义者是腐朽没落的。回去!回到激情澎湃的IT产业,抓住互联网
的大潮,这才是出路!1994年,Oak被命名为Java,针对互联网的新一轮开发如火如荼,一
切已经就绪,熔岩在地下奔流,火山即将喷发。


1995: Java香浓世界
文/马伟

1995年,Sun正式对外公布了Java,并且发布了JDK 1.0。这种外形酷似C++,却包含一颗S
malltalk般纯洁的面向对象之心的全新程序设计语言及其平台,几乎在一夜之间就成为软
件产业的新宠儿。Java当时仅仅被用来为网站制作一些动态应用,诸如动画图片之类,但这
仍然引起了很多Web开发者们的注意,他们非常渴望有一种安全的语言,可以在静态的HTM
L网页上制作动画图片。Sun最终把Java集成到NetScape浏览器。同时因为它具有“只写一
次,随处运行”的特性,而引起了很多开发者的注意,他们可以再也不用为了使程序能够
在不同型号的硬件上运行而耗费大量的时间来编译代码了。
当时的Web浏览器的出现也为Java的出现起到了很好的推动作用,通过Java和Web浏览器的
结合,人们似乎看到了什么,有人甚至预言PC将在一两年内退出历史的舞台,取而代之的
是基于Java的浏览器应用程序,通过网络计算设备来进行应用。Java的出现为当时的软件
产业带来了无限的遐想。


1996:Java大跃进,盟主地位就此定
文/马伟

SUN在1996年一开始首先成立了JavaSoft组织,并在1月23日正式发布自己的Java 1.0,作
为20世纪业界出现的最重要的技术之一,Java引起了编程世界的革命。直到现在,Java仍
然是互联网上最流行的语言。
在Sun正式发布Java 1.0之后,Java这门新生的语言就拥有了自己的会议——JavaOne,这
次会议初试啼音就吸引了600多名参与者。除了拥有这么多的积极参与者来进行Java的开发
之外,各大知名公司也纷纷向Sun申请Java的许可。一时间,NetScape、惠普、IBM、Oral
ce、Sybase甚至当时刚推出Windows 95的微软都是Java的追随者。
Java的应用就像是世界上的顶级玩家们组成的一个公开联盟,告诉全世界我们大家就是都
在用着Java。也正是因为如此,Java也找到了自己的归宿。现在的J2EE已经成为中大型企
业级应用的标准,成为承接数据库和Web之间的一个重要桥梁。
当年Java的机会实在太多了,以至于很难知道到底该做什么。最终Java在应用服务器市场
获得了难以取代的地位,也确定了J2EE的发展方向,并且仍将延续下去。


1997-2001:  微软与Sun的Java官司
文/孟岩

Java诞生的1995年,正是微软在软件产业地位达到巅峰的时代,Windows 95发布时的风光
场面给人们留下的深刻印象至今难忘。尽管如此,作为最卓越的技术领袖,比尔?盖茨仍然
敏锐地注意到Java。当他了解了Java的一些细节之后,给予了这样的评价:“Java是很长时
间以来最优秀的程序设计语言。”基于此,微软于1996年3月申请并获得了Java许可证。微
软对于Java的这一热情态度在当时大大提高了人们对Java的兴趣和信心,但也有不少人担
心微软会依靠自己强大的影响力在标准之外另立标准,从而破坏Java的纯洁性。
果然,从1997年发布Visual J++的第一个版本开始,微软就开始在Java中掺入自己的私有
扩展。这毫无疑问引起Sun的高度重视。1997年10月,Sun向美国加州地方法院起诉微软公
司违反两公司就微软使用Java技术所签定的合同,指控微软公司在自己的Java产品中做了
“不恰当的修改”,违反了合同中承诺向用户提供Java兼容产品的条款。这一官司旷日持
久,直到2001年1月双方达成和解,微软将继续提供采用Sun开发的Java技术的现有产品(
包括测试版)。不过,Sun有限制地仅对包括Java 1.1.4的微软产品提供许可。到了2001年
7月,微软公布新版的Windows XP将不再支持Sun的JVM,并且推出了.NET平台与Java分庭抗
礼。
现在回过头去看,当时的这一场官司对Java世界产生了深远的影响。如果没有这一场官司
,也许很多Java程序员都在使用Visual J++,基于WFC开发Windows客户端程序,同时不得
不面对被两个不同的事实标准所分裂的Java世界。


1998:Java 2平台发布
文/陶文

1998年,Java 2平台正式发布。经过了三年时间的发展、热热闹闹的攻关宣传、红红火火
的众厂商的热情参与,Sun终于知道Java适合干什么了。对比Java刚发明时的技术定位,与
Java的戏剧性触“网”的那段历史,Java 2平台的发布可真算得上是有的放矢了。根据官
方的文档,Java 2是Sun意识到“one size doesn’t fit all”之后,把最初的Java技术
打包成三个版本的产物,也就是著名的J2ME、J2SE、J2EE。
之所以说Java自从Java 2平台发布之后,进入了现代。那是因为之前的历史怎么看来都和
现在程序员日常开发使用的技术无什么关系,比如Applet,已经很少有人使用了。Java 2
之后的历史就不一样了,至少人们在推崇轻量级开发,猛批EJB时还不时会引用J2EE这个词
是如何诞生的。而Java 2的三大版本中,除了J2EE得到了长足发展和广泛使用之外,J2ME
也在手机市场上取得了遍地开花的结果。相较之下,J2SE难免落寞,只剩SWT这个血统不纯
的家伙在Rich Client回归的时代吸引着人们的眼球了。无论今天看来当时的Java 2有多么
的不成熟,至少经过市场和时间的检验,Java 2规划出来的三大方向把Java技术指向了光
明的方向是勿庸置疑的。


1998:JCP成立并正式运作,
Java开源社群开始蓬勃发展
文/黄海波

1998年,JCP组织成立,并且开始把握Java的发展方向。JCP组织的开放性,不但使得所有
对Java感兴趣的商业公司可以参与Java的发展,更重要的是JCP允许个人、非盈利组织、学
校等加入,这就给Java带来了巨大的活力。随之兴起的Java开源运动的最大贡献是实现和
鼓励了知识共享,在众多热情的开源程序员们的努力和分享下,很多原先只被商业公司掌
握的技术、思想和产品可以被所有需要的开发人员免费或者以较低的价格获得使用权, 并
通过开放源代码更容易的获得反馈和改进意见从而进一步演化发展。我们知道,所谓知识
不是孤立发展认知,而是人们的经验,认识是思考交流和积累的产物。而开源运动所带来
的开放、反馈、交流的风气正是符合人类社会知识形成和发展的规律。
开源运动起源于西方的发达国家,有其现实背景和文化根源。1990年代可以说是IT产业的
一个黄金时代。信息时代的兴起对IT人员,特别是软件人员有着巨大的需求。而软件开发
又是一种类似艺术创作的脑力活动,和所有的艺术家、作家们一样,在作品打上自己的印
记并流传在世界上是每一个创作人员的梦想。互联网时代下的高收入的舒适生活,早九晚
五的编写公司的代码并不能满足很多有激情的软件开发人员的梦想,再加上西方传统的基
督教文化中十分推崇的分享和交流,开源的出现和兴起也就水到渠成了。今天,开源运动
已经不仅仅是一些个人天才程序员们的游乐园地,而是发展成为一项开源软件产业。


1998:WebLogic打开J2EE的魔匣
文/霍泰稳

Java语言的出现使得互联网络有了良好的交互性能,但这些很“酷”的技术仅被人们认为
是一些小花招,它还无法消除企业级用户对它的怀疑。1998年,BEA公司宣布收购WebLogi
c公司,并接着推出由Sun公司第一个授权使用J2EE许可证的WebLogic Server应用服务器,
这个Java版的AppServer一推出就引起业界极大的兴趣。WebLoigc Server以其对标准的支
持、强悍的运算能力和安全的架构设计等特性也很快征服了那些怀疑J2EE应用的人们。推
出市场后不到一年,WebLogic Server就成为业内第一Java应用服务器。
这里我们援引一些当时著名咨询公司的调查数据来说明问题,“在IDC的报告中,BEA在应
用服务器和交易服务器领域市场份额第一;在Gartner的报告中,BEA WebLogic Server拥
有业内最广泛的EJB应用安装基础;在Giga Group的报告中,BEA WebLogic Server市场份
额占32%”。
因为应用服务器市场极大的发展潜力,在WebLogic Server之后,其它的很多公司也推出了
自己的AppServer,如IBM的WebSphere、Sun公司的iPlanet等,逐渐地应用服务器取代了传
统意义上的各类中间件,成为企业应用的基础平台。应用服务器的出现使得Java有了真正
意义上的发展。
 

2002-2004: Sun与微软的法律碰撞最终以喜剧收场
文/恶魔

2003年4月2 日,Sun与微软达成16亿美元的法律和解。如果不是晚了一天,许多人会以为
这是一个在4月1日愚人节开的玩笑。尽管当时所有人都像是看到“太阳从西边出来了”那
样张大了嘴巴,但这的确是事实。
根据两家公司达成的版权协议,双方会为采用对方的技术而支付专利费用,微软向Sun提前
支付3.5亿美元使用费,Sun则承诺,如果Sun集成微软的某些技术,也会向微软付款。
毫无疑问,“私下了结”的方式对双方而言都是最好的结果。就在协议签署的当天,在美
国旧金山由Sun和微软为“抛弃十年恩怨、携手合作“举行的新闻发布会上,尽管比尔?盖
茨没有到场,但这并没有防碍现场看起来异常轻松的气氛。麦克尼利和鲍尔默各自穿了一
件密歇根州底特律“Red Wings”曲棍球队的运动服,并谈及了一起在哈佛大学读书的经历
,麦克尼利还说:“当时我们两人是非常要好的朋友,当然我们也有吵架的时候。”人与人
当然可能成为终生的知己,但是公司与公司之间有的只能是利益上的分分合合。


2000-2004: JBoss和Eclipse
——Java开源软件的王者
文/莫映

Java和开源几乎就是天生的一对,这可以从无比兴盛繁荣的Java开源软件社区得到佐证。
目前最有影响力的Java开源软件项目,要数JBoss和Eclipse。可以说,几乎所有的Java开
发人员都获多或少的听到过或接触和使用过它们。前者是目前最优秀、应用最为广泛的企
业级开源J2EE应用服务器,后者是功能完全可以替代商业产品的Java IDE。二者的覆盖功
能之全、支持工具之广、子项目之多,几乎可以仅凭借它俩来完成企业应用的开发构建到
部署实施的全过程,而软件开发者和客户也都可以最大程度上享受高质量,高可靠Java开
源软件所带来的低成本优势。
JBoss和Eclipse的巨大成功,几乎令各自领域的商用竞争者抓狂,其中BEA的WebLogic和I
BM的WebSphere在商业利润上受到JBoss的巨大侵蚀,而Borland的JBuilder、JetBrains的
IDEA等诸多优秀的商用开发工具也不得不面对Eclipse独大的现实。JBoss的CEO兼创始人
Marc Fleury曾直言不讳地表示,希望占据市场主导地位。“我们希望打败IBM,成为中间
件领域里最大的厂商。”JBoss在4.0以前还只是以一个Group存在,盈利手段主要靠服务和
销售文档。但在最近,JBoss已经发展成为一个有限公司,并吸纳多家风险投资,专注于获
取利润为目标之一的第二代开源软件模式(JBoss自己称为“Professional Open Source”
)的创新和运营。这区别于以理论研究为爱好的学院型开源或大公司为基础的非盈利组织
开源,如Linux和Apache。当然JBoss的这种运营方式势必会导致更多的代码控制和专有修
改权,但按JBoss的说法是这样更能获得企业客户的信赖。JBoss的这种模式是否能获得成
功还要我们拭目以待。
不管JBoss和Eclipse的未来发展如何,JBoss和Eclipse的成功已经让我们看到了Java开源
软件的威力,祝愿它们一路走好。


2004:Java 5.0
文/莫映

2004年9月30日,代号为“Tiger”,研发历时近三年的J2SE 5.0发布正式版本,这是Java
平台历来发布版本中改动面波及最大的一次。
纵观Tiger,“Ease of development”是其核心主题,这一点着重体现于语言特性上的改
进,这在很大程度上,简化了开发人员日常的编程任务,以往一些琐碎的手工劳动都代之
以轻松自然,而又安全可靠的自动化实现。其中的注解功能,以及随之而来的声明式编程
,还对构筑于J2SE 5.0之上的J2EE 5.0产生了巨大影响。尽管Tiger在语言特性上做了很大
的动作,但作为Java技术的基础支撑,这些改动都是深思熟虑的结果。
Tiger发布至今也有大半年了,那么Sun又是如何规划J2SE的未来蓝图的呢?据悉,J2SE的
下两个版本分别是代号为“Mustang”的J2SE 6.0和代号为“Dolphin”的J2SE 7.0,预计
Mustang将于明年发布。在吸取了Tiger研发周期过长的教训之后,Sun副总裁Graham Hami
lton表示,Mustang的发布周期将不会那么长。并且,Sun还将“Becoming more open” 作
为Mustang的主题之一。未来JCP对Java技术的影响将会愈加深入,而整个研发过程也将会
愈加透明。Mustang在正式发布前的内部版本也会陆续见诸于众,如此,广大Java开发者便
可以更加及时的了解到Java发展的最新情况。在语言层面上的扩展依然会比较谨慎,比如
像AOP这样的当下热门技术,依然不太可能会见诸其中。据Hamilton所言,一个有可能被引
入的语法特性被称作“friends”import机制,它将使由多个包组成的大型项目变得易于管
理。

 

 

十大人物

James Gosling : Java之父
文/陶文

作为Java之父,James Gosling的名字可谓是耳熟能详。当人们评论一种编程语言时,总喜
欢捎带着把下蛋的母鸡一起带上。Java做为中国的编程语言学习者餐桌上有限的那么几样
餐点中的流行款式,自然是让James Gosling风光不已。虽然James Gosling现在已经不是
领导Java发展潮流的领军人物了,做为Sun的开发者产品组的CTO,怎么算来也是身居高位
了,俗事缠身吧,但是这并不妨碍其对于Java一如既往的爱护,表达着各式各样鲜明的观
点,引发一场又一场的争论。
James Gosling是很爱Java的——是啊,哪有当父母的不爱自己的孩子的呢。James Gosli
ng也是很爱Sun的——是啊,哪有当领导的不爱自己的公司的呢。于是我们在批评.NET的安
全性的队伍前头,在褒扬Java性能的队伍前头,在抨击SWT开倒车的队伍前头,在给NetBe
ans大唱赞歌的队伍前头,我们都看到了James Gosling的身影。无论对错、偏见或者固执
,至少说明了Gosling的鲜明个性丝毫没有受到年龄的影响。也许也只有这种天才而偏执的
人物才能创造出Java这般伟大的语言来吧。
 

Bill Joy : 软件业的爱迪生
文/徐昊

Joy生于1954年,1982年与Vinod Khosla, Scott McNealy和Andy Bechtolsheim一起创建了
Sun Microsystems,并从那时起担任首席科学家,直到2003年离开。他是一位令人崇敬的
软件天才,他在软件和硬件的历史上留下了无数令人仰止的传奇。
在上个世纪80年代早期,DARPA与BBN达成协议,准备将Vinton Cerf和Bob Kahn设计的TCP
/IP协议添加到Berkeley UNIX中。Bill Joy被委派来完成这项任务,然而他却拒绝将BBN的
TCP/IP协议栈添加到BSD中,因为在他的眼中BBN的TCP/IP实现还远不够好,于是他就写了
一个高性能的TCP/IP协议栈。John Gage回忆道,“BBN和DARPA签署了巨额合同来实现TCP
/IP协议,然而他们的员工所编写的代码远没有一个研究生所做的好。于是他们邀请Bill
Joy参加他们的一个会议,这位研究生穿着一件T-Shirt就出现了,他们询问他,‘你是如
何做到的呢?’Bill回答说,‘这是非常简单的一件事,你读一下协议然后就可以编码了
’”。除了TCP/IP协议,基于分页的虚拟内存系统最早也是由Bill Joy添加到Berkeley U
NIX内核当中的。同时他还是vi、csh、早期Pascal编译器的作者。
关于Bill Joy惊人的软件才能流传最广的一个传奇是,据说他在上研究生的时候,想看看
自己能不能写一个操作系统出来,于是就在三天里写了一个非常简陋,但是可以使用的Un
ix系统, 传说就是BSD的前身。虽然如此夸张的才情令人难以置信,但是考虑到主角是Bil
l Joy,还是有一定的可信度的。Bill Joy硕士毕业之后,决定到工业界发展,于是就到了
当时只有一间办公室的Sun, 他作为主要设计者参与了SPARC微处理器的设计,负责设计最
为关键的一部分电路。这样兼精软硬件的天才实在是让人不得不佩服啊。1995年,Sun发布
了轰动世界的Java语言。当然,Bill Joy对Java也作出了不少的贡献,首先是JINI——一
种针对分布式服务的基础连接技术。任何可以内嵌JVM的电子设备都可以通过JINI相互连接
;JXTA是基于Java的P2P协议,允许互联网上的软件进行点对点交流和协作。
这个其貌不扬的瘦高个,有着凌乱的亚麻色头发,被《财富》杂志誉为“网络时代的爱迪
生”的技术狂人,在短短的二十年间,创造了无数令人心动的软件。在MIT的BBS上曾有一
个帖子,说微软电话面试有一道题,问“Who do you think is the best coder, and wh
y?”虽然回复的帖子中大家都声明列举的best coder排名不分先后,然而大多数人仍把Bi
ll Joy列在第一位,或许可以从一个侧面验证Bill Joy在广大Programmer心目中的地位吧



Joshua Bloch :  Java 2 元勋
文/莫映

早在1996年,适逢Java刚刚崭露头角,年内好事连连。先是1月份发布JDK 1.0,然后是5月
底在旧金山召开首届JavaOne大会,年末又是JDK 1.1紧跟其后。正是在Java技术如火如荼
、大展拳脚的背景之下,Joshua Bloch来到了Sun,开始了他带领Java社区步入“迦南美地
”的漫长历程。
很快,他被从安全组调入核心平台组,从事底层API设计。至此以后,每逢JDK的重大版本
发布,总能在其中见到Joshua的“妙笔”。JDK 1.1中的java.math、1.4中的assertions,
还有大家所熟识的Collections Framework皆是Joshua一手打造。其中的Collections Fra
mework还获得了当年的Jolt大奖。到了J2SE 5.0研发阶段,身为平台组构架师的Joshua接
掌了Tiger大旗,其核心地位已然无人可以替代。作为Tiger的代言人和领路人,没有谁比
Joshua更清楚Tiger。相信大家一定还记得Joshua当年仿效英国诗人William Blake所做的
咏Tiger诗八首,优雅的笔调,透出大师深厚底蕴的同时,也道出了Tiger的几大重要特性
,这些特性是自JDK 1.1引入Inner Class以来,Java最大的语法改进。
Java风雨十年,从JDK 1.1到J2SE 5.0,Joshua实在功不可没。难怪有人戏言,假如将Jam
es Gosling比作Java之父,那么Joshua就是一手将Java “哺育”成人的Java之母。Joshu
a对Java的贡献还不止于JDK,提起他的大作《Effective Java》(Addison Wesley, 2001
),相信Java粉丝们一定耳熟能详。该书荣膺2002年度Jolt大奖,且备受James Gosling推
崇。书中57条颇具实用价值的经验规则,来自Joshua多年来在JDK开发工作中,尤其是Col
lections Framework设计中的实践心得,各个有理有据,剖析深入,也足见其深厚功力。
该书对Java社群的影响,犹如C++社群中的《Effective C++》。Joshua对JCP的贡献也不小
。他是JSR201和JSR175的领导者,前者包含了Tiger四大语言特性,后者则为Java提供了元
数据支持。此外,他还是JSR166的发起人之一(该JSR由Doug Lea领导),并且是许多其他
JSR的参与者。Joshua目前是JCP为数不多的几个执行委员会成员之一。
Joshua Bloch给人的印象是谦逊平和,行事低调而不喜抛头露面,一个典型的技术人员和
实干家。不过即便如此,也丝毫不会减弱他对Java技术的卓越贡献和对Java社区的绝对影
响力。有人说,如果他能更彰显一些,就很有可能成为Java开发者中的领军人物,就有如
Don Box之于微软社群。
2004年7月初,就在Tiger发布在即之时,就在Jusha Bloch刚刚荣获Sun“杰出工程师(Di
stinguished Engineer)”的称号之时,他突然离开Sun而去了正值发展态势迅猛的Googl
e。当他离开Sun的消息在TSS发布之后,众多拥趸表达了怀念与不舍之情。一年过去了,我
们还没有获知Joshua的任何近闻,似乎又是他行事低调的一贯作风所致,不知他在Google
状况如何。希望Joshua依然能继续“摩西未尽的事业”,以他的影响力推动Java社群继续
前行。据称,《Effective Java》的下一版会加入Java 5.0的部分,让我们翘首以待吧。



Bruce Eckel : 功勋卓著的机会主义分子
文/孟岩

Bruce Eckel原本是一位普通的汇编程序员。不知道是什么因缘际会,他转行去写计算机技
术图书,却在此大红大紫。他成功的秘诀不外乎两点:超人的表达能力和捕捉机会的能力
。他最早的一本书是1990年代初期的《C++ Inside & Out》,随后,在1995年他写出了改
变自己命运的《Thinking in C++》。如果说这本书充分表现了他作为优秀技术作家的一面
,那么随后他写作《Thinking in Java》并因此步入顶级技术作家行列,则体现了他作为
优秀的机会主义分子善于捕捉机会的另一面。写作中擅长举浅显直接的小例子来说明问题
,语言生动,娓娓道来,特别适合于缺乏实践经验的初学者。因此《Thinking in Java》
俨然成为天字第一号的Java教科书,对Java的普及与发展发挥着不可忽略的作用。不过公
允地说,Bruce Eckel的书欠深刻。比如在“Thinking in…”系列中对设计模式的解说就
有失大师水准。这一方面是因为书的定位非常清晰,另一方面也是因为Bruce太过分心赶潮
流,未能深入之故。TIJ之后,他预言Python将火,就匆匆跑去写了半本《Thinking in P
ython》。后来Python并未如期而旺,于是他也就把书稿撂在那里不过问了,机会主义的一
面暴露无遗。我们也可以善意的猜测一下,他的下一个投机对象会是什么呢?Ruby?.NET
?MDA?总之,是什么我都不奇怪。


Rickard Oberg :J2EE奇才
文/熊节

Oberg的作品很多,流行的代码生成工具XDoclet和MVC框架WebWork都出自他的手笔。这两
个框架有一个共同的特点,即它们的功能虽然简单,但设计都非常优雅灵活,能够很方便
地扩展新功能甚至移植到新环境下使用。优雅的设计源自Oberg的过人才华,简单的功能则
折射出他玩世不恭的人生态度。正是这两种特质的融合,才造就了这个不世出的奇才。
1999年,JDK 1.3发布,其中带来了一个重要的新特性:动态代理(Dynamic Proxy)。当
所有人都还在对这项新技术的用途感到迷惑时,Oberg发现用它便可以轻松攻克EJB容器实
现中的一些难关。这一发现的产物就是一本《Mastering RMI》,以及大名鼎鼎的JBoss应
用服务器。但Oberg很快又让世人见识了他的玩世不恭。由于和总经理Marc Fleury在经营
理念上不合,Oberg抱怨“法国的天空总让我感到压抑”,甩手离开了自己一手打造的JBo
ss。此后的几年里,他和老友Hani Suleiman不断地对JBoss的“专业开源”模式和Marc F
leury的商人味道冷嘲热讽,让众人为他的孩子气扼腕叹息。
2002年10月,微软推出Petstore示例应用的.NET版本,并宣称其性能比Java Petstore高出
数倍。正是Oberg深入分析这个示例应用的源代码,在第一时间指出它大量运用了SQL Ser
ver专有的特性,性能对比根本不具参考价值。后来Oberg又先后关注了AOP和IoC容器,两
者都成为了J2EE架构的新宠。
 

Doug Lea : 世界上对Java影响力最大的个人
文/KIT

如果IT的历史,是以人为主体串接起来的话,那么肯定少不了Doug Lea。这个鼻梁挂着眼
镜,留着德王威廉二世的胡子,脸上永远挂着谦逊腼腆笑容,服务于纽约州立大学Oswego
分校计算器科学系的老大爷。
说他是这个世界上对Java影响力最大的个人,一点也不为过。因为两次Java历史上的大变
革,他都间接或直接的扮演了举足轻重的脚色。一次是由JDK 1.1到JDK 1.2,JDK1.2很重
要的一项新创举就是Collections,其Collection的概念可以说承袭自Doug Lea于1995年发
布的第一个被广泛应用的collections;一次是2004年所推出的Tiger。Tiger广纳了15项J
SRs(Java Specification Requests)的语法及标准,其中一项便是JSR-166。JSR-166是来
自于Doug编写的util.concurrent包。
值得一提的是: Doug Lea也是JCP (Java小区项目)中的一员。
Doug是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己
的知识并不会因为给了别人就减少了,知识的分享更能激荡出不一样的火花。《Effectiv
e JAVA》这本Java经典之作的作者Joshua Blosh便在书中特别感谢Doug是此书中许多构想
的共鸣板,感谢Doug大方分享丰富而又宝贵的知识。这位并发编程的大师级人物的下一步
,将会带给Java怎样的冲击,不禁令人屏息以待。


Scott McNealy :SUN十年来的掌舵者
文/KIT

McNealy,Sun的CEO、总裁兼董事长。他曾经狂傲的说:“摧毁微软是我们每个人的任务。
”这位英勇的硅谷英雄,似乎带头起义,试图组织一个反微软阵线联盟,以对抗微软这股
庞大的托拉斯恶势力。他时常口出惊人之语,在公开场合大肆的批评微软,并曾经说微软
的.NET是.NOT。
Scott McNealy先后毕业于哈佛大学及史丹佛大学,分别持有经济学学士学位及企管硕士。
1982年MBA毕业的他和三个同学共同合伙创建了Sun,并于1984年成为Sun的执行官。“要么
吞了别人,不然就被别人吞了”是Scott McNealy的名言录之一。他擅长以信念带动员工,
鼓舞士气。极富自信的他,对于认定的事,总是坚持自己的想法,因此有人形容他是一个
刚愎自用的决策者。
身为Sun这艘船的掌舵者,Scott McNealy能够看多远,Sun就能走多远。Scott McNealy认
为将来软件界是一个只有服务,没有产品的世代。他希望打造出Sun不是一个纯靠硬件赚钱
的公司。从Open Source到Open Solaris,Sun希望可以成为提供整合性解决方案的服务厂
商。Solaris 10 + UltraSPARC是否可以像Scott McNealy希望的是下一匹世纪黑马呢?Su
n是否能以股价来证明华尔街分析师及普罗大众的诽短流长?Scott McNealy是否能带领着
Sun成为继微软之后的下一个巨人,一场场IT界的争霸战值得我们拭目以待。


Rod Johnson : 用一本书改变了Java世界的人
文/ 刘铁锋

Rod在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位。更令人吃惊的是在回到软
件开发领域之前,他还获得了音乐学的博士学位。有着相当丰富的C/C++技术背景的Rod早
在1996年就开始了对Java服务器端技术的研究。他是一个在保险、电子商务和金融行业有
着丰富经验的技术顾问,同时也是JSR-154(Servlet 2.4)和JDO 2.0的规范专家、JCP的
积极成员。
真正引起了人们的注意的,是在2002年Rod Johnson根据多年经验撰写的《Expert One-on
-One J2EE Design and Development》。其中对正统J2EE架构的臃肿、低效的质疑,引发
了人们对正统J2EE的反思。这本书也体现了Rod Johnson对技术的态度,技术的选择应该基
于实证或是自身的经验,而不是任何形式的偶像崇拜或者门户之见。正是这本书真正地改
变了Java世界。基于这本书的代码,Rod Johnson创建了轻量级的容器Spring。Spring的出
现,使得正统J2EE架构一统天下的局面被打破。基于Struts+Hibernate+Spring的J2EE架构
也逐渐得到人们的认可,甚至在大型的项目架构中也逐渐开始应用。
Rod Johnson的新作《Expert One-on-one J2EE Development without JEB》则更让人吃惊
,单单“Without EJB”一词就会让大多数J2EE架构师大跌眼镜了。不过Rod Johnson可能
仅仅是想通过“Without EJB”一词表明应该放开门户之见。这也是Rod Johnson一贯的作
风,。也许正是这种思想,促使得Rod Johnson创建了Spring,真正改变了Java世界。

 

Alan Kay :Java的精神先锋
文/徐昊

Sun的官方Java教材中有一句话,说Java是“C++的语法与Smalltalk语义的结合”。而Sma
lltalk的创造者就是Alan Kay。
Alan Kay于1970年加入Xerox公司的Palo Alto研究中心。早在70年代初期,Alan Kay等人
开发了世界上第二个面向对象语言Smalltalk,因此,Alan Kay被誉为Smalltalk之父。20
03年,Alan Key因为在面向对象程序设计上的杰出贡献,获得了有计算机界的诺贝尔奖之
称的ACM Turing Award。
Alan Kay成名于Smapltalk和OOP,而Java虽然在语言上类似于C,但是在语义上非常接近S
malltalk,很多Java中的设计思想在Alan Kay的文献中找到根源,也有些人将Alan Kay尊
为Java思想的先驱。不过遗憾的是似乎Alan Kay老先生对Java并不买账,反倒攻击说Java
是存在致命缺陷的编程语言,Java的成功不是由于Java本身的内在价值,而是其商业化的
成功。Alan Kay欣赏的是Lisp,他认为Lisp是软件的麦克斯韦方程,其中的许多想法是软
件工程和计算机科学的一部分。看来拥有Alan Kay这样一位重量级的Java先驱仍是我们Ja
va一厢情愿的单恋吧。

 

Kent Beck : 领导的敏捷潮
文:刘铁锋

Beck全家似乎都弥漫着技术的味道。生长在硅谷, 有着一个对无线电痴迷的祖父,以及一
个电器工程师父亲。从小就引导Kent Beck成为了业余无线电爱好者。
在俄勒冈州大学读本科期间,Kent Beck就开始研究起模式。然而在他最终拿到计算机学位
之前,他却是在计算机和音乐中交替学习。似乎Java大师都能够有这样的能耐,另一Java
大牛Rod Johnson同样也拥有音乐学的博士学位。
Kent Beck一直倡导软件开发的模式定义。早在1993年,他就和Grady Booch(UML之父)发
起了一个团队进行这个方面的研究。虽然著有了《Smalltalk Best Practice Patterns》
一书,但这可能并不是Kent Beck最大的贡献。他于1996年在DaimlerChrysler启动的关于
软件开发的项目,才真正地影响后来的软件开发。这次的杰作就是XP(极限编程)的方法
学。
和软件开发大师Martin Fowler合著的《Planning Extreme Programming》可谓是关于XP的
奠基之作。从此,一系列的作品如《Test Driven Development: By Example》,《Extre
me Programming Explained: Embrace Change》让更多的人领略到了极限编程的精髓,也
逐步导致了极限编程的流行。
Kent Beck的贡献远不仅如此。对于众多的Java程序员来说,他和Erich Gamma共同打造的
JUnit,意义更加重大。也许正式这个简单而又强大的工具,让众多的程序员更加认可和信
赖极限编程,从而引起了Java敏捷开发的狂潮吧。


 十大产品

Sun JDK :Java的基石
文/莫映

众所周知,流传于市的JDK不单Sun一家,比如IBM的JDK、BEA的JRocket、GNU的GCJ,以及
如Kaffe这样的开源实现,不一而足。但是,根正苗红的Sun官方JDK一直以来都是备受瞩目
的主流,它对Java社区的影响也是举足轻重。
1996年1月,Sun在成立了JavaSoft部门之后,推出了JDK 1.0,这是Sun JDK(Java Devel
opment Kit)的首个正式版本;当年12月,JDK1.1出炉。该版除了对前序版本部分特性做了
改进以外,重写了AWT,采用了新的事件模型。1998年12月,JDK 1.2正式发布。此时的类
库日臻完善,API已从当初的200个类发展到了1600个类。在1.2版本中引入了用100%纯Jav
a代码写就的Swing,同时,Sun将Java更名为Java 2。
1999年,Java 技术形成了J2SE、J2EE和J2ME三大格局。Sun向世人公布了Java HotSpot性
能引擎技术的研究成果。HotSpot旨在进一步改善JVM性能,提高Java ByteCode的产生品质
,加快Java应用程序的执行速度。J2SE 1.3发布于2000年;2002年2月间,J2SE 1.4问世,
这是有JCP参与以来首个J2SE的发行版本。2004年9月30日,代号为“Tiger”的J2SE 5.0终
于出笼了,这次发布被誉为Java平台历来发布中特性变动最大的一次。包括泛型在内的若
干重大语法改进、元数据支持,包括多线程、JDBC在内的多项类库改进,都令广大Java程
序员激动不已。自此,Sun的官方JDK(J2SE Development Kit)已经步入了一个新的高度


 

Eclipse :以架构赢天下
文/恶魔

IBM是在2001年以4000万美元种子基金成立Eclipse联盟,并且捐赠了不少程序代码。如今
,该组织有91个会员,包含许多全球最大的软件商。根据Evans Data公司的资料,Eclips
e是目前最受欢迎的Java开发工具。
Java厂商若要共同对抗微软,彼此之间就要有共同的开发工具才行。
在Eclipse平台上,程序员可使用好几种不同的语言。在前端方面,用户可整合多种工具来
撰写Plug-in程序或Unit Test。Eclipse最大的特色就在于其完全开放的体系结构,这代表
任何人都可下载并修改程序代码,给Eclipse写插件,让它做任何你能想到的事情,即所谓
“Design for everything but nothing in particular”。
Eclipse基金会的架构比较特别,反映出企业现今对于开放原始码计划也越来越积极主动。
Eclipse不像一般开放源码软件容许个人的捐献程序,该基金会是由厂商主导。不论是董事
会成员或者是程序赞助者几乎都来自于独立软件开发商(ISVs)的员工。
Eclipse首席执行官Mike Milinkovich说,这种厂商会员制是特意设计的;他说Eclispe软
件开发快速就是因为会员制的关系,同时又加上开放源码开发模式的临门一脚。这与一般
透过标准组织的做法全然不同。 这其实正好验证了一句老话:“开放即标准”。


JUnit/Ant : 让Java自动化的绝代双骄
文/刘铁锋

在Java程序员必备的工具中,共 同拥有且交口称赞的恐怕就非JUnit、Ant莫属了。一个是
单元测试的神兵利器,一个是编译部署的不二之选,它们让Java的开发更简单。
JUnit由XP和TDD的创始人、软件大师Kent Back以及Eclipse架构师之一、设计模式之父Er
ich Gamma共同打造。名家的手笔和理念使得JUnit简单而强大,它将Java程序员代入了测
试驱动开发的时代。JUnit连任了2001、2002年“Java World编辑选择奖”以及2003年“J
ava World最佳测试工具”和2003年“Java Pro最佳Java测试工具”等众多奖项,深受Jav
a程序员好评。
Ant是开源项目的典范,它让IDE的功能更加强大,从Sun的NetBeans到JBuilder,主流的ID
E中处处都有它的身影。“Another Neat Tool”原是它的本名,但这已经渐渐不为人知。
它彻底地让部署自动化,而程序员需要做的仅仅是几条简单的配置命令。和JUnit一样,A
nt也荣获了众多的殊荣:2003年JavaWorld“最有用的Java社区开发的技术编辑选择奖”,
2003年Java Pro“最有价值的Java部署技术读者选择奖”,2003年“JDJ编辑选择奖”,也
让Ant受到的多方的认可。
Ant对JUnit的全面集成,则使得一切都变得更加完美。只需简单地配置,从自动测试到报告
生成,从编译到打包部署均可自动完成。强大的功能,简单的配置,让Java程序员高枕无
忧。实可谓让Java自动化的绝代双骄。

Websphere : 活吞市场的大鲸
文/jini

1999年, IBM与Novell签订合作协议,成功地提供电子商务的解决方案给予原先使用NetW
are的用户。同年更是推出了WebSphere Application Server 3.0,并且推出WebSphere S
tudio与VisualAge for Java让工程师可以快速开发相关的程序。2001年,IBM更是宣布将
应用服务器、开发工具整合在一起,与DB2、 Tivoli及Lotus结合成为一套共通解决方案,
如今、IBM更是并入了Rational Rose ( UML tools )让开发流程更是完整化。
Sun在Web Services的策略方面远远落后于微软与IBM, 当他们手拉手在研订Web Services
规范, 加上IBM买硬件送软件或是买WebSphere送DB2的策略让企业大佬们纷纷转向IBM的阵
营, Sun才惊觉大势已去。WebSphere复杂的安装,深奥的设定,难以理解的出错讯息不断
地挑战开发者的耐心与毅力。
IBM如今已经不是将WebSphere定义为单一产品,它已经是一个平台的代名词。它里面的产
品目前包含了应用服务器、商业整合、电子商务、 数据讯息管理、网络串流、软件开发流
程、系统管理、无线语音等等。非常多样化,也让企业界愿意相信WebSphere可以带给他们
一套完整的解决方案。同时, IBM也在推广SOA的概念, 简单来说, 利用Web Service的耦合
性与工作流程的整合, 为企业内部打造以服务为导向的架构。
IBM捐献出Eclipse带给Java开发人员对IDE的重新掌握。未来是否会捐献出WebSphere的哪
一个部分成为OpenSources, 或许, 又是改写Java世界的时刻了。

 

WebLogic : 技术人的最爱
文/jini

1995年, BEA成立了, 初期以Tuxedo数据转换的产品为基础, 成长之迅速是历年来最强的企
业。 1998年, BEA推出以Java为基础的网络解决方案, 提供了完整的中间层架构, 更同时
支持EJB 1.0 及微软的COM组件, 方便的管理接口掳掠了工程师的心。 在IBM和Oracle尚未
准备好迎击的时候, BEA已经席卷企业应用平台的市场。 WebLogic无论在市场领先度与技
术领导性与策略远观性都优于当年的所有应用服务器厂商。
如今WebLogic不仅仅是应用平台服务器的名称, 而是BEA对于整个企业解决方案的总称, 无
论是WebLogic Portal或是WebLogic Integration配合着Workshop开发环境, 来自微软的U
I开发团队让Workshop几乎达到所见即所得。 接着, 在下一个版本之中, BEA的BeeHive开
放源代码计划将释出中间层控件的开发模块, 并且与Eclipse合作共同打造新一代的开发环
境。 如此强而有力的技术支持, 更是让顾客愿意使用WebLogic平台的最大原因。
代号为“Diablo”的 WebLogic Server 9.0小恶魔已经出现了, 目前虽然仅仅是BETA版,
以Portlet 方式打造的管理接口与完整且美妙的WebServices支持, 实在很难找到可以挑剔
的地方, 虽然去年被IBM的技术性推销超越了市场占有率, 不过接下来SOA的平台竞争现在
才开始, BEA的LOGO也加入“Think liquid”并且推出新的AquaLogic平台做为数据服务平
台, 可见, Java的应用服务器的战争, 还会继续进行着。

 

JBuilder : Java开发工具的王者
文/刘铁锋

Java的开发工具中,最出名的莫过于Borland公司的JBuilder了。对于一些没有弄清楚开发
工具与JDK的区别的Java入门者来说,JBuilder就如同Visual C++之于C++,以为JBuilder
就是Java的全部。比起捆绑在服务器上销售的JDeveloper,JBuilder应该是唯一的仅靠自
身的实力而占领了大部分市场的Java商用开发工具了。而JBuilder作为Java 开发工具的王
者,其夺冠之路并非一帆风顺。直到Java的天才Blake Stone成为JBuilder的Architect之
后,JBuilder 2.0以及3.0才逐渐推出。2000年3月14日,JBuilder 3.5的推出别具意义,
它成为了业界第一个用纯Java打造的开发工具,也风靡了整个Java开发工具市场。在同年
11月份推出的JBuilder 4.0乘胜追击,冲破了50%的市场占有率,成为了真正Java开发工具
的王者。
Borland以每半年左右推出一个新版本的速度,让众多的对手倒在了沙场。而Microsoft因
为与Sun的官司,也使得一个强大的对手退出了战争。2001年,加入了对企业协作支持的J
Builder 5以及强化了团队开发工具的JBuilder 6打败了最后一个对手Visual Age For Ja
va。2002年JBuilder 7推出之后,再也没有其他厂商与JBuilder竞争。
孤独的王者并没有停下脚步,在2003年到2005年间,JBuilder也仍然延续了其半年一个版
本的速度,推出了8、9、10、2005四个版本。强大的功能以及持续的改进,也让Java程序
员多了一分对能够在开发工具市场上与Microsoft血拼十数年的Borland的敬仰。

 

Oracle : Java人永远的情结
文/熊节

在林林总总的数据库之中,有一种尤其令人又爱又恨、印象深刻,那就是关系型数据库市
场的“大佬”——Oracle。
从公司的角度,Oracle和Sun有着诸多相似之处,例如:两家公司都拥有一位个性鲜明的C
EO。早在Java诞生之初的1995年,Oracle就紧随NetScape从而第二个获得了Java许可证。
从那以后,Oracle对Java的鼎力支持是Java能够在企业应用领域大获成功的重要原因之一

所有J2EE程序员都知道,Oracle的JDBC驱动虽然与Oracle数据库配合良好,但在不少地方
使用了专有特性。其中最为著名的就是“CLOB/BLOB问题”,诸如此类的问题给开发者带来
了很多麻烦。为了同时兼顾不同的数据库,他们不得不经常把自己的一个DAO(数据访问对
象)写成两份版本:针对Oracle的版本和针对其他数据库的版本。有不少人为了开发便利
,舍弃了数据库之间的可移植性,将自己的产品绑定在Oracle的专有特性上。
Oracle提供的Java开发工具也与此大同小异。不管是数据库内置的Java支持还是JDevelop
er IDE, Oracle的Java工具都和Oracle数据库有着千丝万缕的联系。看起来,只要Oracl
e还是数据库市场上的“头牌”,了解、学习Oracle的专有特性,周旋于Oracle特有的问题
和解决方案之中,就将仍旧是J2EE程序员在数据库基础和SQL之外的必修功课。对Oracle的
爱与恨,也将仍旧是Java人心头一个难解的情结。

 

Struts、Hibernate : 让官方框架相形失色的产品
文/刘铁锋

好的框架能够让项目的开发和维护更加便捷和顺利。相比Sun官方标准的迟钝以及固执,开
源框架也更得到Java程序员的共鸣。Struts以及Hibernate就是这样一类产品,它们简单、
优雅,更让官方的产品相形失色。
谈起Struts,不可避免地就要提及MVC(Model-View-Controller)的理念。而准确地讲,
MVC的提出却最早源于JSP的标准。在1998年10月7号,Sun发布的JSP的0.92的规范中提出的
Model 2就是MVC的原型。在1999年12月Java World的大会中,Gavind Seshadri的文章最早
阐述了Model 2就是一种MVC的架构,同时也提及了MVC架构是一种最好的开发方法。2000年
3月,由Craig McClanahan发布的Struts成为了最早支持MVC的框架。Struts在设计上虽然
存在一些诟病,但是不可否认的是,它使得Java Web应用的开发更加简洁和清晰,也让更
多的程序员爱上了Java,并开始遗忘官方的JSP。时至今日,比起如WebWork、Tapestry以
及Sun官方的JSF,Struts或多或少存在些不足,但是众多成功项目的实施,仍然使其牢牢
占据的Java Web应用框架的首位。
Hibernate则在某种程度上改变了人们对构建J2EE的思路。相比其EJB的Entity Bean的映射
技术,Hibernate则显得更加简洁和强大。五分钟就能把Hibernate跑起来,让更多的Java
程序员享受到了开发的乐趣。第15届Jolt大奖中,最优秀数据库、框架以及组件的奖项中
,Hibernate当仁不让获得头筹;不仅如此, Hibernate甚至还影响了官方的标准。在众多
Java程序员翘首以待的EJB 3.0的规范中,Hibernate得到了支持。
Java开源的繁荣不仅让众多Java的开发者享受到了更多的便利,甚至影响了官方的标准。
恐怕这也是作为Java人独有的乐趣之一吧。

 

PetStore : J2EE人的必修课
文/陶文

很少有一个例子项目如PetStore这 般广为人知,而这很大程度上要归功于Sun很“英明”
地把PetStore做成一个只展示架构而在性能调优上留下了大大余地的例子。围绕着性能话
题,产生了颇为有趣的厂商之间以及平台之间的Pet Wars。除去这些关于性能的流言蜚语
乃至中伤,PetStore在展示J2EE1.3平台的架构、演示什么叫分层方面还是有着很大的功劳
的。而且PetStore在架构方面的丰富性使得其成为J2EE的那些轻量级小兄弟们展示自身的
一个必选科目。
不谈那些围绕PetStore的口水,那些数不尽的盗版,PetStore给开发新手带来的最重大的
影响,我想应该是架构的观念而不是性能,也不是业务。做为一种技术的Demo,这无可非
议。但是如果你是一个新手,跟着PetStore亦步亦趋地学习J2EE开发,难免会陷入过度设
计、华而不实之类的困境。围绕着.NET的PetStore的克隆PetShop展开的架构与性能的大讨
论,是不是也在促使我们学习新技术时应该以解决问题为导向呢?特别是当你想把一个如
PetStore这般的Sample Project的技术照搬到你的现实世界的Real Project来时。

 

十大组织

Sun : 因为Java而永被荣光
文/孟岩

Sun是1980年代初期由斯坦福大学三位年轻学生创立的公司。与一般人的印象不同,“SUN
”的本意并不是企图剽窃天上那颗温暖的恒星的威名,而是“斯坦福大学网络”的意思。
Sun在“前Java”时代就因为SPARC芯片、Solaris操作系统和“网络就是计算机”的口号而
为人所知。1990年12月,Sun启动了一个看上去没什么意思的嵌入式软件项目。然而,基于
C++的开发很快遇到了麻烦。一个创新型技术公司的特色立刻显示出来,一群天才不是去深
入C++,而是另辟蹊径,发明了Java。这个传奇故事已经尽人皆知,但是其中所包含的精神
却始终令人望空凝思。
Java的发明,使得Sun真正有机会在软件的历史天空中放射出太阳的光芒。Sun发明了Java
,并且在长达十年的时间里始终走在Java大潮的最前端。Sun是Java的老家,是Java慈爱的
母亲,这一切任何人都改变不了。虽然Sun似乎没能够从Java中获得应有的金钱回报,但这
丝毫没有挫伤Sun对于Java的母爱,还有对于Java大潮的舍我其谁的领导气概。
所有人都迷恋富有的感觉,但是也迟早会意识到钱不是世上最宝贵的东西。这个世界并不
缺少会赚钱的公司,但是能够靠着创新型技术推动整个世界进步的公司却是凤毛麟角。Su
n应该感到骄傲,他们将因为Java而在历史的天空里发射出太阳的光芒。

 

IBM : Java经济的最大受益人
文/恶魔

Sun公司是Java的发明人,但IBM却是Java最大的受益者。是IBM抢占了利润丰厚的应用服务
器市场的头把交椅,是IBM在Java技术上投入最多的金钱,拥有最大的影响力和最好的开发
者社区。可以毫不夸张地说,Java使IBM的软件体系得到复兴,在某种意义上,甚至可以说
,是Java创造了这种复兴。Java之后又来了Linux,这种建造在不属于自己的平台上以获得
成功的理念更是变得非常有影响力。正是这种理念铸就了今天IBM “按需计算,服务为王
”的王者风范。
2004年三月,IBM以Java的解放者的姿态借机向Sun发难。IBM公司负责新兴技术的副总裁史
密斯在一封公开信中表示,IBM愿意与Sun合作成立一个项目,意在通过开放源代码开发模
式管理Java的开发工作。
墙内开花,墙外香。面对IBM的成功,到底是谁妒嫉呢?或许去程序的社区中逛逛聊聊,明
眼人是不难发现事实真相的。也许Sun应该好好向IBM学习经营之道。尽管利润额不如硬件
及服务部门,但IBM软件部门的利润率是最高的——高达85%的利润率足以令人惊叹。在最
近的一个季度里,IBM软件部的利润率上升了8%,其中WebSphere产品组的利润率上升了14
%。
正是IBM在开源和Java上的全身心地投入又秉承开放性的原则,今日的Java才能以日进千里
的速度将许多竞争对手远远抛在后面。Java 10年,IBM功不可没。

 

BEA : 用AppServer影响Java阵营
文/霍泰稳

十年前诞生的Java并不是一开始 就那么引人注目的,虽然用Applet也曾为互联网络带来一
抹亮色,但毕竟只是Toy。在企业级应用市场上,Java一直没有什么起色,虽然Java的支持
者一直在鼓吹它有着大型企业级应用的强悍功能。过高的期望与低能的产品,一时间颇让
人怀疑Java的路是否已经走到了尽头?可以说是WebLogic Server的出现逐渐打消了人们的
顾虑,BEA公司慧眼独具在2001年收购的这个产品将人们的目光吸引到电信、金融、政府等
Java企业级应用方面,WebLogic Server以其优良的性能让人们看到Java应用广阔的未来。
虽然随后在Java应用服务器方面出现了像IBM公司的WebSpere、开源软件JBoss等Java应用
服务器,但WebLogic Server几乎占领世界前500强所有企业的应用服务器市场地位依然无
法撼动。
Java现在已经不单纯是一个语言,从另一方面它也代表着开放与创新。很多以Java产品为
基础的公司或者从事Java开发的程序员骨子里都有着开放与创新的烙印,BEA公司的发展深
深地印证了这一点。与合作伙伴的密切合作向Java社区贡献产品基础源代码、加入权威开
源组织参与Java标准的制定等证实着BEA的开放,而其产品从WebLogic Server一种拓展到
WebLogic Platform、WebLogic Portal、WebLogic Workshop等其它领域又证实着它的创新
能力。

 

Oracle : 早起的鸟儿有虫吃
文/孟岩
Oracle的老板拉里?艾利森是有名的混世魔王和花花公子,所以尽管他也是软件产业成功人
士的代表,却绝不是程序员们心目中的英雄,程序员们毕竟不是央视《对话》节目里群众
演员,没必要为了节目需要而对权贵财阀们做出一副贱骨头状。但是,任何人都不能不钦
佩Oracle在技术上的前瞻性和坚决性。Oracle是1996年获得Java许可证的,紧接着就大胆
地将Java作为战略性的发展方向而予以全面支持。要知道当时Java的前景并不是十分确定
的,而Oracle的坚决投入,使得它在后来的Java世界中抢得一席之地。1998年9月发布的O
racle 8i为数据库用户提供了全方位的Java支持。Oracle 8i成为第一个完全整合了本地J
ava运行时环境的数据库,开发者用Java就可以编写Oracle的存储过程,这意味着可以仅在
Oracle数据库中就完成几乎全部的应用开发。J2EE兴起后,Oracle更是有心进入开发工具
市场,因而购买了JBuilder的源码,并在此基础上开发出JDeveloper。如今Oracle除了数
据库稳居第一之外,在Java开发工具世界里也自成一派。这一切不能不归功于当初的眼光
远大。


Apache : 开源软件的品牌保证
文/陶文

Java程序员的日常工具箱中,我们可以发现Ant、Tomcat、Log4、Lucene这些鼎鼎大名的开
源产品。而它们的共同点在于,都是由Apache Software Foundation社群中杰出的开发者
开发的开源项目。Apache这个名字在Java的世界中实在太出名了,以至于“Apache”这六
个字母成为开源项目品质保证的代名词。Apache是自由开源的一面旗帜,其Apache Licen
se更是成为商业友好的License的首选,只SourceForge上就有1000多个以Apache License
授权的项目,其流行程度可见一斑。
但是,如我们所知,Apache最早闻名IT界是靠高性能的Web服务器,其历史甚至和Java一样
长。Apache对于Java的偏爱,以及其发展的速度也映射出了Java繁荣的一角。现在去它的
主页上看看,满目望去全部都是Java的开源项目,早就不光是其C服务器的老本行了。Apa
che对Java最大的贡献就是提供了这么一个精品的开放舞台,让杰出的开发者和成熟的开源
项目走到一起,共同给Java语言提供一个丰富的工具仓库。对于一种语言、一个平台来说
,其库的丰富程度对于开发者来说的重要性再怎么强调也不为过。勿庸置疑,Aapache上会
出现越来越多的Java开源项目,而我们开发者也将更多地得益于这令人目不暇接的繁荣。


 

TheServerSide : 论坛的专业精神
文/刘天北

成立于2000年5月,TSS最初以一本书而广为人知。它的创始人Ed Roman同时也是J2EE名著
《Mastering EJB》的作者;Roman运营着一个J2EE咨询/培训公司TheMiddlewareCompany(
简称TMC),TSS当时是TMC的下属部门;为了扩大企业的影响,Roman在TSS网站上免费发布
了那本书的电子版。J2EE程序员要吃下这个香饵,就得在论坛中注册;注册的同时,多半也
会看一眼论坛的内容;一看之下,大部分人都被吸引住,成了社区的忠实成员。
TSS究竟有什么吸引人的秘诀?首先,它有一支能力过人的运营团队,除了Roman本人之外
,其中还有好几人都是J2EE领域的顶尖专家;第二,TSS和TMC定期会推出专家研讨会/视频
访谈、技术白皮书、评测报告,通读TSS提供的这些内容,基本上就可以把握技术的当前趋
势。但这还不是全部。最可贵的还是TSS的社区风格:他们深谙技术,但不盛气凌人;思想
敏锐,但不因此缺乏审慎和大局感。其中大多数人都已在自己的开发领域颇有建树,在TS
S上的活动既给他们提供了与同行进行深度交流的机会。一个新成员进入社区,就像参加了
一个起点很高的专业俱乐部,这不是一个求解“怎样设置JAVA_HOME环境变量”之类问题的
地方。事实上,在J2EE技术发展的若干转折点上,TSS都起到了关键的推动作用。
几经易主之后,J2EE咨询培训公司TMC在2004年关闭;TSS则被IT媒体集团TechTarget收购
。我们期待着它更加繁荣的未来。

JBoss : 职业开源软件组织
文/刘天北

J2EE的婴儿期,“应用服务器”原本是“昂贵”的代名词。但从1999年起,Marc Fleury和
Rickard Oberg等人就已经着手改变这种状况。他们开发的开源EJB容器当时叫做“EJBoss
”,在Sun公司的干预下(注意,“EJB”是注册商标),JBoss获得了今天的名字。虽然从
问世起就一直受到关注,但JBoss第一个达到产品化标准的版本可能是它的2.2版。它的易
用让人一见难忘:除了标准部署描述符,无需编写专用的xml配置文件。Oberg自豪地说,
“我们的架构并不是按照EJB规范指定的路线设计的,因此也没有走大多数应用服务器走过
的弯路。”
Jboss 3.x版本保持了一贯的创新精神,在用户中间获得了更广泛的认可。但是,文档要收
费下载、在邮件列表上提问常常会遭到Fleury等人的斥责。无疑,JBoss的创始者也意识到
了自己的幼稚:开源软件只能靠服务盈利,卖文档赚钱有限、骂用户当然更损害企业形象

虽然以Oberg为首的许多程序员退出了开发队伍(其中很多人成了JBoss的死敌),在开源
软件领域也面临JOnAS Geronimo等新老对手的竞争,但JBoss还是以不断推出的新版本站稳
了脚跟。在技术上,它是策动J2EE演进的重要力量:拟议中的EJB 3也要追随Jboss 4倡导
的开发范式,以至于二者的代码样本之间的差别几乎难以分辨;在商业上,JBoss与Sun公
司言和修好,甚至还获得了数量可观的风险投资。JBoss已经像拥护者预期的那样,成为了
应用服务器领域的Linux。

 

Borland : 深度介入Java
文/左轻候


除了Sun以外,也许没有一家公司 像Borland这样深层地介入Java。Borland开发了最早的
Java编译器之一,Borland的工程师参与了早期JDK的设计,Borland的JBCL(JavaBeans Co
mponent Library) 技术也成为后来Java Bean规范的基础。但是Borland对Java世界最大的
影响还是JBuilder。
1997年11月,Borland JBuilder 1.0发布。虽然第一个版本相对于竞争对手并没有表现出
明显的优势,但是Borland凭借深厚的技术实力和正确的市场策略,不断地超越了对手。J
Builder 3.5成为业界第一个100%基于Java架构的开发工具,并且市场份额很快超过了50%
。在随后的版本中,JBuilder持续改进对团队开发、J2EE架构、Mobile技术等方面的支持
,最终成为了Java开发工具市场,特别是大型企业级Java开发市场中的霸主。JBuilder的
成功,很大一个原因来自于Borland坚持的平台中立性,即对不同厂商的解决方案提供一视
同仁的支持。
2005年初,随着Eclipse社区的迅速崛起,Borland进入了Eclipse的董事会,成为战略开发
者(Strategy Developer) ,并宣布将推动Borland的其它产品与Eclipse的集成。在随后发
布的一份文件中,Borland宣称JBuilder的未来版本将放弃原有的PrimeTime架构,而基于
Eclipse架构。这个代号为“Peloton”的版本预计于2006年下半年发布。
Borland对Java的另外两个主要贡献来自Together和BES(Borland Enterprise Server)。T
ogether是著名的建模工具,能够与包括JBuilder在内的许多开发工具进行集成,全球市场
份额占有率排名第二。BES AppServer是一种J2EE服务器,在全球市场份额占有率上次于W
ebLogic和WebSphere,排名第三。

 

JCP : Java世界的联合国
文/黄海波


当联合国正在为安理会改革问题 吵得如火如荼时,Java世界的“联合国安理会”已经成功
地运作了七个年头。JCP(Java Community Process)在1998年由Sun发起成立,目标是通
过一个开放、合作和鼓励参与的非盈利组织来发展和推进Java和相关的技术。正是由于JC
P计划的推出可以让所有对Java感兴趣的软硬件厂商,个人和组织都能参与到技术规范的制
定和发展过程中,协调各方的兴趣和利益、集思广益,才可以让Java在短短的几年内异军
突起,成为可以和微软开发平台抗衡的一个主流开发语言。JCP计划既然是一个组织,自然
也有一定的架构。JCP组织架构主要包括PMO(Program Management Office)、JCP成员、EC
、EG。事实上,JCP的架构就好像一个Java世界的联合国。虽然也有不少人批评JCP成为各
派利益的角力场,因而效率低下;但是,它毕竟为Java的顺利发展很好地掌握了方向。

 

微软与Java : 不得不说的故事
文/孟岩


微软跟Java不对付,地球人都知 道。跟Sun和解了又怎么样?  .NET跟Java就是竞争对手
,没什么说的。但是有点IT掌故的人都知道,微软并非一开始就跟Java过不去。当年比尔
?盖茨盛赞Java是“长期以来最好的程序设计语言”,而且很早就购买了Java许可证。但是
微软作为村里的老大,看着人家的儿子茁壮呈长,不由得生了私心杂念,搞起了小动作,
在Visual J++中加入了一些破坏纯洁性的东西。单独来看,Visual J++是COM时代微软最棒
的开发工具,用WFC写Windows应用程序和COM组件实在是一种享受。但是放在Java大家庭里
,这个家伙就显得多少有点不怀好意。一场官司下来,微软被逐出Java大家庭,Visual J
++无疾而终。以后的事情尽人皆知,.NET出笼,利齿直指Java,几年撕咬下来,没占着便
宜也没吃大亏,如今也算是南北朝对峙,二分天下有其一。设想如果当时微软能够摒弃帝
国主义心态,正确对待Java,与其他人一起共建美好的Java“共产主义社会”,那么今天
我们的软件开发世界应该会美好得多。可惜黄粱一梦,终究是蚂蚁的喜事。2004年,微软
与Sun实现了和解,但愿到Java 20周年的时候,我们能更正面地描述微软对Java发挥的作
用。

2005年03月26日

IBM developerWorks search results
1900多篇IBM专家在eclipse方面的技术文章
    http://www-106.ibm.com/search/searchResults.jsp?searchType=1&searchSite=dW&query=eclipse&searchScope=dW

在工作台外使用Eclipse GUI
Using the Eclipse GUI outside the Eclipse Workbench
    http://www-106.ibm.com/developerworks/java/library/os-ecgui1/

Eclipse平台下进行C/C++开发
C/C++ development with the Eclipse Platform
    http://www-128.ibm.com/developerworks/opensource/library/os-ecc/index.html

创建本机、跨平台 GUI 应用程序
    http://www-900.ibm.com/developerWorks/cn/linux/guitoolkit/j-nativegui/index.shtml
创建本机的跨平台 GUI 应用程序(重述)
    http://www-900.ibm.com/developerworks/cn/java/j-nativegui2/

将基于 Swing 的开发工具插入 Eclipse 中
    http://www-900.ibm.com/developerWorks/cn/java/os-swing/index.shtml
Responding to resource changes in the Eclipse workspace
    http://www.eclipse.org/articles/Article-Resource-deltas/resource-deltas.html

在 Eclipse Workbench 之外使用 Eclipse GUI,第 1 部分:独立使用 JFace 和 SWT
    http://www-900.ibm.com/developerWorks/cn/java/os-ecgui1/
在 Eclipse Workbench 之外使用 Eclipse GUI,第 2 部分:使用 JFace 图像注册表
    http://www-900.ibm.com/developerWorks/cn/linux/opensource/os-ecgui2/index.shtml
在 Eclipse Workbench 之外使用 Eclipse GUI,第 3 部分:添加操作、菜单和工具栏
    http://www-900.ibm.com/developerWorks/cn/linux/opensource/os-ecgui3/index.shtml

JFace 开发向导
    http://www-900.ibm.com/developerWorks/cn/linux/opensource/os-ecjfw/index.shtml
Creating JFace Wizards
    http://www.eclipse.org/articles/Article-JFace%20Wizards/wizardArticle.html

开发 Eclipse 插件
    http://www-900.ibm.com/developerWorks/cn/java/os-ecplug/index.shtml
驾驭 Eclipse 功能部件
    http://www-900.ibm.com/developerWorks/cn/linux/opensource/os-ecfeat/index.shtml
扩展 Eclipse 的 Java 开发工具
    http://www-900.ibm.com/developerWorks/cn/linux/opensource/os-ecjdt/index.shtml

问题一:我声明了什么!

String s = “Hello world!”;

许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答,一半的人大概会回答错误。
这个语句声明的是一个指向对象的引用,名为“s”,可以指向类型为String的任何对象,目前指向”Hello world!”这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象,我们只是声明了一个只能指向String对象的引用变量。所以,如果在刚才那句语句后面,如果再运行一句:

String string = s;

我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生,string还是指向原来那个对象,也就是,和s指向同一个对象。

问题二:”==”和equals方法究竟有什么区别?

==操作符专门用来比较变量的值是否相等。比较好理解的一点是:
int a=10;
int b=10;
则a==b将是true。
但不好理解的地方是:
String a=new String(“foo”);
String b=new String(“foo”);
则a==b将返回false。

根据前一帖说过,对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内容为”foo”的字符串,既然是“两个”,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值,所以使用”==”操作符,结果会是false。诚然,a和b所指的对象,它们的内容都是”foo”,应该是“相等”,但是==操作符并不涉及到对象内容的比较。
对象内容的比较,正是equals方法做的事。

看一下Object对象的equals方法是如何实现的:
boolean equals(Object o){

return this==o;

}
Object对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。同样也可以看出,Object的equals方法没有达到equals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定,所以Object把这个任务留给了类的创建者。

看一下一个极端的类:
Class Monster{
private String content;

boolean equals(Object another){ return true;}

}
我覆盖了equals方法。这个实现会导致无论Monster实例内容如何,它们之间的比较永远返回true。

所以当你是用equals方法判断对象的内容是否相等,请不要想当然。因为可能你认为相等,而这个类的作者不这样认为,而类的equals方法的实现是由他掌握的。如果你需要使用equals方法,或者使用任何基于散列码的集合(HashSet,HashMap,HashTable),请察看一下java doc以确认这个类的equals逻辑是如何实现的。

问题三:String到底变了没有?

没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。请看下列代码:

String s = “Hello”;
s = s + ” world!”;

s所指向的对象是否改变了呢?从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中,s原先指向一个String对象,内容是”Hello”,然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个String对象,内容为”Hello world!”,原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public class Demo {
private String s;

public Demo {
s = “Initial Value”;
}

}
而非
s = new String(“Initial Value”);
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即StringBuffer。

问题四:final关键字到底修饰了什么?

final使得被修饰的变量”不变”,但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:引用本身的不变,和引用指向的对象不变。

引用本身的不变:
final StringBuffer a=new StringBuffer(“immutable”);
final StringBuffer b=new StringBuffer(“not immutable”);
a=b;//编译期错误

引用指向的对象不变:
final StringBuffer a=new StringBuffer(“immutable”);
a.append(” broken!”); //编译通过

可见,final只对引用的“值”(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。

理解final问题有很重要的含义。许多程序漏洞都基于此—-final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它“永远不变”。其实那是徒劳的。

问题五:到底要怎么样初始化!

本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。
1. 类的属性,或者叫值域
2. 方法里的局部变量
3. 方法的参数

对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。

int类型变量默认初始值为0
float类型变量默认初始值为0.0f
double类型变量默认初始值为0.0
boolean类型变量默认初始值为false
char类型变量默认初始值为0(ASCII码)
long类型变量默认初始值为0
所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象,所以没有初始化的数组引用在自动初始化后其值也是null。

对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细讨论。

对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果在catch或finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初始化了。所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成什么值好,就用上面的默认值吧!

其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入的值就是初始值,所以不需要初始化。

问题六:instanceof是什么东东?

instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。举个例子:

String s = “I AM an Object!”;
boolean isObject = s instanceof Object;

我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为True。
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:

public class Bill {//省略细节}
public class PhoneBill extends Bill {//省略细节}
public class GasBill extends Bill {//省略细节}

在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:

public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//计算电话账单
}
if (bill instanceof GasBill) {
//计算燃气账单
}

}
这样就可以用一个方法处理两种子类。

然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:

public double calculate(PhoneBill bill) {
//计算电话账单
}

public double calculate(GasBill bill) {
//计算燃气账单
}

所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。

2005年02月05日

0.简介
本文介绍Java Web Framework的基本工作原理,和一些常用的开源Web MVC
Framework(Struts, Web Work, Tapestry, Echo, JSF, Maverick, Spring MVC,
Turbine, Cocoon, Barracuda)。

Web开发的最重要的基本功是HTTP;Java Web开发的最重要的基本功是Servlet
Specification。HTTP和Servlet Specification对于Web Server和Web Framework的开发
实现来说,是至关重要的协议规范。

应用和剖析开源Web Framework,既有助于深入掌握HTTP & Servlet Specification, 也
有助于了解一些现代的B/S Web框架设计思想,如MVC,事件处理机制,页面组件,IoC,
AOP等。在这个现代化的大潮中,即使Servlet规范本身也不能免俗,不断引入Filter、L
istener等现代框架设计模式。同是Sun公司出品的JSF更是如此。

关于MVC模型、项目简介、配置文件、入门示例等基础知识,网上已经有大量的重复资料
信息,本文不再赘述。

文中会提到一些相关的开源项目,和一些编程思想,如有需要,可以用相关的关键字在
网上搜索,获取基本的背景知识。

本文力图言简意赅,突出重点。着重描述其他资料没有提到、或很少提到的较重要内容
,如运行原理、主流用法,相关知识,关键特性等。

1. Java Web程序工作原理
Tomcat的Server.xml文件中定义了网络请求路径到主机本地文件路径的映射。比如,<co
ntext path=”/yourapp” docBase=”yourapp_dir/webapp”/>

 

我们来看一下,一个HTTP Request-Response Cycle的处理过程。

HTTP Request URL一般分为三段:host, context, path。

http://yourhost/yourapp/en/index.html这个URL,分为host=yourhost,
context=yourapp, path=en/index.html三段。其中,Context部分由request.getContex
t()获得,path部分由request.getServletPath()获得(返回结果是“/en/index.html”
)。

yourhost主机上运行的Tomcat Web Server接收到这个URL,根据Context定义,把yourap
p这个网络路径映射为yourapp_dir/webapp,并在此目录下定位en/index.html这个文件
,返回到客户端。

 

如果我们这个URL更换为http://yourhost/yourapp/en/index.jsp,这个时候Tomcat会试
图把yourapp_dir/webapp/en/index.jsp文件编译成Servlet,并调用运行这个Servlet。


我们再把这个URL更换为http://yourhost/yourapp/en/index.do。

注意,戏剧化的事情就发生在这个时候,Servlet规范中最重要的类RequestDispatcher
登场了。RequestDispatcher根据WEB-INF/web.xml配置文件的定义,调用对应的Servlet
来处理en/index.do这个路径。

假设web.xml里面有这样的定义。

  <servlet>

    <servlet-name>DispatchServlet</servlet-name>

    <servlet-class>yourapp.DispatchServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>DispatchServlet</servlet-name>

    <url-pattern>*.do</url-pattern>

  </servlet-mapping>

那么,RequestDispatcher会调用yourapp.DispatchServlet类处理这个路径。

如果web.xml没有定义对应en/index.do这个路径的Servlet,那么Tomcat返回“您请求的
资源不存在”。

RequestDispatcher用于Web Server中,也可以用于应用程序中进行处理转向,资源定位
。比如,我们在处理en/index.do的代码中调用,

request.getRequestDispatcher(“cn/index.jsp”).forward(request, response), 就
可以转交另外的资源cn/index.jsp来处理。

 

几乎所有的Web Framework都需要定义自己的Dispatch作用的Servlet,并调用RequestDi
spatcher进行转向处理。

阅读Web Framework源代码,有两条主要线索,(1)根据web.xml找到对应的Servlet类;(
2)搜索包含“RequestDispatcher”词的代码文件。

 

我们看到,request, response  这两个参数,被RequestDispatcher在各种Servlet之间
传来传去(JSP也是Servlet)。所以,request的setAttribute()和getAttribute()方法
是Servlet之间传送数据的主要方式。

在MVC结构中,一般的处理流程如下:

处理HTTP Request的基本单位一般称为Action,是一个比Servlet轻量得多的接口定义,
通常只有一两个方法,如execute(perform), validate等。

我们知道,URL->Servlet映射,定义在Web.xml配置文件里,但MVC框架通常会有另外一
个定义URL-> Action映射的配置文件。

入口Dispatcher Servlet根据URL -> Action的映射关系,把请求转发给Action。

Action获得输入参数,调用商业逻辑,并把结果数据和View标识给(Model & View)返
回给Dispatcher Servlet。

Dispatcher Servlet根据这个View 标识,定位相应的View Template Path,把处理转交
给View(JSP +TagLib, Velocity, Free Marker, XSL等)。

View一般通过request.getAttribute()获得结果数据,并显示到客户端。至于是谁把结
果数据设置到request.attribute里面,有两种可能:Action或Dispatcher Servlet。

2. Struts
http://struts.apache.org/

Struts是目前用户群最大、开发厂商支持最多的开源Web Framework。

Struts劳苦功高,为普及MVC框架作出了不可磨灭的贡献。显赫的声望,趋于老化的厚重
结构,令Struts成为很多现代Web Framework参照、挑战的目标。

 

Struts应用主要包括3件事情: 配置struts-config.xml文件,实现Action类,实现View;
还有一些高级扩展用法。下面分别讲述。

 

1. 配置struts-config.xml文件:

Struts支持多级配置文件,具体用法和限制,详见Struts文档。这里只讨论struts-conf
ig.xml主流配置的内容。:-)

 

(1) URL Path到Action的映射。

如<action path=”/LogonSubmit” type=”app.LogonAction” … />

 

Struts的入口Servlet是ActionServlet。

ActionServlet需要此信息把URL Path调用对应的Action类处理。

在Struts运行期间,一个URL Path,只存在一个对应的Struts Action实例。所有的该UR
L Path的请求,都经过这同一个Struts Action实例处理。所以Struts Action必须线程
安全。

想想看,其实这个要求并不过分,Action只是一个处理程序,不应该保存跨HTTP请求的
状态数据,按理来说,也应该做成线程安全的。

 

(2) Template Name到View Template Path的映射。

<forward name=”success” path=”/pages/Welcome.jsp”/>

 

Action类返回一个Template Name,ActionServlet根据这个Template Name获得对应的Vi
ew Template Path,然后调用

request.getRequestDispatcher(“View Template Path”),把处理转向路径对应的Ser
vlet。在这个例子中,是转向/pages/Welcome.jsp编译后的Servlet。

 

我们来看一个一个Velocity的例子。

<include name=”success” path=”/pages/Welcome.vm”/>

web.xml的定义如下

<servlet>

  <servlet-name>velocity</servlet-name>

<servlet-class>org.apache.velocity.tools.view.servlet.VelocityViewServlet</se
rvlet-class>

</servlet>

<servlet-mapping>

  <servlet-name>velocity</servlet-name>

  <url-pattern>*.vm</url-pattern>

</servlet-mapping>

 

这时,request.getRequestDispatcher(“/pages/Welcome.vm”)会调用VelocityViewSe
rvlet,由VelocityViewServlet负责装并驱动运行/pages/Welcome.vm这个模板文件。

这里面有一个问题,如果调用的是DispatchRequester.include()方法,那么如何才能把
pages/Welcome.vm传给VelocityViewServlet呢?

如前所说,RequestDispatcher传递的参数只有两个,request和response。那么只能通
过request attribute。正是为了解决这个问题,Servlet2.3规范之后,加入了javax.se
rvlet.include.servlet_path这个属性。

参见VelocityViewServlet的代码(velocity-tool开源项目)

// If we get here from RequestDispatcher.include(), getServletPath()

// will return the original (wrong) URI requested.  The following special

// attribute holds the correct path.  See section 8.3 of the Servlet

// 2.3 specification.

String path =
(String)request.getAttribute(“javax.servlet.include.servlet_path”);

 

从这里我们可以看出,为什么通晓Servlet Specification对于通晓Web Framework至关
重要。

 

(3) Form Bean的定义

如<form-bean name=”logonForm” type=”app.LogonForm”/>
Struts Form Bean需要继承ActionForm类。
Form Bean类,主要有三个作用:

[1]根据bean的定义,利用reflection机制,自动把request参数转化为需要的数据类型
,填入到bean的属性当中。ActionForm类名中虽然有Form这个词,但不仅能够获取Form
提交后的HTTP Post参数,也可以获取URL后缀的HTTP Get参数。

[2]输入验证。用户可以配置validation.xml,定义各属性的验证规则。

[3]当作View Object来用。用户需要熟练掌握Struts HTML TagLib的用法,才能把Form
Bean的属性正确显示出来。

 

(4)其他定义。详见Struts文档。不再赘述。

 

2.实现Action。

Action类从Form Bean或直接从request中获得输入参数,调用商业逻辑,把结果数据(
也许会包装成View Object),用request.setAttribute()放到request中,最后返回一
个用ForwardMapping类包装的Template Name。

 

3.实现View。

Struts View的标准实现方法是JSP + Struts TagLib,其中最重要的就是Struts HTML
TagLib。

html:form tag则是整个HTML Tag的核心,其它的如html:input, html:select等tag,都
包含在html:form tag里面。

html:form tag用来映射Form Bean(也可以通过适当定义,映射其他的bean,但使用上
会有很多麻烦)。html:form tag包含的其他Struts html tag用来映射Form Bean的属性


 

Struts Bean TagLib的用法比较臃肿,一般情况下可以用JSTL代替。当然,如果需要用
到bean:message tag实现国际化,那又另当别论。

Struts Tile TagLib用于页面布局。开源Portal项目Liferay使用了Struts Tile
TagLib做为布局控制。

 

4.高级扩展用法

用户可以重载Struts的一些控制类,引入自己的一些定制类。详见Struts文档。

本文不是Struts专题,只讲述最重要的主流用法,其它边边角角的,不再赘述。

3. WebWork
http://www.opensymphony.com/webwork/

WebWork由于灵活的可插拔特性,受到很多资深程序员的欢迎。似乎很有可能大肆流行起
来。

WebWork项目建立在XWork项目上。入口Servlet是WebWork项目中定义的ServletDispatch
er,而Action在XWork项目中定义。

XWork Action接口的execute()方法没有参数,不像Struts Action那样接受request,
response参数,所以XWork Action能够脱离Web环境被直接调用,便于单元测试。

这里引入了一个问题。没有了request参数,那么XWork Action如何获得request
parameters作为输入数据?又通过什么桥梁(Struts用request.setAttribute)把结果
数据传送到View层?

在Web Work中,只能通过Action本身的getter, setter属性来传送输入参数和输出结果


比如,我们有这样一个实现了XWork Action接口的类,

YourAction implements Action{

  int productId = null;

  String productName = null;

 

  public void setProductId(int productId){this.productId = productId;}

  public String getProductName(){return productName;}

 

  public String execute(){

      productName = findNameById(productId);

      return “success”;

  }

}

这个类里面的productId将接受request输入参数,productName是输出到页面显示的结果


比如,这样的请求,http://yourhost/yourapp/MyAction.action?productId=1 

Web Work会把1填到YourAction的productId里面,然后执行execute()方法,JSP里的语
句<ww:property value=“productName”>会把YourAction的productName显示在页面上


 

如果一个Web Framework采用了这种屏蔽Action的request, response参数的设计方式,
一般也同时会采用这种Action和输入输出数据结合成一体的解决方式。类似的情形也存
在于Tapestry和Maverick中,后面会讲到。

当WebWork ServletDispatcher接收到HTTP Request的时候,首先把所有相关的信息(包
括request, response, session, servlet config, servelt context, 所有request参
数)等存放到AcationContext中,然后根据Interceptor配置信息,生成一个YourAction
的动态代理类对象。实际上运行的正是这个代理对象,如同Servlet Filter的工作机制
一般,所有注入的Interceptor方法会先于Actio方法运行。

我们来看一下Action和Interceptor的地位:Action没有参数,无法获得ActionContext
;而Interceptor接受的ActionInvoication参数拥有包括ActionContext在内的所有重要
信息。

这种权力分配的不平等,注定了Action的作用非常有限,只限于调用商业逻辑,然后返
回一个成功与否标志。所有与外部Web世界打交道、协调内部工作流程的重担,都责无旁
贷地落在Interceptor的肩上。

我们可以设想一个极端的例子。我们声明一批不做任何事情的空Action,我们只是需要
它们的空壳类名;我们制作一批对应的Interceptor,所有的转发控制、商业逻辑都在In
terceptor上实现,然后把Interceptor都注入到对应的空Action。这在理论上是完全可
行的。

在Web海洋的包围中,Action可少,Interceptor不可少。Action是一个孤岛,如果没有外
来盟友Interceptor的协助,只能在自己的小范围内独立作战(比如Unit Test),而对
整体大局的作战目标无法产生影响。

下面我们来看一下Action是如何在Interceptor的全程监管下工作的。

 

在WebWork中,我们需要如下配置XWork.xml。

<xwork>

<!– Include webwork defaults (from WebWork-2.1 JAR). –>

<include file=”webwork-default.xml” />

 

<!– Configuration for the default package. –>

<package name=”default” extends=”webwork-default”>

    <!– Default interceptor stack. –>

    <default-interceptor-ref name=” defaultStack” />

 

    <!– Action: YourAction. –>

    <action name=”youraction” class=”yourapp.YourAction”>

        <result name=”success” type=”dispatcher”>

YourAction.jsp

</result>

</action>

</package>

</xwork>

 

webwork-default.xml里面的相关定义如下:

<interceptors>

<interceptor name=”validation”
class=”com.opensymphony.xwork.validator.ValidationInterceptor”/>

 

<interceptor name=”static-params” class=”com.opensymphony.xwork.interceptor.


StaticParametersInterceptor”/>

<interceptor name=”params”
class=”com.opensymphony.xwork.interceptor.ParametersInterceptor

“/>

<interceptor name=”conversionError”
class=”com.opensymphony.webwork.interceptor.


WebWorkConversionErrorInterceptor”/>

<interceptor-stack name=”defaultStack”>

    <interceptor-ref name=”static-params”/>

    <interceptor-ref name=”params”/>

    <interceptor-ref name=”conversionError”/>

</interceptor-stack>

</interceptors>

 

从上述的配置信息中可以看出,YourAction执行execute()方法的前后,会被

defaultStack所定义的三个Intercepter截获。这些Interceptor的任务之一就是把输入
参数设置到Action的对应属性当中。

如果我们需要加入对YourAction的属性的验证功能,只要把上述定义中的validation
Interceptor加入到defaultStack中就可以了。当然,实际工作还没有这么简单,一般来
说,还要为每个进行属性验证的Action的都配置一份validation.xml。

XWork Interceptor能够在Package和Action级别上,进行截获处理。

Servlet Filter能够在URL Patten级别上,进行截获处理。虽然实际上,Servlet
Filter截获的是Servlet,但某些情况下,可以达到和截获一批Action的同样效果。

比如,在Web Work中,我们可以为所有admin package的Action,加入一个Interceptor
,当检查到当前Session的用户没有admin权限时,统一返回一个警告页面:您没有足够
的权限执行这个操作。

我们看到也可以为所有URL Pattern为“admin/*.action”的URL定义一个Servlet
Filter,当检查到当前Session的用户没有admin权限时,统一返回一个警告页面:您没
有足够的权限执行这个操作。

 

WebWork的Interceptor配置是相当灵活的,相当于对Action实现了AOP。Interceptor相
当于Aspect,基类AroundInterceptor的before(), after()方法相当于Advice。

另外,XWork也提供了从XML配置文件装配Component的机制,相当于实现了对于Componen
t的IoC。

提到AOP和IoC,顺便多讲两句。Spring AOP能够截获所有Interface,不限于某个特定接
口;Spring框架支持所有类型的IoC,不限于某种特定类型。

 

要知道,AOP, IoC可是现在最时髦的东西,一定不要错过啊。:D

相关概念导读(如果需要,请用如下关键字搜索网络):

AOP — Aspect Oriented Programming — 面向方面编程。

IoC – Inversion of Control –控制反转

Dynamic Proxy — 动态代理,JDK1.4引入的特性。还可以进一步参考CGLib, ASM等开源
项目。

 

WebWork直接支持所有主流View — XSL,Velocity, FreeMarker,JSP。WebWork还提供了
自己的TagLib。“直接支持”的意思是说,不用像Struts那样,使用Velocity的时候,
还需要引入辅助桥梁Velocity-tool。

WebWork中用到一种功能和XPath类似的对象寻径语言ONGL,是一个开源项目。ONGL同样
用在下面要介绍的Tapestry项目中。

Opensymphony下还有一个SiteMesh项目,通过Servlet Filter机制控制布局。可以和Web
Work组合使用。

 

4. Tapestry
http://jakarta.apache.org/tapestry/

Tapestry近来突然火了起来,令我感到吃惊。也许是JSF带来的Page Component风潮令人
们开始关注和追逐Tapestry。

Tapestry的重要思想之一就是Page Component。

前面讲到,XWork能够自动把request参数映射到Action的属性当中。Tapestry走得更远
,甚至能够根据request参数,映射到Action(Tapestry里面称为Page)的方法,并把re
quest参数映射为Page方法需要的参数,进行正确的调用。就这样,Tapestry不仅把输入
输出数据,而且把事件方法也绑定到了Page上面。

在Tapestry框架中,Action的概念已经非常模糊,而换成了Page的概念。而Tapestry
Page是拥有属性和事件的页面组件,其中的事件处理部相当于Action的职责,而属性部
分起着Model的作用。

除了使用Page和其它的Tapestry页面组件,用户也可以自定义页面组件。

 

这种页面组件/属性事件的编程模型,受到一些程序员的欢迎。当然,这种编程模型并不
是没有代价的,每个Tapestry模板文件都需要一个对应的.page文件。这些.page文件定
义了页面组件的属性、事件、Validator等信息。

 

我们来看一下B/S结构中,组件的属性、事件和HTTP Request绑定的基本原理。一个能够
发出请求的页面组件(比如Link和Button),在输出自己的HTML的时候,需要输出一些
特殊的信息来标志本组件的属性/事件,这样下次HTTP Request来的时候,会把这些信息
带回来,以便Web Framework加以辨认识别,发给正确的Page Component处理。

这些特殊信息通常包含在URL参数或Hidden Input里面,必要的时候,还需要生成一些Ja
va Script。Tapestry,Echo,JSF都是这种原理。

Tapestry的例子如下:

<a href=”#” jwcid=”@DirectLink” parameters=”ognl:currentItem.itemId”
listener=”ognl:listeners.showItem”>

JSF用TagLib实现页面组件,也提供了类似的CommandLink和CommandButton Tag。其中对
应Tapestry listener的Tag属性是action。后面会讲解。

 

Tapestry的模板标签是HTML标签的扩展,具有良好的“所见即所得”特性,能够直接在
浏览器中正确显示,这也是Tapestry的一个亮点。

5. Echo
http://sourceforge.net/projects/echo

Echo提供了一套类似Swing的页面组件,直接生成HTML。

从程序员的角度看来,用Echo编写Web程序,和用Swing编写Applet一样,属于纯面向组
件事件编程,编程模型也以Event/Listener结构为主体。

Echo没有Dispatcher Servlet,也没有定义URL->Action映射的配置文件。

Echo的Action就是实现了ActionListener接口(参数为ActionEvent)的Servlet(继承E
choServer类)。

所以,Echo直接由Web Server根据web.xml配置的URL -> Servlet的映射,进行转发控制


 

Echo也没有明显的View层,Echo在页面组件方面走得更远,所有的HTML和JavaScript都
由框架生成。你不必(也没有办法)写HTML,只需要(也只能)在Java代码中按照类似S
wing编程方式,生成或操作用户界面。用户也可以定制自己的Echo组件。

Echo的UI Component的实现,采用了两个重要的模式。一个是Peer(Component ->
ComponentPeer)模式,一个是UI Component -> Renderer模式。

虽然Echo的API更类似于Swing,但实现上却采用更接近于AWT的Peer模式。每个Componen
t类(代表抽象的组件,比如Button),都有一个对应的ComponentPeer类(代表实际的
组件,比如windows桌面的Button,Linux桌面的Button,HTML Button等)。

先别急,这个事情还没有完。虽然ComponentPeer落实到了具体的界面控件,但是它还是
舍不得显示自己,进一步把显示工作交给一个Renderer来执行。

比如,在Echo里面,Button类对应一个ButtonUI(继承了ComponentPeer)类,而这个Bu
ttonUI类会把最终显示交给ButtonRender来处理。

据说多了这么一步,能够让显示控制更加灵活丰富。比如,同一个Renderer可以处理不
同的UI Component,同一个UI Component也可以交给不同的Renderer处理。

JSF的页面组件也采用了UI Component -> Renderer模式,后面会讲到。

6. JSF
http://java.sun.com/j2ee/javaserverfaces/index.jsp

http://wwws.sun.com/software/communitysource/jsf/download.html download
source

 

JSF的中心思想也是页面组件/属性事件。一般来说,JSF的页面组件是一个三件套{ UI
Component, Tag, Renderer}。

UI Component有可能对应Model,Event,Listener。Tag包含componentType和rendererT
ype两个属性,用来选择对应的的UI Component和Renderer。

JSF的应用核心无疑是JSF TagLib。JSF TagLib包含了对应所有重要HTML元素的Tag,而
且Input Tag可以直接包含Validator Tag或者Validator属性,来定义验证手段。

 

我们通过JSF携带的cardemo例子,来看JSF的处理流程。

(1) carDetail.jsp有如下内容:

<h:commandButton action=”#{carstore.buyCurrentCar}” value=”#{bundle.buy}” />

可以看到,这个button的submit action和carstore.buyCurrentCar方法绑定在一起。我
们在Tapestry里面曾经看到过类似的情景。

 

(2) carstore在faces-config.cml中定义:

  <managed-bean>

    <managed-bean-name> carstore </managed-bean-name>

    <managed-bean-class> carstore.CarStore </managed-bean-class>

    <managed-bean-scope> session </managed-bean-scope>

  </managed-bean>

 

(3) carstore.CarStore类中的buyCurrentCar方法如下:

    public String buyCurrentCar() {

        getCurrentModel().getCurrentPrice();

        return “confirmChoices”;

    }

 

(4) confirmChoices转向在faces-config.cml中定义:

  <navigation-rule>

    <from-view-id>/carDetail.jsp</from-view-id>

    <navigation-case>

      <description>

        Any action that returns “confirmChoices” on carDetail.jsp should

        cause navigation to confirmChoices.jsp

      </description>

      <from-outcome>confirmChoices</from-outcome>

      <to-view-id>/confirmChoices.jsp</to-view-id>

    </navigation-case>

  </navigation-rule>

 

(5)于是转到页面confirmChoices.jsp。

 

除了Interceptor之外,JSF几乎包含了现代Web Framework应该具备的所有特性:页面组
件,属性事件,IoC (ManagedBean),Component -> Renderer,类似于Swing
Component的Model-Event-Listener。

也许设计者认为,众多庞杂的模式能够保证JSF成为一个成功的框架。Portal开源项目eX
o就是建立在JSF框架上。

 

可以看出这样一个趋势,现代Web Framework认为B/S结构的无状态特性和HTML界面是对
编程来说是需要极力掩盖的一个缺陷,所以尽量模拟C/S结构的组件和事件机制,以吸引
更多的程序员。

7. Maverick
http://mav.sourceforge.net/

Maverick是一个轻量而完备的MVC Model 2框架。Maverick的Action不叫Action,直截了
当的称作Controller。

Controller只接受一个ControllerContext参数。request,response, servlet
config, servelt context等输入信息都包装在ControllerContext里面,而且Model也通
过ControllerContext的model属性返回。整个编程结构清晰而明快,令人赞赏。

但这个世界上难有十全十美的事情,由于ControllerContext只有一个model属性可以传
递数据,程序员必须把所有需要的数据都打包在一个对象里面设置到model属性里。这种
麻烦自然而然会导致这样的可能用法,直接把Controller本身设置为model,这又回到了
Controller(Action)和Model一体的老路。

 

前面讲到,WebWork也把所有的输入信息都包装在ActionContext里面,但Action并没有
权力获取。而在Maverick中,Controller对于ControllerContext拥有全权的控制,两者
地位不可同日而语。当然,由于参数ControllerContext包含request,reponse之类信息
,这也意味着,Maverick Controller不能像WebWork Action那样脱离Web环境独立运行


当然,这也并不意味着任何结构性缺陷。程序的结构由你自己控制,你完全可以把需要U
nit Test的那部分从Web环境脱离开来,放到Business层。

如同WebWork,Maverick直接支持所有的主流View。Maverick的配置文件采Struts,
Cocoon两家之长,URL -> Action -> View映射的主体结构类似于Struts,而View定义部
分对Transform的支持则类似于Cocoon。如:

<command name=”friends”>

<controller class=”org.infohazard.friendbook.ctl.Friends”/>

<view name=”success” path=”friends.jsp”>

        <transform path=”trimInside.jsp”/>

</view>

</command>

8. Spring MVC
http://www.springframework.com/

Spring MVC是我见过的结构最清晰的MVC Model 2实现。

Action不叫Action,准确地称做Controller;Controller接收request, response参数,
干脆利落地返回ModelAndView(其中的Model不是Object类型,而是Map类型)。

其它的Web Framework中, Action返回值一般都只是一个View Name;Model则需要通过
其它的途径(如request.attribute,Context参数,或Action本身的属性数据)传递上
去。

 

Spring以一招IoC名满天下,其AOP也方兴未艾。“Spring出品,必属精品”的观念已经
深入人心。我这里多说也无益,强烈建议读者去阅读Spring Doc & Sample & Code本身


9. Turbine
http://jakarta.apache.org/turbine/

Turbine是一个提供了完善权限控制的坚实框架(Fulcrum子项目是其基石)。Turbine的
个人用户不多,但不少公司用户选择Turbine作为框架,开发一些严肃的应用(我并没有
说,用其它框架开发的应用就不严肃^_^)。Portal开源项目JetSpeed建立在Turbine上


Turbine用RunData来传递输入输出数据。如同Maverick的ControllerContext,RunData
是整个Turbine框架的数据交换中心。除了request, response等基本信息,RunData直接
包括了User/ACL等权限控制相关的属性和方法,另外还包括Action Name和Target
Template Name等定位属性。

Module是Turbine里面除了RunData之外的又一个核心类,是Turbine框架的基本构件,Ac
tion是Module,Screen也是Module。Turbine提供了LoginUser和LogoutUser两个Action
作为整个系统的出入口。而其余流量的权限控制则由类似于Servlet Filter机制的Pipel
ine控制。

Turbine Pipeline的编程模型和Servlet Filter一模一样:Turbine Pipeline的Valve就
相当于Servlet Filter,而ValveContext则相当于Filter Chain。还有更相近的例子,T
omcat源代码里面也有Valve和ValueContext两个类,不仅编程模型一样,而且名字也一
样。

 

权限控制贯穿于Turbine框架的始终。要用好Turbine,首先要通晓子项目Fulcrum 的Sec
urity部分的权限实现模型。

Fulcrum Security的权限实体包括四个– User, Group, Role, Permission。

实体之间包含{Role,Permission}和{ Group, User, Role}两组关系。

{Role,Permission}是多对多的关系,一个Role可以具有各种Permission;{ Group,
User, Role}之间是多对多的关系,一个Group可包含多个User,并可以给User分配不同
的Role。

权限模型的实现同样采用Peer模式,Entity -> EntityPeer, Entity -> ManagerPeer。


Entity和EntityManger代表抽象的模型概念,而EntityPeer和ManagerPeer代表具体的实
现。

用户可以根据模型,提供不同的实现,比如,用内存结构中实现,用数据表结构实现,
与Windows NT权限验证机制结合,与OSWorkflow的权限控制模型结合,等等。其中,用
数据表结构实现,又可以选择用Torque实现,或者用Hibernate实现。(Torque是Turbin
e的O/R Mapping子项目)

 

例如,Falcrum.property配置文件包含如下Security相关选项:

# ——————————————————————-

#  S E C U R I T Y  S E R V I C E

# ——————————————————————-

services.SecurityService.user.class=org.apache.fulcrum.security.impl.db.entit
y.TurbineUser

services.SecurityService.user.manager=org.apache.fulcrum.security.impl.db.DBU
serManager

services.SecurityService.secure.passwords.algorithm=SHA

# ——————————————————————-

#  D A T A B A S E  S E R V I C E

# ——————————————————————-

services.DatabaseService.database.newapp.driver=org.gjt.mm.mysql.Driver

services.DatabaseService.database.newapp.url=jdbc:mysql://127.0.0.1/newapp

services.DatabaseService.database.newapp.username=turbine

services.DatabaseService.database.newapp.password=turbine

 

这说明,权限控制实现由数据库提供,需要根据权限模型创建如下数据表:

TURBINE_USER,TURBINE_ROLE,TURBINE_GROUP,

TURBINE_PERMISSION,TURBINE_ROLE_PERMISSION,

TURBINE_USER_GROUP_ROLE。

 

10. Cocoon
http://cocoon.apache.org

Cocoon项目是一个叫好不叫做的框架。采用XML + XSLT Pipeline机制,Java程序只需要
输出XML数据,Cocoon框架调用XSL文件把XML数据转换成HTML、WML等文件。

Cocoon强大灵活的XSL Pipeline配置功能,XSLT的内容/显示分离的承诺,一直吸引了不
少程序员fans。怎奈天不从人愿,由于复杂度、速度瓶颈、XSL学习难度等问题的限制,
Cocoon一直主要限于网站发布出版领域,向CMS和Portal方向不断发展。另外,Cocoon开
发了XSP脚本和Cocoon Form技术。

Cocoon的sitemap.xmap配置文件比较复杂,与其它的Web Framework差别很大。

主体Pipelines配置部分采用Pattern Match的方式,很像XSL语法,也可以类比于Web.xm
l里面Servlet Mapping的定义。比如,一个典型的URL->Action的映射定义看起来是这个
样子:

<map:pipelines>

<map:pipeline>

<map:match pattern=”*-dept.html”>

  <map:act set=”process”>

    <map:parameter name=”descriptor”

                   value=”context://docs/department-form.xml”/>

    <map:parameter name=”form-descriptor”

                   value=”context://docs/department-form.xml”/>

    <map:generate type=”serverpages” src=”docs/confirm-dept.xsp”/>

    <map:transform src=”stylesheets/apache.xsl”/>

    <map:serialize/>

  </map:act>

  <map:generate type=”serverpages” src=”docs/{1}-dept.xsp”/>

  <map:transform src=”stylesheets/apache.xsl”/>

  <map:serialize/>

</map:match>

</map:pipeline>

</map:pipelines>

11. Barracuda
http://barracudamvc.org/Barracuda/index.html

Barracuda是一个HTML DOM Component + Event/Listener结构的框架。

根据模板文件或配置文件生成静态Java类,并在代码中使用这些生成类,是Barracuda的
一大特色。

Barracuda需要用XMLC项目把所有的HTML或WML模板文件,静态编译成DOM结构的Java类,
作为页面组件。XMLC会根据HTML元素的id定义,生成相应DOM结点的简便操作方法。

 

Barracuda的事件类也需要用Barracuda Event Builder工具把event.xml编译成Java类,
引入到工程中。Barracuda直接用Java类的继承关系映射事件之间的父子层次关系。比如
,ChildEvent是ParentEvent的子类。

Barracuda的事件分为两类:Request Events(Control Events)和Response Events(V
iew Events)。

 

Barracuda事件处理过程很像Windows系统消息队列的处理机制。

(1) Barracuda根据HTTP Request生成Request Event,放入到事件队列中。

(2) EventDispatcher检查事件队列是否为空,如果为空,结束。如果非空,按照先进先
出的方式,从事件队列中取出一个事件,根据这个事件的类型,选择并调用最合适的Eve
ntListener,参数Event Context包含事件队列。

 “根据事件类型,选择最合适的EventListener对象”的过程是这样的:比如,

EventDispatcher从时间队列里取出来一个事件,类型是ChildEvent;Barracuda首先寻
找注册了监听ChildEvent的EventListener,如果找不到,再上溯到ChildEvent的父类Pa
rentEvent,看哪些EventListener对ParentEvent感兴趣。

详细过程参见Barracuda的DefaultEventDispatcher类。

(3) EventListener根据Event Context包含的request信息,调用商业逻辑,获得结果数
据,然后根据不同情况,把新的事件加入到Event Context的事件队列中。

(4) 控制交还给EventDispatcher,回到第(2)步。

 

The End.

Enjoy.