GHawk

2005年12月2日 #

运用Jakarta Struts的七大实战心法

当作者 Chuck Cavaness(著有《Programming Jakarta Struts》一书)所在的网络公司决定采用Struts框架之后,Chuck曾经花费了好几个月来研究如何用它来构建公司的应用系统。本文叙述的正是作者在运用Struts过程中来之不易的若干经验和心得。如果你是个负责通过jsp和servlet开发Web应用的Java程序员,并且也正在考虑采用基于Struts的构建方法的话,那么你会在这里发现很多颇有见地同时也很有价值的信息。

  1. 只在必要的时候才考虑扩展Struts框架

  一个好的framework有很多优点,首先,它必须能够满足用户的可预见的需求。为此 Struts为Web 应用提供了一个通用的架构,这样开发人员可以把精力集中在如何解决实际业务问题上。其次,一个好的framework还必须能够在适当的地方提供扩展接口,以便应用程序能扩展该框架来更好的适应使用者的实际需要。

  如果Struts framework在任何场合,任何项目中都能很好的满足需求,那真是太棒了。但是实际上,没有一个框架声称能做到这一点。一定会有一些特定的应用需求是框架的开发者们无法预见到的。因此,最好的办法就是提供足够的扩展接口,使得开发工程师能够调整struts来更好的符合他们的特殊要求。

  在Struts framework中有很多地方可供扩展和定制。几乎所有的配置类都能被替换为某个用户定制的版本,这只要简单的修改一下Struts的配置文件就可以做到。

  其他组件如ActionServlet和 RequestProcessor 也能用自定义的版本代替. 甚至连Struts 1.1里才有的新特性也是按照扩展的原则来设计的。例如,在异常处理机制中就允许用户定制异常处理的句柄,以便更好的对应用系统发生的错误做出响应。

  作为框架的这种可调整特性在它更适合你的应用的同时也在很大的程度上影响了项目开发的效果。首先,由于您的应用是基于一个现有的成熟的、稳定的framework如Struts,测试过程中发现的错误数量将会大大减少,同时也能缩短开发时间和减少资源的投入。因为你不再需要投入开发力量用于编写基础框架的代码了。

  然而, 实现更多的功能是要花费更大的代价的。我们必须小心避免不必要的滥用扩展性能, Struts是由核心包加上很多工具包构成的,它们已经提供了很多已经实现的功能。因此不要盲目的扩展Struts框架,要先确定能不能采用其他方法使用现有的功能来实现。 在决定编写扩展代码前务必要确认Struts的确没有实现你要的功能。否则重复的功能会导致混乱将来还得花费额外的精力清除它。

  2. 使用异常处理声明

  要定义应用程序的逻辑流程,成熟的经验是推荐在代码之外,用配置的方法来实现,而不是写死在程序代码中的。在J2EE中,这样的例子比比皆是。从实现EJB的安全性和事务性行为到描述JMS消息和目的地之间的关系,很多运行时的处理流程都是可以在程序之外定义的。

  Struts 创建者从一开始就采用这种方法,通过配置Struts的配置文件来定制应用系统运行时的各个方面。这一点在版本1.1的新特性上得到延续,包括新的异常处理功能。在Struts framework以前的版本中,开发人员不得不自己处理Struts应用中发生的错误情况。在最新的版本中,情况大大的改观了,Struts Framework提供了内置的一个称为 ExceptionHandler 的类, 用于系统缺省处理action类运行中产生的错误。这也是在上一个技巧中我们提到的framework许多可扩展接口之一。

  Struts缺省的 ExceptionHandler类会生成一个ActionError对象并保存在适当的范围(scope)对象中。这样就允许JSP页面使用错误类来提醒用户出现什么问题。如果你认为这不能满足你的需求,那么可以很方便的实现你自己的ExcepionHandler类。

  具体定制异常处理的方法和机制

  要定制自己的异常处理机制,第一步是继承org.apache.struts.action.ExceptionHandler类。这个类有2个方法可以覆盖,一个是excute()另外一个是storeException(). 在多数情况下,只需要覆盖其中的excute()方法。下面是ExceptionHandler类的excute()方法声明:
  正如你看到的,该方法有好几个参数,其中包括原始的异常。方法返回一个ActionForward对象,用于异常处理结束后将controller类带到请求必须转发的地方去。

  当然您可以实现任何处理,但一般而言,我们必须检查抛出的异常,并针对该类型的异常进行特定的处理。缺省的,系统的异常处理功能是创建一个出错信息,同时把请求转发到配置文件中指定的地方去。 定制异常处理的一个常见的例子是处理嵌套异常。假设该异常包含有嵌套异常,这些嵌套异常又包含了其他异常,因此我们必须覆盖原来的execute()方法,对每个异常编写出错信息。

  一旦你创建了自己的ExceptionHandler 类,就应该在Struts配置文件中的部分声明这个类,以便让Struts知道改用你自定义的异常处理取代缺省的异常处理.

  可以配置你自己的ExceptionHandler 类是用于Action Mapping特定的部分还是所有的Action对象。如果是用于Action Mapping特定的部分就在元素中配置。如果想让这个类可用于所有的Action对象,可以在 元素中指定。例如,假设我们创建了异常处理类CustomizedExceptionHandler用于所有的Action类, 元素定义如下所示:

  在元素中可以对很多属性进行设置。在本文中,最重要的属性莫过于handler属性, handler属性的值就是自定义的继承了ExceptionHandler类的子类的全名。 假如该属性没有定义,Struts会采用自己的缺省值。当然,其他的属性也很重要,但如果想覆盖缺省的异常处理的话,handler无疑是最重要的属性。

  最后必须指出的一点是,你可以有不同的异常处理类来处理不同的异常。在上面的例子中,CustomizedExceptionHandler用来处理任何java.lang.Exception的子类. 其实,你也可以定义多个异常处理类,每一个专门处理不同的异常树。下面的XML片断解释了如何配置以实现这一点。

  在这里,一旦有异常抛出,struts framework将试图在配置文件中找到ExceptionHandler,如果没有找到,那么struts将沿着该异常的父类链一层层往上找直到发现匹配的为止。因此,我们可以定义一个层次型的异常处理关系结构,在配置文件中已经体现了这一点。

  3. 使用应用模块(Application Modules)

  Struts 1.1的一个新特性是应用模块的概念。应用模块允许将单个Struts应用划分成几个模块,每个模块有自己的Struts配置文件,JSP页面,Action等等。这个新特性是为了解决大中型的开发队伍抱怨最多的一个问题,即为了更好的支持并行开发允许多个配置文件而不是单个配置文件。

  注:在早期的beta版本中,该特性被称为子应用(sub-applications),最近的改名目的是为了更多地反映它们在逻辑上的分工。

  显然,当很多开发人员一起参加一个项目时,单个的Struts配置文件很容易引起资源冲突。应用模块允许Struts按照功能要求进行划分,许多情况已经证明这样更贴近实际。例如,假设我们要开发一个典型的商店应用程序。可以将组成部分划分成模块比如catalog(商品目录), customer(顾客), customer service(顾客服务), order(订单)等。每个模块可以分布到不同的目录下,这样各部分的资源很容易定位,有助于开发和部署。图1 显示了该应用的目录结构。

  图 1. 一个典型的商店应用程序的目录结构
  

  注:如果你无需将项目划分成多个模块,Struts框架支持一个缺省的应用模块。这就使得应用程序也可以在1.0版本下创建,具有可移植性,因为应用程序会自动作为缺省的应用模块。

  为了使用多应用模块功能,必须执行以下几个准备步骤:

  • 为每个应用模块创建独立的Struts配置文件。

  • 配置Web 部署描述符 Web.xml文件。

  • 使用org.apache.struts.actions.SwitchAction 来实现程序在模块之间的跳转.

  创建独立的Struts配置文件

  每个Struts应用模块必须拥有自己的配置文件。允许创建自己的独立于其他模块的Action,ActionForm,异常处理甚至更多。

  继续以上面的商店应用程序为例,我们可以创建以下的配置文件:一个文件名为struts-config-catalog.xml,包含catalog(商品目录)、items(商品清单)、和其它与库存相关的功能的配置信息;另一个文件名为struts- config-order.xml, 包含对order(订单)和order tracking(订单跟踪)的设置。第三个配置文件是struts-config.xml,其中含有属于缺省的应用模块中的一般性的功能。

  配置Web部署描述符

  在Struts的早期版本中,我们在Web.xml中指定Struts配置文件的路径。好在这点没变,有助于向后兼容。但对于多个应用模块,我们需要在Web部署描述符中增加新的配置文件的设定。

  对于缺省的应用(包括Struts的早期版本),Struts framework 在Web.xml文件中查找带有config的元素,用于载入Action mapping 和其它的应用程序设定。作为例子,以下的XML片断展现一个典型的元素:

  注:如果在现有的元素中找不到"config"关键字,Struts framework将缺省地使用/WEB/struts-config.xml

  为了支持多个应用模块(Struts 1.1的新特性),必须增加附加的元素。与缺省的元素不同的是,附加的元素与每个应用模块对应,必须以config/xxx的形式命名,其中字符串xxx代表该模块唯一的名字。例如,在商店应用程序的例子中,元素可定义如下(注意粗体字部分):

  第一个 元素对应缺省的应用模块。第二和第三个元素分别代表非缺省应用模块catalog 和 order。

  当Struts载入应用程序时,它首先载入缺省应用模块的配置文件。然后查找带有字符串config/xxx 形式的附加的初始化参数。对每个附加的配置文件也进行解析并载入内存。这一步完成后,用户就可以很随意地用config/后面的字符串也就是名字来调用相应的应用模块。

  多个应用模块之间调用Action类

  在为每个应用模块创建独立的配置文件之后,我们就有可能需要调用不同的模块中Action。为此必须使用Struts框架提供的SwitchAction类。Struts 会自动将应用模块的名字添加到URL,就如Struts 自动添加应用程序的名字加到URL一样。应用模块是对框架的一个新的扩充,有助于进行并行的团队开发。如果你的团队很小那就没必要用到这个特性,不必进行模块化。当然,就算是只有一个模块,系统还是一样的运作。

  4. 把JSP放到WEB-INF后以保护JSP源代码

  为了更好地保护你的JSP避免未经授权的访问和窥视, 一个好办法是将页面文件存放在Web应用的WEB-INF目录下。

  通常JSP开发人员会把他们的页面文件存放在Web应用相应的子目录下。一个典型的商店应用程序的目录结构如图2所示。跟catalog (商品目录)相关的JSP被保存在catalog子目录下。跟customer相关的JSP,跟订单相关的JSP等都按照这种方法存放。
  
  这种方法的问题是这些页面文件容易被偷看到源代码,或被直接调用。某些场合下这可能不是个大问题,可是在特定情形中却可能构成安全隐患。用户可以绕过Struts的controller直接调用JSP同样也是个问题。

  为了减少风险,可以把这些页面文件移到WEB-INF 目录下。基于Servlet的声明,WEB-INF不作为Web应用的公共文档树的一部分。因此,WEB-INF 目录下的资源不是为客户直接服务的。我们仍然可以使用WEB-INF目录下的JSP页面来提供视图给客户,客户却不能直接请求访问JSP。

  采用前面的例子,图3显示将JSP页面移到WEB-INF 目录下后的目录结构

  如果把这些JSP页面文件移到WEB-INF 目录下,在调用页面的时候就必须把"WEB-INF"添加到URL中。例如,在一个Struts配置文件中为一个logoff action写一个Action mapping。其中JSP的路径必须以"WEB-INF"开头。如下所示:请注意粗体部分.

  这个方法在任何情况下都不失为Struts实践中的一个好方法。是唯一要注意的技巧是你必须把JSP和一个Struts action联系起来。即使该Action只是一个很基本的很简单JSP,也总是要调用一个Action,再由它调用JSP。

  最后要说明的是,并不是所有的容器都能支持这个特性。WebLogic早期的版本不能解释Servlet声明,因此无法提供支持,据报道在新版本中已经改进了。总之使用之前先检查一下你的Servlet容器。

  5. 使用 Prebuilt Action类提升开发效率

  Struts framework带有好几个prebuilt Action类,使用它们可以大大节省开发时间。其中最有用的是org.apache.struts.actions.ForwardAction 和 org.apache.struts.actions.DispatchAction.

  使用 ForwardAction

  在应用程序中,可能会经常出现只要将Action对象转发到某个JSP的情况。在上一点中曾提到总是由Action调用JSP是个好习惯。如果我们不必在Action中执行任何业务逻辑,却又想遵循从Action访问页面的话,就可以使用ForwardAction,它可以使你免去创建许多空的Action类。运用ForwardAction的好处是不必创建自己的Action类,你需要做的仅仅是在Struts配置文件中配置一个Action mapping。

  举个例子,假定你有一个JSP文件index.jsp ,而且不能直接调用该页面,必须让程序通过一个Action类调用,那么,你可以建立以下的Action mapping来实现这一点:

  正如你看到的,当 /home 被调用时, 就会调用ForwardAction 并把请求转发到 index.jsp 页面.

  再讨论一下不通过一个Action类直接转发到某个页面的情况,必须注意我们仍然使用元素中的forward属性来实现转发的目标。这时元素定义如下:

  以上两种方法都可以节省你的时间,并有助于减少一个应用所需的文件数。

  使用 DispatchAction

  DispatchAction是Struts包含的另一个能大量节省开发时间的Action类。与其它Action类仅提供单个execute()方法实现单个业务不同,DispatchAction允许你在单个Action类中编写多个与业务相关的方法。这样可以减少Action类的数量,并且把相关的业务方法集合在一起使得维护起来更容易。

  要使用DispatchAction的功能,需要自己创建一个类,通过继承抽象的DispatchAction得到。对每个要提供的业务方法必须有特定的方法signature。例如,我们想要提供一个方法来实现对购物车添加商品清单,创建了一个类ShoppingCartDispatchAction提供以下的方法:

  那么,这个类很可能还需要一个deleteItem()方法从客户的购物车中删除商品清单,还有clearCart()方法清除购物车等等。这时我们就可以把这些方法集合在单个Action类,不用为每个方法都提供一个Action类。

  在调用ShoppingCartDispatchAction里的某个方法时,只需在URL中提供方法名作为参数值。就是说,调用addItem()方法的 URL看起来可能类似于:

  http://myhost/storefront/action/cart?method=addItem

  其中method参数指定ShoppingCartDispatchAction中要调用的方法。参数的名称可以任意配置,这里使用的"method"只是一个例子。参数的名称可以在Struts配置文件中自行设定。

  6.使用动态ActionForm

  在Struts framework中,ActionForm对象用来包装HTML表格数据(包括请求),并返回返回动态显示给用户的数据。它们必须是完全的JavaBean,并继承.Struts 里面的ActionForm类,同时,用户可以有选择地覆盖两个缺省方法。

  该特性能节省很多时间,因为它可以协助进行自动的表现层的验证。ActionForm的唯一缺点是必须为不同的HTML表格生成多个ActionForm 类以保存数据。例如,如果有一个页面含有用户的注册信息,另一个页面则含有用户的介绍人的信息,那么就需要有两个不同的ActionForm类。这在大的应用系统中就会导致过多的ActionForm类。Struts 1.1对此做出了很好的改进,引入了动态ActionForm类概念

  通过Struts framework中的DynaActionForm类及其子类可以实现动态的ActionForm ,动态的ActionForm允许你通过Struts的配置文件完成ActionForm的全部配置;再也没有必要在应用程序中创建具体的ActionForm类。具体配置方法是:在Struts的配置文件通过增加一个元素,将type属性设定成DynaActionForm或它的某个子类的全名。下面的例子创建了一个动态的ActionForm名为logonForm,它包含两个实例变量:username 和 password.

  动态的ActionForm可以用于Action类和JSP,使用方法跟普通的ActionForm相同,只有一个小差别。如果使用普通的ActionForm对象则需要提供get 和 set方法取得和设置数据。以上面的例子而言,我们需要提供getUsername() 和 setUsername()方法取得和设置username变量,同样地有一对方法用于取得和设置password变量.

  这里我们使用的是DynaActionForm,它将变量保存在一个Map类对象中,所以必须使用DynaActionForm 类中的get(name) 和 set(name)方法,其中参数name是要访问的实例变量名。例如要访问DynaActionForm中username的值,可以采用类似的代码:

  String username = (String)form.get("username");

  由于值存放在一个Map对象,所以要记得对get()方法返回的Object对象做强制性类型转换。

  DynaActionForm有好几个很有用的子类。其中最重要的是DynaValidatorForm ,这个动态的ActionForm和Validator 一起利用公共的Validator包来提供自动验证。这个特性使你得以在程序代码之外指定验证规则。将两个特性结合使用对开发人员来说将非常有吸引力。

  7. 使用可视化工具

  自从Struts 1.0 分布以来,就出现了不少可视化工具用于协助创建,修改和维护Struts的配置文件。配置文件本身是基于XML格式,在大中型的开发应用中会增大变得很笨拙。为了更方便的管理这些文件,一旦文件大到你无法一目了然的时候,建议试着采用其中的一种GUI 工具协助开发。商业性的和开放源代码的工具都有不少,表1列出了可用的工具和其相关链接,从那里可以获取更多信息。

  表 1. Struts GUI 工具
  应用程序 性质 网址
  Adalon 商业软件 http://www.synthis.com/products/adalon
  Easy Struts 开放源码 http://easystruts.sourceforge.net/
  Struts Console 免费 http://www.jamesholmes.com/struts/console
  JForms 商业软件 http://www.solanasoft.com/
  Camino 商业软件 http://www.scioworks.com/scioworks_camino.html
  Struts Builder 开放源码 http://sourceforge.net/projects/rivernorth/
  StrutsGUI 免费 http://www.alien-factory.co.uk/struts/struts-index.html

  相关资源

  要获取更为全面的Struts GUI 工具列表 (包括免费的和商业性的), 请访问 Struts resource page.

