28.2. 实现消费者

概述

要在从 WSDL 合同开始实施消费者,您必须使用以下根根:

  • Service class
  • SEI

使用这些存根时,使用者代码会实例化服务代理来在远程服务上发出请求。它还实施使用者的业务逻辑。

生成的服务类

例 28.2 “生成 Service 类概述” 显示了生成的服务类 ServiceName_Service的典型概述[2],它扩展了 javax.xml.ws.Service 基础类。

例 28.2. 生成 Service 类概述

@WebServiceClient(name="..." targetNamespace="..."
                  wsdlLocation="...")
public class ServiceName extends javax.xml.ws.Service
{
  ...
  public ServiceName(URL wsdlLocation, QName serviceName) { }

  public ServiceName() { }

  // Available only if you specify '-fe cxf' option in wsdl2java
  public ServiceName(Bus bus) { }

  @WebEndpoint(name="...")
  public SEI getPortName() { }
  .
  .
  .
}

例 28.2 “生成 Service 类概述” 中的 ServiceName 类定义以下方法:

  • ServiceName(URL wsdlLocation, QName serviceName) - 基于 wsdl:service 元素中具有来自 wsdlLocation 的 QName ServiceName 服务的数据构造服务对象。
  • ServiceName() - 默认构造器。它根据服务名称和生成 stub 代码时提供的 WSDL 合同来构建服务对象(例如,在运行 wsdl2java 工具时)。使用此构造器预测 WSDL 合同在指定位置仍然可用。
  • ServiceName(Bus 总线) - (CXF 特定) 是一个额外的构造器,允许您指定用来配置该服务的总线实例。这在多线程应用程序上下文中很有用,其中多个总线实例可以与不同的线程关联。此构造器提供了一种简单方法,可确保您指定的总线是与此服务一起使用的总线。只有在调用 wsdl2java 工具时指定 -fe cxf 选项,才可用。
  • getPortName() - 为 wsdl:port 元素定义的端点返回代理,name 属性等于 PortName。为 ServiceName 服务定义的每个 wsdl:port 元素生成一个 getter 方法。包含多个端点定义的 wsdl:service 元素会生成带有多个 getPortName()方法的 服务类。

服务端点接口

对于原始 WSDL 合同中定义的每个接口,您可以生成对应的 SEI。服务端点接口是 wsdl:portType 元素的 Java 映射。原始 wsdl:portType 元素中定义的每个操作都映射到 SEI 中的对应方法。操作的参数映射如下: .输入参数映射到方法参数。

  1. 第一个输出参数映射到返回值。
  2. 如果有多个输出参数,则第二个输出参数和后续输出参数映射到方法参数(此外,必须通过 Holder 类型传递这些参数的值)。

例如,例 28.3 “Greeter Service Endpoint Interface” 显示 Greeter SEI,它从 例 26.1 “HelloWorld WSDL Contract” 中定义的 wsdl:portType 元素生成。为简单起见,例 28.3 “Greeter Service Endpoint Interface” 省略了标准 JAXB 和 JAX-WS 注释。

例 28.3. Greeter Service Endpoint Interface

package org.apache.hello_world_soap_http;
  ...
public interface Greeter
{
  public String sayHi();
  public String greetMe(String requestType);
  public void greetMeOneWay(String requestType);
  public void pingMe() throws PingMeFault;
}

消费者主要功能

例 28.4 “消费者实施代码” 显示实现 HelloWorld 消费者的代码。使用者连接到 SOAPService 服务中的 SoapPort 端口,然后继续调用 Greeter 端口类型支持的每个操作。

例 28.4. 消费者实施代码

package demo.hw.client;

import java.io.File;
import java.net.URL;
import javax.xml.namespace.QName;
import org.apache.hello_world_soap_http.Greeter;
import org.apache.hello_world_soap_http.PingMeFault;
import org.apache.hello_world_soap_http.SOAPService;

public final class Client {

  private static final QName SERVICE_NAME =
  new QName("http://apache.org/hello_world_soap_http",
            "SOAPService");

  private Client()
  {
  }

  public static void main(String args[]) throws Exception
  {
 if (args.length == 0)
    {
      System.out.println("please specify wsdl");
      System.exit(1);
    }

 URL wsdlURL;
    File wsdlFile = new File(args[0]);
    if (wsdlFile.exists())
    {
      wsdlURL = wsdlFile.toURL();
    }
    else
    {
      wsdlURL = new URL(args[0]);
    }

    System.out.println(wsdlURL);
 SOAPService ss = new SOAPService(wsdlURL,SERVICE_NAME);
 Greeter port = ss.getSoapPort();
    String resp;

 System.out.println("Invoking sayHi...");
    resp = port.sayHi();
    System.out.println("Server responded with: " + resp);
    System.out.println();

    System.out.println("Invoking greetMe...");
    resp = port.greetMe(System.getProperty("user.name"));
    System.out.println("Server responded with: " + resp);
    System.out.println();

    System.out.println("Invoking greetMeOneWay...");
    port.greetMeOneWay(System.getProperty("user.name"));
    System.out.println("No response from server as method is OneWay");
    System.out.println();

 try {
      System.out.println("Invoking pingMe, expecting exception...");
      port.pingMe();
    } catch (PingMeFault ex) {
      System.out.println("Expected exception: PingMeFault has occurred.");
      System.out.println(ex.toString());
    }
    System.exit(0);
  }
}

例 28.4 “消费者实施代码” 中的 Client.main() 方法进行,如下所示:

如果 Apache CXF 运行时类位于类路径上,则运行时会被隐式初始化。不需要调用特殊的函数来初始化 Apache CXF。

使用者需要一个字符串参数,为 HelloWorld 提供 WSDL 合同的位置。WSDL 合同的位置保存在 wsdlURL 中。

您可以使用需要 WSDL 合同的位置和服务名称的构造器创建服务对象。 调用适当的 getPortName() 方法以获取所需端口的实例。在这种情况下,SOAPService 服务只支持 SoapPort 端口,该端口实施 Greeter 服务端点接口。

使用者调用 Greeter 服务端点接口支持的每个方法。

如果是 pingMe() 方法,示例代码演示了如何捕获 pingMeFault 故障异常。

使用 -fe cxf 选项生成的客户端代理

如果您在 wsdl2java 中指定 -fe cxf 选项来生成客户端代理(因此选择 cxf frontend),则生成的客户端代理代码与 Java 7 更好地集成。在这种情况下,当您调用 getServiceNamePort() 方法时,您会返回一个类型为 SEI 的子接口并实现以下附加接口:

  • java.lang.AutoCloseable
  • javax.xml.ws.BindingProvider (JAX-WS 2.0)
  • org.apache.cxf.endpoint.Client

要了解这如何简化使用客户端代理的过程,请考虑使用标准 JAX-WS 代理对象编写的 Java 代码示例:

// Programming with standard JAX-WS proxy object
//
(ServiceNamePortType port = service.getServiceNamePort();
((BindingProvider)port).getRequestContext()
        .put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address);
port.serviceMethod(...);
((Closeable)port).close();

并将前面的代码与以下等效的代码示例进行比较,使用 cxf frontend 生成的代码编写:

// Programming with proxy generated using '-fe cxf' option
//
try (ServiceNamePortTypeProxy port = service.getServiceNamePort()) {
    port.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address);
    port.serviceMethod(...);
}


[2] 如果 wsdl:service 元素的 name 属性以服务结尾,则不使用 _Service。