IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Web development | WebSphere | Java technology  >

利用 IBM Web 2.0 Feature Pack 创建 Ajax 风格的架构

向现有的 J2EE 应用程序添加 Ajax 风格的架构

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

讨论

英文原文

英文原文


级别: 初级

Kevin Haverlock (kbh@us.ibm.com), 软件开发人员, IBM

2008 年 4 月 15 日

本文展示了如何借助 IBM® WebSphere® Application Server Feature Pack for Web 2.0 以 Ajax 风格的架构增强 Java™ 2 Platform, Enterprise Edition (J2EE) 应用程序。了解如何在不重写整个 Web 应用程序的情况下,将 Ajax 风格的架构与现有的应用程序结合起来。此外,您还会了解如何将 Web 2.0 Feature Pack 应用到面向 IBM WebSphere Application Server 的 J2EE 应用程序中。

“Plants by WebSphere” 应用程序是随 IBM WebSphere Application Server Feature Pack for Web 2.0 提供的诸多示例程序中的一个。此应用程序演示了一个典型的 J2EE 应用程序以及如何在不重写整个应用程序的前提下用 Ajax 风格的架构对其进行增强。此示例应用程序虚拟了一个在线植物商店,在该商店中,顾客可以订购和购买鲜花、树、植物和其他附件。图 1 展示了此 Web 应用程序的首页:


图 1:Plants by WebSphere Web 应用程序
Plants by WebSphere Web 应用程序

图 2 展示了此应用程序在添加 Ajax 风格特性之前的最初架构。该架构的设计初衷是为了代表一种典型的运行于 WebSphere Application Server 上的 J2EE 应用程序。在高层,此应用程序遵从的是一种 Model-View-Controller (MVC) 设计模式,这也是很多 Web 应用程序都不同程度采用的模式。浏览器访问该应用程序的 URL,并返回一个由 JSP 呈现的 HTML 页面。浏览器向该应用程序发出额外请求,servlet 用来控制由用户购买请求驱动的流程。Enterprise JavaBeans (EJB) 则用来服务于数据库上可用的模型数据。


图 2:典型的 Web 架构
典型的 Web 架构

如下所示的 图 3 显示了应用程序的最初架构是如何使用 Ajax 扩展的。其目的是不重写应用程序,只利用 IBM Feature Pack 内的技术来改善和创建更加交互的丰富用户体验。

在浏览器端,应用程序使用 JavaScript Dojo Toolkit 提供的小部件。此外,创建定制用户界面小部件是为了提高 Plants by WebSphere 的交互性,而同时又不重写它。定制用户界面小部件是异步的,这意味着它们使用受 Dojo Toolkit 支持的浏览器的 XHR 机制进行通信。这些小部件使用一种 XML 交换格式来与服务器交换数据。在服务器端,随 Feature Pack 提供的 RPCAdapter 被用来将 EJB 数据转换成 XML 交换格式,这种格式极易由浏览器上新创建的小部件使用。

Ajax 应用程序的特点是用户界面上的响应有所改进。Plants by WebSphere 在 Web 浏览器内使用 Dojo Toolkit 来改进应用程序的用户界面(UI)。Dojo Toolkit 是纯 JavaScript,而 JavaScript 文件可直接托管于 Web Archive File(WAR)的 Web-Content 目录内,也可以作为静态 Web 内容存在于性能优化了的内容发布网络上。比如,Dojo Toolkit JavaScript 文件就作为 WAR 的一部分被托管。

Dojo Toolkit 支持声明式或过程式使用。在声明式使用中,可将计划使用的 JavaScript 直接嵌入到 HTML 内容内。Dojo Toolkit 包含大量小部件,可用来在 HTML 内声明式使用,减少了手工编写函数的需要。在 Plants by WebSphere,Dojo Toolkit 小部件直接嵌入在 JSP 页面内。


图 3:由 Ajax 增强了的架构
由 Ajax 增强了的架构

示例:Web 表单处理