posted @ 2005-12-02 13:37 GHawk 阅读(244) | 评论 (1)编辑 收藏

我应该使用哪种样式的 WSDL 呢? (From IBM developerWorks)

我应该使用哪种样式的 WSDL 呢?
内容:
引言
RPC/编码
RPC/文字
文档/编码
文档/文字
文档/文字包装模式
为什么不始终采用文档/文字包装的样式
SOAP 响应消息
结束语
参考资料
关于作者
对本文的评价
相关内容:
Web services with WSDL
Handle namespaces in SOAP messages you create by hand
订阅:
developerWorks 时事通讯

级别: 高级

Russell Butek
Web 服务顾问, IBM
2003 年 10 月 31 日
2005 年 6 月 29 日 更新

WSDL 绑定样式可以是 RPC 样式或文档样式。用法可以是编码的,也可以是文字的。您如何决定使用哪一种样式/用法的组合呢?本文将帮助您解决这个问题。

引言
Web 服务是通过 WSDL 文档来描述的。WSDL 绑定描述了如何把服务绑定到消息传递协议(特别是 SOAP 消息传递协议)。WSDL SOAP 绑定可以是 RPC 样式的绑定,也可以是文档样式的绑定。同样,SOAP 绑定可以有编码的用法,也可以有文字的用法。这给我们提供了四种样式/用法模型:

  1. RPC/编码
  2. RPC/文字
  3. 文档/编码
  4. 文档/文字

除了这些样式之外,还有一种样式也很常见,它称为文档/文字包装的样式,算上这一种,在创建 WSDL 文件时您就有了五种绑定样式可以从中选择。您应该选择哪一种呢?

在我进一步讨论以前,让我阐明一些容易混淆的地方。这里,这些术语是非常不合适的:RPC 与文档。这些术语意味着 RPC 样式应该用于 RPC 编程模型,文档样式应该用于文档或消息编程模型。 但事实完全不是这样。样式对于编程模型没有任何意义。它只是指明了如何将 WSDL 绑定转化为 SOAP 消息。其他就没什么了。你可以将任一种样式用于任何编程模型。

同样,术语编码文字只对于 WSDL 到 SOAP 映射有意义,可是,至少这里,这两个单词的字面意思更容易理解一些。

对于这篇讨论,让我们从清单 1 中的 Java 方法开始,并且应用 JAX-RPC Java-to-WSDL 规则(参阅参考资料查看 JAX-RPC 1.1 规范)。

清单 1. Java 方法
public void myMethod(int x, float y);

RPC/编码
采用清单 1 中的方法并且使用你喜欢的 Java-to-WSDL 工具来运行,指定您想让它生成 RPC/编码的 WSDL。您最后应该得到如清单 2 所示的 WSDL 片断。