Web 应用程序中的一种常见场景是表单处理,用户通过表单将数据输入到 Web 页面内,数据可以是用户的名字、地址、首选项等。信息然后再发送回服务器进行处理,结果返回给用户。表单上的一个常见行为是进行验证以确保用户正输入的内容的格式是一种可接受的格式。输入字母的地方是不是输入了数字?输入的邮编是否有效?Dojo Toolkit 提供了一组丰富的表单处理验证功能,可添加到 Web 页面。如下所示的清单 1 是用于 Plants by WebSphere 应用程序中的一个示例。它代表了如何声明式地在 Web 应用程序中使用 Dojo Toolkit 的典型示例。

首先在 HTML 页面声明 Dojo 的使用。参见清单 1,第一个声明 dojo.js. Dojo.js 的使用的脚本标记是核心 Dojo Toolkit 内核,这是使用 Dojo Toolkit 所必需的。第二个 <script> 标记声明了页面使用的 Dojo 小部件。dojo.declare 子句对比了 Java import 或 C++ includes 子句。此语句告知 dojo.parser,在何处能找到适合页面使用的 Dojo Toolkit JavaScript 文件。对于清单 1 中的示例,页面使用的是 dijit.form.ValidationTextBox 小部件。此外,JavaScript dojo.parser 也需要包括进来。此解析器用来扫描小部件要声明的页面。后面的清单 2 将会具体解释其中的原因。


清单 1:完整长度的示例代码清单
                
       
<script type="text/javascript"
        src="/PlantsByWebSphereAjax//dojo/dojo.js"
	  djConfig="isDebug: false, parseOnLoad: true, 
        extraLocale: ['de-de', 'en-us']">
</script>
		
<script type="text/javascript">
        dojo.require("dijit.form.ValidationTextBox");
	  dojo.require("dojo.parser");// scan page for widgets and
                                       // instantiate them
</script>

清单 1 展示了 Dojo Toolkit 是如何在 JavaScript 中声明的以及页面将要使用的小部件的类型。清单 2 展示了小部件是如何在页面声明的。清单 1 中声明了的 dojo.parser 将用来解析 HTML 页面。当解析器发现小部件后,就会调用小部件构造函数代码。结果是会在页面的 Document Object Model (DOM) 内创建验证小部件代码。


清单 2:声明使用 Dojo ValidationTextBox 小部件
                
<TD width="100%">
   <P>
      <input type="text" id="sname" name="_sname" class="medium"
             dojoType="dijit.form.ValidationTextBox"
		 propercase="true"
		 required="true"
		 promptMessage="Enter Name" />
   </P>
</TD>

客户端身份验证
客户端身份验证不应该是您惟一的用户身份验证方式。应该采取相信但需验证的方式:相信客户机会返回结果,但需要验证这是您所希望的。常见的策略包括服务器端针对预期输入的白名单以及模式匹配。

图 4 给出了结果。当用户没有在要求字段输入值时,浏览器会自动通知用户。


图 4:帐单地址使用了 Dojo 的验证小部件
帐单地址使用了 Dojo 的验证小部件

表单验证展示了如何在现有 J2EE 应用程序开始使用 Ajax。它同时也有力地展示了用户界面如何能在不重写任何现有代码的情况下轻松地改进。

还有其他许多的示例都说明了使用 Dojo Toolkit 的声明式编程表单如何能用在 J2EE 应用程序内。Dojo 在 Dojo Toolkit 的测试目录内存有大量示例应用程序。这些应用程序将通过打开 HTML 页面在浏览器内运行。





回页首


创建定制小部件

另一个有趣的示例是创建定制 JavaScript 小部件。Dojo 提供了一种功能强大的框架来创建新式的可直接嵌入到 HTML 页面内的小部件。

图 5 展示了用于 Plants by WebSphere 应用程序的目录浏览页面。当用户单击其中一个图像时,目录页就会在顶部显示条目的图片,在底部显示每个条目的详细信息。


图 5: Plants by WebSphere 的目录页
Plants by WebSphere 的目录页

虽然此页包含了大量不同的小部件,我们只重点看看其中的 itemDetails 小部件,如图 6 底部所示。itemDetails 小部件从服务器获取信息并将信息显示在细节窗口内。用户可以向购物篮中添加条目或使用鼠标按钮将其拖动到购物篮内。当用户单击顶部的某个目录条目时,小部件就会从服务器获取信息。现在我们要看看 itemDetails 小部件是如何创建的。


图 6:itemDetails 小部件
itemDetails 小部件