清单 2. 用于 myMethod 的 RPC/编码的 WSDL
<message name="myMethodRequest">
    <part name="x" type="xsd:int"/>
    <part name="y" type="xsd:float"/>
</message>
<message name="empty"/>

<portType name="PT">
    <operation name="myMethod">
        <input message="myMethodRequest"/>
        <output message="empty"/>
    </operation>
</portType>

<binding .../>  
<!-- I won't bother with the details, just assume it's RPC/encoded. -->

现在用“5”作为参数 x 的值,“5.0”作为参数 y 的值来调用这个方法。发送一个如清单 3 所示的SOAP 消息。

清单 3. 用于 myMethod 的 RPC/编码的 SOAP 消息
<soap:envelope>
    <soap:body>
        <myMethod>
            <x xsi:type="xsd:int">5</x>
            <y xsi:type="xsd:float">5.0</y>
        </myMethod>
    </soap:body>
</soap:envelope>

关于前缀和命名空间的注意事项
为了简单起见,在本文的大部分 XML 示例中,我省略了命名空间和前缀。不过,我还是使用了少数前缀,您可以假定它们是用下列名称空间进行定义的:

  • xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  • xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

关于命名空间和 WSDL-to-SOAP 映射的讨论,请参考文章“Handle namespaces in SOAP messages you create by hand”(参阅 参考资料)。

关于 RPC/编码例子中的 WSDL 和 SOAP 消息有一些需要注意的地方:

优点

  • WSDL 尽可能的简单明了。
  • 操作名出现在消息中,因此接收者可以很容易的将消息分派到操作的实现。



缺点

遵照 WS-I
各种 Web 服务规范有时候是不一致和不明确的。WS-I 组织成立用来解决这些规范上的问题。它已经定义了许多概要,指明了你应该如何编写 Web 服务来实现互操作性。要获取 WS-I 的更多信息,请参阅参考资料中的 WS-I 链接。

  • 类型编码信息(xsi:type="xsd:int")通常就是降低吞吐量性能的开销。
  • 你不能很容易的验证这个消息的有效性,因为只有 <x ...>5</x><y ...>5.0</y> 行包含 Schema 中定义的内容;soap:body 内容的其余部分来自于 WSDL 定义。
  • 虽然它是合法的 WSDL,但 RPC/encoded 是不遵守 WS-I 的。

有没有一种方法可以取其精华,弃其糟粕呢?可能有。让我们看一下 RPC/文字样式。

RPC/文字
用于这个方法的 RPC/文字样式的 WSDL 看起来与 RPC/编码的 WSDL(清单 4)几乎一样。绑定的用法从 编码 变为 文字。仅此而已。