在使用 Dojo Toolkit 创建小部件时,有几个文件必须考虑在内。前两个文件是 HTML 和 CSS 模板文件。这两个文件会被用来定义当小部件在浏览器内呈现时,页面的主体外观。HTML 页将包含声明了的附着点(attachment point),这些附着点正是小部件插入 DOM 元素的位置。附着点称为 DojoAttachPoint

清单 3 显示了 itemDetail 小部件的 HTML 模板和用于表行的 DojoAttachPoint。正如 JavaScript 在定制的小部件内处理内容一样,它将动态创建元素并将这些元素插入到附着点中。


清单 3:itemDetail.html Dojo 模板内的 DojoAttachPoint
                
<div>
<table cellpadding="2" cellspacing="2" border="0" width="100%" 
       height="100%">
   <tr width="100%" height="100%">
	<td valign="top" align="left" width="75%">
	   <span class="itemNameText" 
               dojoAttachPoint="nameElement"></span>

         <span dojoAttachPoint="descElement" 
               class="itemDescText"></span><br><br>

         <table cellspacing="0" cellpadding="0" width="100%" 
                height="100%">
		<tr style="width: 80%">
		   <td valign="top" align="left">
			 <span style="font-family:verdana,arial,
                                sans-serif;
                                font-size:11px;">Price:</span>
               </td>
               <td valign="top">
                  <span class="itemNameText" >$
                  <span dojoAttachPoint="priceElement">  
                  </span></span>
               </td>
               <td valign="top" align="right">
                  <button dojoType="dijit.form.Button"  
                   dojoAttachEvent="onclick: addToCart">
                   Add to cart</button>
               </td>
		</tr>
          </table>
	. . . . .
      . . . . .
      // Additional content 
      . . . . .
      . . . . .
	</table>
</div>

小部件模板和 CSS 定义了小部件的外观并确定了更新在此 DOM 的何处发生。下一步是将 JavaScript 代码添加到能实现此功能的小部件。

清单 4 显示了此定制 itemDetail 小部件的开始部分。此代码首先声明了要被创建的小部件:dojo.provide("ibm.widget.ItemDetails");。此名称用来引用新创建的小部件。

dojo.provide 之下的是 dojo.declaredojo.declare 定义了此小部件以及该小部件可能具有的所有继承。在本例中,此段代码声明了 base dijit._Widgetdijit._Templated 的继承。由于这里的小部件只是一个模板化了的小部件,此段代码声明了 templatePath。模板路径定义了上述定义的模板的位置。dojo.moduleUrl 是一个实用函数,用来解析模板的位置。

在代码接下来的部分,声明了在清单 3 中定义的 DojoAttachPoint 那些声明。这些声明将是被更新的 DOM 引用的根节点。在本例中,除了其他几个变量之外,还将声明 nameElementdescElement 。在本文的下一章节,将会详细介绍这些元素如何被修改。


清单 4:声明 itemDetails 小部件
                

dojo.provide("ibm.widget.ItemDetails");