清单 4. 用于 myMethod 的 RPC/文字样式的 WSDL
<message name="myMethodRequest">
    <part name="x" type="xsd:int"/>
    <part name="y" type="xsd:float"/%gt;
</message>
<message name="empty"/>

<portType name="PT">
    <operation name="myMethod">
        <input message="myMethodRequest"/>
        <output message="empty"/>
    </operation>
</portType>

<binding .../>  
<!-- I won't bother with the details, just assume it's RPC/literal. -->

RPC/文字的 SOAP 消息又是怎样的呢(参阅清单 5)?这里的更改要多一点。去掉了类型编码。

清单 5. 用于 myMethod 的 RPC/literal SOAP 消息
<soap:envelope>
    <soap:body>
        <myMethod>
            <x>5</x>
            <y>5.0</y>
        </myMethod>
    </soap:body>
</soap:envelope>

关于 xsi:type 和文字用法的注意事项
虽然在一般情况下,xsi:type 没有出现在文字 WSDL 的 SOAP 消息中,但是仍然有一些情况,类型信息是必须的,并且它将以多种形式出现。如果 API 期望一个基础类型,并且发送一个扩展实例,则必须提供这个实例的类型以便正确的反序列化该对象。

这里是这种方法的优点和缺点:

优点

  • WSDL 尽可能的简单明了。
  • 操作名仍然出现在消息中。
  • 去掉了类型编码。
  • RPC/文字是遵循 WS-I 的。

缺点

  • 你仍然不能很容易的验证这个消息的有效性,因为只有 <x ...>5</x><y ...>5.0</y> 行中包含定义在 Schema 中的内容;soap:body 内容的其余部分来自于 WSDL 定义。

文档样式如何呢?它们能够帮助克服这些困难吗?

文档/编码
没有人使用这个样式。它不遵循 WS-I。因此此处略过。

文档/文字
文档/文字的 WSDL 在 RPC/文字的 WSDL 基础上做了一些修改。其不同点已经在清单 6 中指出。

清单 6. 用于 myMethod 的文档/文字 WSDL
<types>
    <schema>
        <element name="xElement" type="xsd:int"/>
        <element name="yElement" type="xsd:float"/>
    </schema>
</types>

<message name="myMethodRequest">
    <part name="x" element="xElement"/>
    <part name="y" element="yElement"/>
</message>
<message name="empty"/>

<portType name="PT">
    <operation name="myMethod">
        <input message="myMethodRequest"/>
        <output message="empty"/>
    </operation>
</portType>

<binding .../>  
<!-- I won't bother with the details, just assume it's document/literal. -->

用于这个 WSDL 的 SOAP 消息如清单 7 所示:

清单 7. 用于 myMethod 的文档/文字 SOAP 消息
<soap:envelope>
    <soap:body>
        <xElement>5</xElement>
        <yElement>5.0</yElement>
    </soap:body>
</soap:envelope>

关于消息组成部分的注意事项
我本来可以只更改绑定,就像我从 RPC/编码转到 RPC/所做的那样。它将是合法的 WSDL。然而,WS-I 基本概要(WS-I Basic Profile)规定文档/文字的消息的组成部分引用元素而不是类型,所以我遵循了 WS-I(并且此处使用元素部分可以很好地把我们带到关于文档/文字包装的样式的讨论)。

下面是这种方法的优点和缺点:

优点

  • 没有类型编码信息。
  • 您可以在最后用任何 XML 检验器检验此消息的有效性。soap:body 里面的所有内容都定义在 schema 中。
  • 文档/文字是遵循 WS-I 的,但是有限制(参阅缺点)。

缺点

  • WSDL 有一点复杂。不过,这是一个非常小的缺点,因为 WSDL 并没有打算由人来读取。
  • SOAP 消息中缺少操作名。而如果没有操作名,发送就可能比较困难,并且有时变得不可能。
  • WS-I 仅仅允许 SOAP 消息中 soap:body 的一个子元素。正如你在清单 7 中所见的那样,该消息的 soap:body 有两个子元素。

文档/文字样式似乎只是重新排列了一下 RPC/文字模型中的优点和缺点。你可以验证该消息,但是你已经失去了操作名。有没有什么办法可以改进这一点呢?是的,它就是文档/文字包装模式。

文档/文字包装模式
在我描述文档/文字包装模式的规则之前,让我先向您展示 WSDL 和 SOAP 消息,如清单 8清单 9 所示。

清单 8. 用于 myMethod 的文档/文字封装的 WSDL。
<types>
    <schema>
        <element name="myMethod">
            <complexType>
                <sequence>
                    <element name="x" type="xsd:int"/>
                    <element name="y" type="xsd:float"/>
                </sequence>
            </complexType>
        </element>
        <element name="myMethodResponse">
            <complexType/>
        </element>
    </schema>
</types>
<message name="myMethodRequest">
    <part name="parameters" element="myMethod"/>
</message>
<message name="empty">
    <part name="parameters" element="myMethodResponse"/>
</message>

<portType name="PT">
    <operation name="myMethod">
        <input message="myMethodRequest"/>
        <output message="empty"/>
    </operation>
</portType>

<binding .../>  
<!-- I won't bother with the details, just assume it's document/literal. -->

WSDL Schema 现在把参数放在包装中(参阅清单 9)。

清单 9. 用于 myMethod 的文档/文字包装的 SOAP 消息
<soap:envelope>
    <soap:body>
        <myMethod>
            <x>5</x>
            <y>5.0</y>
        </myMethod>
    </soap:body>
</soap:envelope>

注意这个 SOAP 消息同 RPC/文字的 SOAP 消息(清单 5)非常相似。您可能会说,它看起来与 RPC/文字的 SOAP 消息是完全一样的,不过,这两种消息之间存在着微妙的区别。在 RPC/文字的 SOAP 消息中,<soap:body><myMethod> 子句是操作的名称。在文档/文字包装的 SOAP 消息中,<myMethod> 子句是单个输入消息的组成部分引用的元素的名称。因此,包装的样式具有这样的一个特征,输入元素的名称与操作的名称是相同的。此样式是把操作名放入 SOAP 消息的一种巧妙方式。

文档/文字包装的样式的特征有:

  • 输入消息只有一个组成部分。
  • 该部分是一个元素。
  • 该元素同操作有相同的名称。
  • 该元素的复杂类型没有属性。

下面是该种方法的优缺点:

优点

  • 没有类型编码信息。
  • soap:body 中出现的所有内容都定义在 schema 中,所以您可以很容易地检验此消息的有效性。
  • 方法名又出现在 SOAP 消息中。
  • 文档/文字是遵守 WS-I 的,并且包装模式符合了 WS-I 的限制,即 SOAP 消息的 soap:body 只有一个子元素。

缺点

  • WSDL 更加复杂。

文档/文字包装的样式还是有一些缺点,不过与优点比起来,它们都显得微不足道。

RPC/文字包装?
从 WSDL 的角度来考虑,没有理由只是把把包装的样式和文档/文字绑定联系在一起。它可以很容易地应用于 RPC/文字绑定。但是这样做是相当不明智的。SOAP 将包含操作的一个 myMethod 元素和元素名称的子 myMethod 元素。另外,即使它是一个合法的 WSDL,RPC/文字元素部分也不遵循 WS-I。

文档/文字的样式在哪里定义?
这种包装类型来源于 Microsoft?。并没有任何规范来定义这个类型;因此虽然这个类型是一个好东西,但不幸的是,为了与 Microsoft 和其他公司的实现进行互操作,现在惟一的选择就是根据 Microsoft WSDL 的输出来猜测它是如何工作的。该模式已经出现了一段时间,并且业界也很好的理解了它,虽然该模式在例子中是非常明显的,但是也有一些内容不够清晰。我们希望一个独立的组织比如 WS-I 来帮助稳定和标准化这一模式。

为什么不始终采用文档/文字包装的样式
至此,本文已经给了您这样的一个印象,文档/文字包装的样式是最好的方法。而实际的情况往往确实如此。不过,仍然存在着一些情况,在这些情况下,您最好是换一种别的样式。

采用文档/文字非包装的样式的理由
如果您已经重载了操作,就不能采用文档/文字包装的样式。

想象一下,除了我们一直在使用的方法之外,还有另一种方法,请参见清单 10

清单 10. 用于文档/文字包装的有问题的方法
public void myMethod(int x, float y);
public void myMethod(int x);

关于重载的操作的注意事项
WSDL 2.0 不会允许重载的操作。这对于一些允许该操作的语言(比如 Java)来说是不幸的。一些规范(比如 JAX-RPC)将不得不定义一个名称转换模式(name mangling scheme)来将重载的方法映射到 WSDL 中。WSDL 2.0 只不过将问题从 WSDL-to-SOAP 映射转移到 WSDL-to-language 映射中。

WSDL 允许重载的操作。但是当你向 WSDL 上添加包装模式的时候,需要元素有与操作相同的名称,并且在 XML 中不能有两个名称相同的元素。所以您必须采用文档/文字非包装的样式或某种 RPC 样式。

采用 RPC/文字的样式的理由
由于文档/文字非包装的样式没有提供操作名,所以在有些情况下,您将需要采用某种 RPC 样式。比如说清单 11 中的一组方法。





清单 11. 用于文档/文字非包装的样式的问题方法
public void myMethod(int x, float y);
public void myMethod(int x);
public void someOtherMethod(int x, float y);