dojo.declare(
    "ibm.widget.ItemDetails",
    [dijit._Widget, dijit._Templated],
    {
	initializer: function(){ },
	templatePath:
      dojo.moduleUrl("ibm","widget/templates/ItemDetails.html"),
	isContainer: false,
	
	// your DOM nodes declared as DojoAttachPoint in the
      // Template:
	nameElement: null,
	descElement: null,
	imageElement: null,
	priceElement: null,

     // Additional Code
     . . . . 
     . . . .

要想让此小部件真的有趣起来,需要使用数据填充它。细节小部件所需的数据位于服务器上。此时,您也许会困惑如何才能让描述数据呈现在页面内。

Dojo Toolkit 提供了一种功能强大的 I/O 传输机制,可用来向服务器发送 XMLHttpRequests (XHR) 请求并处理结果。XHR API 可在浏览器内打开一个单独的信道。XHR 是使 Ajax 获得交互性的核心所在。

清单 5 给出了 dojo.xhrGet 函数的使用以便从服务器检索数据。Dojo Toolkit 用其自身的 API 包装 XHR API,这让处理 XHR API 变得更为简单。url: 参数定义了服务器的地址。?p0="+item.id 则是传递到服务器的一个 URL 参数。超时定义了要等多长时间才能放弃连接到服务器。header 定义了应该应用到发送给服务器的请求的额外的 HTTP 报头。 handleAs 参数告知 Dojo Toolkit 该如何处理响应。在本例中,所期望的响应是 XML 代码。

deferred.addCallback(function(response) 是回调函数,可在响应从服务器返回后由 Dojo Toolkit 调用。前一节将 DojoAttachPoint 定义成了要被修改的 DOM 节点的位置。descElement 是在这里修改的 DOM 节点之一。self.descElement.innerHTML 值被设置成以 XML 形式返回的描述文本。descElement 是之前在小部件的 Template 文件中声明的 DOM 节点(参见 清单 3)。


清单 5:到服务器的 XHR GET 请求
                

var deferred = dojo.xhrGet( {
    url:      self.url+"?p0="+item.id,
    timeout:  5000,
    headers:  { "Content-Type":"text/html" }, 
    handleAs: "xml"
} );
		
deferred.addCallback(function(response){

     var itemElement = 
         response.getElementsByTagName( "entry" )[0];
		 
     self.descElement.innerHTML =  
         itemElement.getElementsByTagName( "description" )[0].firstChild.nodeValue;

     self.hiddenData.setAttribute("itemname",  item.name);
     self.hiddenData.setAttribute("itemprice", item.price);
     self.hiddenData.setAttribute("itemid",    item.id);

     . . . . . 

     return response;
   });


在创建定制 Dojo 小部件时,需要将其插入到 HTML 页面内以便使用。图 6 展示了此页面的外观。该清单来自于 flowers.html 文件。清单 6 给出了页面背后的一部分 HTML 代码。DIV 标记用来嵌入小部件,关键字 dojoType 用来声明 itemDetails 小部件。还有额外的参数以 url 格式传递给小部件 — 对服务器上的远端过程调用适配器(Remote Procedure Call Adapter,RPCAdapter)的 URL 请求。您可能还记得,itemDetails 小部件会向服务器发送 XHR 请求以便检索要显示的数据。服务器端的代码以及 PRC 适配器的使用将在下一节介绍。


清单 6:在 HTML 页面内声明 itemDetails
                

<body style="background: #FFF;">
   . . . . 
   . . . . //additional HTML 
   . . . .        
    
<div dojoType="ibm.widget.ItemDetails"  
        url="/PlantsByWebSphereAjax/servlet/RPCAdapter/httprpc/Sample/detailRequest" 
style="width: 100%;" 
itemTopic="itemdetails_flowers"></div>

</body>





回页首


服务器端

至此,您已经看到了定制小部件如何在 Dojo 创建以及如何准备让这个小部件向服务器发出 XHR 请求。现在,我们来看看服务器是如何处理这些请求的。

在 Plants by WebSphere 应用程序,小部件向服务器发出 XHR 请求以便获得某些数据。servlet 读取此请求并查找正确的 Enterprise JavaBean (EJB)。此 EJB 随后被调用,EJB 容器向数据库发出请求,数据库返回结果。结果需要被编码成 XML 并返回给浏览器上的 JavaScript 小部件。

在本文的场景中,随 IBM Feature Pack for WebSphere Application Server 提供的 RPC 适配器(RPCAdapter)将被用来连接 Plants by WebSphere EJB。

RPCAdapter 运行时 JAR 包括在 Plants by WebSphere 应用程序内并使用位于 WAR 文件的 WEB-INF 目录中的 XML 文件进行配置。清单 7 显示了一个示例配置。


清单 7:RpcAdapterConfig.xml
                

<?xml version="1.0" encoding="UTF-8"?> 
<rpcAdapter> 
   <default-format>xml</default-format> 
   <services> 
       <pojo> 
          <name>Sample</name>      

<implementation>
   com.ibm.websphere.samples.plantsbywebspherewar.RpcConnecter
</implementation>
 
          <methods filter="whitelisting">
            <method>
               <name>detailRequest</name>
               <description>
                     returns back detail information for an item 
               </description>
               <http-method>GET</http-method>
                   <parameters>
                        <parameter>
                            <name source="request">message</name>
                            <description>
                               Contains the message to be returned
                            </description>  
                        </parameter>
                   </parameters>
            </method>
          </methods>
       </pojo>

   </services> 
</rpcAdapter> 


我们来仔细看看 RpcAdapterConfig.xml 文件。由 RPCAdapter 返回的 <default-format> 被声明为 XML。此适配器也支持以 JavaScript Object Notation (JSON) 返回数据。<implementation> 元素包含用户定义的类定义,RPCAdapter 将实例化此类定义以调用方法。此类中要调用的方法名在 <name> 元素内定义,称为 detailRequest。RPCAdapter 需要的 <http-method>GET。URL 请求需要的参数 是 message

综合起来,清单 8 中的 itemDetails 小部件将会使用如下格式调用 RPCAdapter,其中 F0001 是该条目的标识符:


                
/PlantsByWebSphereAjax/servlet/RPCAdapter/httprpc/Sample/detailRequest?message=F0001

RPCAdapter 返回的结果将是 XML 数据。清单 8 给出了 XML 输出结果:


清单 8:从 RPCAdapter 返回给 itemDetails 小部件的 XML 输出结果
                

<results>
     <entry>
	<description>
African orchids are some of the most endangered and rare kinds of orchids grown today. 
This variety is medium yellow with variegated salmon and pink insides. Height: 18 to 28 
inches.
           </description>
           <image>
/PlantsByWebSphereAjax/servlet/ImageServlet?getimagebyid=F0001
           </image>
           <id>F0001</id>
           <name>African Orchid</name>
            <price>$250.00</price>

           <thumb>
/PlantsByWebSphereAjax/servlet/ImageServlet?getimagebyid=F0001
           </thumb>
           <dept>0</dept>
           <heading>Rare Delicate Beauty</heading>
      </entry>
</results>

现在,我们来仔细看看这个实现类。它在 RpcAdapterConfig.xml 文件内定义并包含 detailRequest 方法。清单 9 展示了在 RpcAdapterConfig.xml 配置文件内定义的实现类 com.ibm.websphere.samples.plantsbywebspherewar.RpcConnecter


清单 9:实现类 com.ibm.websphere.samples.plantsbywebspherewar.RpcConnecter
                

public Collection detailRequest( String itemId )  {

   ItemDetailPojo itemDetailPojo = new ItemDetailPojo();
   Vector itemCollection = new Vector();

   if (itemId != null) {
      try
      {
         Catalog catalog = catalogHome.create();
         StoreItem item = (StoreItem)
                           catalog.getItem(itemId);

         if ( item != null ) {
            itemDetailPojo.name        = item.getName();
            itemDetailPojo.id          = item.getID();
            itemDetailPojo.price       = 
               java.text.NumberFormat.getCurrencyInstance(java.util.Locale.US)
              .format(new Float(item.getPrice()));
            
            itemDetailPojo.heading     = item.getHeading();
            itemDetailPojo.description = item.getDescription();
            itemDetailPojo.dept        = new  
                            Integer(item.getCategory()).toString();
            itemDetailPojo.thumb       = 
               "/PlantsByWebSphereAjax/servlet/ImageServlet?getimagebyid="+item.getID();
            itemDetailPojo.image       = 
               "/PlantsByWebSphereAjax/servlet/ImageServlet?getimagebyid="+item.getID();

             itemCollection.add(itemDetailPojo);
         }

       }
            catch (javax.ejb.CreateException e)
            {
                Util.debug("RpcConnecter: EJB Create Exception in 
                            detailRequest(...)");
            }
            catch (Exception e)
            {
                Util.debug("RpcConnecter: general Exception in 
                            detailRequest(...)");
            }
    }        
    return itemCollection;    
}



方法 detailrequest( ) 返回的是一组 Plain Old Java Objects (POJO) 元素。collection 类是 java.util 包的一部分。POJO 元素来自于由调用 StoreItem EJB 返回的数据。StoreItem 从 Catalog EJB 检索得到。Catalog EJB 是包含在 Derby 数据库的 Plants by WebSphere 库存条目集合。一旦 Java Collection 返回,RPCAdapter 将透明地将该集合映射到 XML 数据。集合的键对应于 XML 元素,键的值对应于 XML 值。XML 流被返回给 itemDetails 小部件。

通过使用 RPCAdapter 并让其返回 XML 代码,就创建了一种其他人也可连接到的服务。这种服务只返回数据,而如何呈现数据的任务则交由调用程序处理。一种使用场景是 Plants by WebSphere 的合作公司可能需要将其自身温室中的数据与由 Plants by WebSphere 维护的类别信息聚合起来。这种场景有时又被称为 mashup





回页首


可以考虑的其他场景

虽然 Plants by WebSphere 应用程序不能实现下述特定的场景,但它们有助于您理解在 J2EE 应用程序内使用 IBM WebSphere Application Server Feature Pack for Web 2.0 的其他方式。

代理服务

您可能听说过创建一个 mashup 应用程序来综合多个站点的内容用以创建一个站点的外观。一个典型的 mashup 示例是用用户基于位置的惟一内容对 Google 地图进行定制。创建 mashups 的困难之一是如何在浏览器内处理跨站点的脚本。比如,如果您到 mydomain.com 以访问 Plants by WebSphere 应用程序,但您所创建的小部件却使用 XHR 向 mypartner.combut 发出 XML 请求,那么浏览器就会拒绝此请求。通常,这是一种很好的行为,因为它防止了在访问网页时跨站点脚本安全性隐患的发生。

在 Plants by WebSphere 应用程序中,若想允许小部件访问其他站点以获得内容,那么该如何在浏览器内启用跨站点的脚本功能呢?

一种常见的方法是使用转发代理。转发代理从浏览器获得请求,查看 URL,并将请求转发给合适的域。从浏览器的角度来看,信息像是来自同一个域,但实际上,内容请求是由代理代表客户机发出的。随 IBM WebSphere Application Server Feature Pack for Web 2.0 提供的 Ajax 代理包括了一种可定制的 servlet 代理。

这个代理基于的是 servlet,可直接嵌入到 EAR 文件或作为 WAR 文件运行于服务器。此代理还包括白名单功能,可进一步用来限制代理所允许服务的请求种类。它还包括在 header、mime type 和 cookie 上进行过滤的功能,而且可以使用 XML 文件进行定制。

Web 提要联合 —— 更详细的信息

Web 联合(或 Web 提要)的做法是为了让内容对其他站点可用。通常,一个站点会把包含标题和对内容简短描述的 Web 提要设为可用。如果用户对此内容感兴趣,他或她就会单击此链接,此后会被带到其他站点以阅读更详细的信息。在 Plants by WebSphere 应用程序的示例中,您可以想象这样一种场景,一个 Web 提要将会包含额外的栽种提示、销售数据或园艺专家的博客。

developerWorks Ajax 资源中心
访问 Ajax 资源中心,这是一个一站式中心,其中包含开发 Ajax 应用程序所需的免费工具、代码和信息。活跃的 Ajax 社区论坛 由 Ajax 专家 Jack Herrington 主持,也许可以为您解答疑问。

IBM Feature Pack for Web 2.0 提供了 Apache Abdera 库,它包括 Atom Syndication Format 和 Atom Publishing protocol 的实现,可帮助您开发自己的提要。

联合的解决方案在不考虑客户端实现的情况下是无法完成的。IBM Feature Pack 提供了 Atom 提要查看器,作为 Dojo Toolkit 扩展的一部分。此查看器可作为小部件嵌入到 HTML 以便提供提要查看功能。





回页首


结束语

Plants by WebSphere 应用程序展示了如何使用 IBM Feature Pack for Web 2.0 为现有的 J2EE 应用程序创建 Ajax 风格的架构。Feature Pack 附带了多种客户端和服务器端技术,可与 Web 应用程序一同部署,也可单独部署。Feature Pack 下载中有额外的许多信息(更多信息,请参阅 参考资料 小节)。



参考资料

学习

获得产品和技术

讨论


关于作者

Author photo

Kevin Haverlock 是 IBM WebSphere Application Server 产品的架构师和开发人员,目前是 Web 2.0 Feature Pack 团队中的一员。通过 kbh@us.ibm.com 可以与 Kevin 联系。




对本文的评价








IBM 和 WebSphere 是 International Business Machines Corporation 在美国和/或其他国家的商标。 Java 和所有基于 Java 的商标都属 Sun Microsystems 所有。 其他公司、产品或服务的名称可能是其他公司的商标或服务标志。

IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款