现在假设你的服务器接收到了文档/文字的 SOAP 消息,你可以回头看一下清单 7。服务器应该发送哪一种方法呢?所有您能确切知道的就是,它一定不是 myMethod(int x),因为消息有两个参数,而这种方法只需要一个参数。它可能是其他两种方法中的一种。采用文档/文字的样式,您没有办法知道是哪一种方法。

假定服务器接收到一个 RPC/文字的消息,而不是文档/文字的消息,如清单 5 所示。对于这种消息,服务器很容易决定把它发送到哪一种方法。你知道该操作名称是 myMethod,并且你知道你有两个参数,因此肯定是 myMethod(int x, float y)

采用 RPC/编码的理由
使用 RPC/编码样式最重要的原因是为了数据图表。设想你有一个二进制树,如清单 12 所示。

清单 12. 二进制树节点 schema
<complexType name="Node">
    <sequence>
        <element name="name" type="xsd:string"/>
        <element name="left" type="Node" xsd:nillable="true"/>
        <element name="right" type="Node" xsd:nillable="true"/>
    </sequence>
</complexType>

根据这种节点定义,你可以构建一个树,其根节点 -- A -- 通过左/右链接指向节点 B(参阅图 1)。

图 1. 编码树。
编码树

发送数据图表的标准方式是使用 href 标签,它是 RPC/编码的样式(清单 13)的一部分。

清单 13. RPC/编码的二进制树
<A>
    <name>A</name>
    <left href="12345"/>
    <right href="12345"/>
</A>
<B id="12345">
    <name>B</name>
    <left xsi:nil="true"/>
    <right xsi:nil="true"/>
</B>

在任何文字样式中,href 属性都是不可用的,这样图形链接就不再起作用了(参阅清单 14图 2)。你仍然有一个根节点 A,其指向左边的节点 B和右边的另一个节点 B。这些节点 B 都是一样的,但是它们不是相同的节点。数据被复制而不是引用两次。

清单 14. 文字二进制树
<A>
    <name>A</name>
    <left>
        <name>B</name>
        <left xsi:nil="true"/>
        <right xsi:nil="true"/>
    </left>
    <right>
        <name>B</name>
        <left xsi:nil="true"/>
        <right xsi:nil="true"/>
    </right>
</A>

图 2. 文字树
文字树

在文字样式中,您可以通过各种方法构造图表,但是却没有标准的方法;所以您做的任何事情很可能不能与网络中其他端点上的服务进行互操作。

SOAP 响应消息
到目前为止我已经讨论了请求消息。但是响应消息呢?它们是怎样的呢?现在你应该很清楚一个文档/文字消息的响应消息应该是怎样的。soap:body 的内容是由 schema 定义的,因此你所需要做的就是查看该 schema 来了解响应消息的内容。比如,参考清单 15 来查看清单 8 中的 WSDL 文件的响应消息。

清单 15. 用于 myMethod 的文档/文字包装的响应 SOAP 消息
<soap:envelope>
    <soap:body>
        <myMethodResponse/>
    </soap:body>
</soap:envelope>

但是用于 RPC 样式响应的 soap:body 的子元素是什么呢?WSDL 1.1 规范并不是很清楚。但是 WS-I 解决了这个问题。WS-I 的 Basic Profile 指明了在 RPC/文字响应消息中,soap:body 子元素的名称是“... 相应的 wsdl:operation 名称加上字符串 'Response' 作为后缀。”奇怪!这正是常规包装模式的响应元素的名称。因此清单 15 可以应用到 RPC/文字消息和文档/文字包装的消息。(因为 RPC/编码并不是遵守 WS-I 的,WS-I Basic Profile 并不关心 RPC/编码的响应是怎样的,但是你可以假设应用在这里的约定也可以应用在其他任何地方。)因此响应消息的内容并不神秘。

结束语
这里有四种绑定样式(其实是五个,但是文档/编码的样式是没有意义的)。虽然每种样式都有自己的用处,但是在大多数情况下,最好的样式是文档/文字包装的样式。

参考资料

关于作者
Russell Butek 是 IBM 的一名 Web 服务顾问。他是 IBM WebSphere Web 服务引擎的开发人员之一。他也是 JAX-RPC Java Specification Request (JSR) 专家组的 IBM 代表。他从事 Apache 的 AXIS SOAP 引擎的实现方面的研究,推动了 AXIS 1.0 遵循 JAX-RPC 1.0。以前,他是 IBM CORBA ORB 的开发人员和许多 OMG 特别工作组的 IBM 代表:包括可移植拦截器特别工作组(他是这个特别工作组的主席)、核心特别工作组以及互操作性特别工作组。你可以通过 butek@us.ibm.com 与他联系。

posted @ 2005-12-02 13:36 GHawk 阅读(320) | 评论 (0)编辑 收藏