概览
CXF frontends 是一组编程的API,被用来开发和发布webservice。CXF支持两种类型的frontend--JAX-WS和简单frontend。这章将对JAX-WS frontend提供更加详细的解释。我们同样也会展示如何使用简单frontend API来构建一个webservice。这个章节将会关注使用如下两种frontend来开发基于SOAP的web服务:
- JAX-WS frontend
- 简单 frontend
关于JAX-WS frontend,我们将说明如下内容:
- 使用编码优先的开发方式来开发webservice
- 使用契约优先的开发方式来开发webservice
- 构建一个动态客户端或消费者
- 基于Provider and Dispatch的实现
- 理解webservice上下文
JAX-WS frontend
CXF支持由Java Community Process(JCP)提供的JAX-WS 2.0 API 规范。JAX-WS是一个由JCP制订的正式规范,它定义了API用来构建,开发和部署webservice。CXF提供了它自己的JAX-WS实现来附加在JAX-WS标准规范上。CXF JAX-WS frontend为构建不同种类的webservice提供了不公的API。抛开提供标准的基于WSDL的开发不讲,它还在Provider and Dispatch接口的形式中提供了用于构建基于XML服务的API。
有两种方式来开发基于SOAP的JAX-WS webservice-编码优先和契约优先。正如名字显示的那样,在编码优先的开发模式中,你将从编码开始,然后将它转化成WSDL。编码优先的开发方式适用于:在实现方法的输入和输出的对象格式较简单时,并且你想要将它们快速地暴露为webservice的时候。编码优先的开发方式是更简单的,因为你将从Java对象开始,而不必考虑如何生成WSDL和XSD,而这个过程会在Java对象无法被按照你的意愿映射到XML元素的时候导致一些问题。注意,像CXF Java2WSDL 这样基于实现方法的输入输出格式的工具,将会生成契约,包括为你生成XSD格式。比如,你不能暴露一个Map或者Collection作为一个输出信息格式,因为没有标准的做法将它映射到XML schema,而这将会导致交互上的问题。
在契约优先的开发方式中,开发者从一个已经存在的WSDL artifact来构建webservice。契约优先的开发方式适用于:当你已经有了一个XML schema为webservice的操作定义了输入输出消息的格式的时候,或者想要更好的控制XML如何被映射到Java对象的时候。契约优先的开发方式需要你很精通XSD和WSDL契约,因为你将使用这些契约开始创建你的model。如果你在创建基于工业标准的服务,你或许需要从契约优先的开发方式开始,因为工业消息的格式通常是XML schema的。
如果两种方式你都熟悉,并且知道对象是如何映射到XML的,你可以使用编码优先的开发方式。CXF不仅支持这些开发方式,而且为多种数据绑定机制提供了支持,这些机制将帮助你把Java对象映射到XML。
我们将从编码优先的开发方式开始。
编码优先的开发方式
在这个部分,我们将以开发一个Java类,并且通过注解它将它转化成一个service类开始。你将遵循如下的步骤完成webservice的开发:
- 创建Service Endpoint Interface(SEI)
- 添加Java注解
- 发布服务
- 开发一个消费者
- 运行编码优先的例子
创建Service Endpoint Interface(SEI)
Service Endpoint Interface是一个Java接口,它定义了一个被用来暴露为服务的业务方法。这个服务方法被一个服务类实现。一个SEI可以通过两种不同的方式来构建:
- 从零构建一个SEI组件
- 将存在的业务功能转化成基于服务的组件
第一个方式--从零构建一个SEI组件的方式是指,开发一个全新的webservice而不依赖于任何已经存在的代码或WSDL契约。它推荐你从写服务接口开始,然后创建这个服务的实现类。写服务接口总是好的实践,因为它为你的服务方法提供了一个适当的客户端视图。之后,实现类可以实现定义在接口中的方法。
第二个方式是指,拿到已经存在的业务功能,然后把它转化成基于服务的组件。大多数时候,你已经开发过了业务逻辑,你只是想把它们作为服务方法暴露出去。你可以通过如下的方式来完成这个过程:开发一个SEI,并只定义那些你想要暴露成为一个服务方法的业务方法,然后使已经存在的Java代码实现那个SEI。另一个常用的方式是:创建包装后的SEI和一个实现类,这个实现类可以使用已经存在的实现类来完成自己的功能。
我们将从零创建一个SEI组件开始。我们开发一个OrderProcess
SEI并且实现它。下面的代码显示了这个OrderProcess
SEI:
package demo.order;
public interface OrderProcess {
String processOrder(Order order);
}
你可以看到,上面的代码是一个简单的POJO接口。它定义了一个抽象方法processOrder
,它接收一个Order
bean作为一个参数。OrderProcessImpl
实现类实现了processOrder
方法,并且这个方法将在之后作为一个webservice方法暴露出来。
我们接下来将实现这个接口的业务逻辑。你将要写这个OrderProcessImpl
来实现OrderProcess
SEI。下面的代码显示这个OrderProcessImpl
类:
package demo.order;
public class OrderProcessImpl implements OrderProcess {
public String processOrder(Order order) {
System.out.println("Processing order...");
String orderID = validate(order);
return orderID;
}
}
上面的代码是一个简单的POJO实现类,它实现类processOrder
方法。这个方法简单地校验了订单,并返回一个唯一的订单ID。简单起见,我们返回了一个静态的订单ID作为我们实现的一部分。在下一个部分,我们将通过注解它们的方式把SEI和实现类转化成为webservice组件。
添加Java注解
Webservice注解被添加到一个Java类用来将其暴露成为一个服务组件。JAX-WS使用Java 5注解,它是Web Services Metadata为Java Platform规范(JSR-181)提供的,用来将一个组件转化成为一个webservice。这些注解简单地标记,被用来为一个特定的组件或者方法定义一个特定上下文。每一个注解被一个或多个上下文的属性所支持。在这个部分,我们将向我们的OrderProcess
SEI和实现类添加注解,并将它们转化成一个服务组件。在这个部分,我们将覆盖如下的webservice注解:
- javax.jws.WebService
- javax.jws.soap.SOAPBinding
javax.jws.WebService
一个Java组件可以通过添加一个@WebService
注解的方式被转化成为一个服务。这个注解必须被同时定义在SEI和实现类中。@WebService
注解被定义在javax.jws.WebService
接口。
@WebService
注解支持如下的属性:
让我们注解我们的OrderProcess
SEI和OrderProcessImpl
实现类。下面的代码展示了@WebService
注解的使用:
package demo.order;
import javax.jws.WebService;
@WebService
public interface OrderProcess {
String processOrder(Order order);
}
@WebService
被直接声明在接口或者类之上。它注解了这个类或者接口作为一个webservice的类或者接口。在上面的代码中,OrderProcess
接口通过@WebService
注解,来被定义成为一个webservice接口。
让我们看一下OrderProcessImpl
实现类。下面的代码展示了被注解的OrderProcessImpl
实现类:
package demo.order;
import javax.jws.WebService;
@WebService(serviceName="OrderProcessService",
portName="OrderProcessPort")
public class OrderProcessImpl implements OrderProcess {
public String processOrder(Order order) {
System.out.println("Processing order...");
String orderID = validate(order);
return orderID;
}
}
和SEI类似,你在类之上声明@WebService
。你将定义两个属性--serviceName
和portName
。serviceName
属性被赋值为OrderProcessService
。这个服务名称被消费者用来获取远程接口的句柄来调用服务方法。端口名称签名了endpoint名称。服务的endpoint也同样作为一个服务端口在服务发布的地方被调用。这里的名字是OrderProcessPort
。
有许多其它可选的注解被用来和@WebService
搭配使用,用来更加完整地描述一个webservice。其它注解可以添加更精细的细节到一个服务上。总是推荐你使用这些注解来描述你的webservice,以便于生成的WSDL文档有更多的被这些注解指定的细节。如果你没有使用这些可选的注解,那么WSDL将被按照默认的规则生成,正如前面表格中讨论的一样。
javax.jws.soap.SOAPBinding
@SOAPBinding
注解被定义在javax.jws.soap.SOAPBinding
接口中。这个注解在你想要为你的服务制定SOAP绑定的时候使用。
这个注解支持如下的属性:
SOAP绑定在webservice交互中扮演着重要的角色。让我们更加详细地看看两种风格的SOAP绑定。
RPC 风格 v.s Document 风格
Webservice的SOAP交互风格在服务提供者和消费者之间交互SOAP XML消息中扮演着重要的角色。有两种SOAP消息风格--Document和RPC。SOAP消息的风格被作为SOAP绑定定义在WSDL文档中。一个SOAP绑定能够拥有一个基于编码的使用(encoded use) 或者 基于字面的使用(encoded use or a literal use)。编码,这个词暗示了消息将使用某种格式被编码,而字面的表示使用纯文本消息,而不做任何编码逻辑。
正如名字显示的那样,Document风格将XML文档作为有效的载荷来添加到定义良好的契约中,通常的做法是使用XML schema定义来创建。XML schema格式指定了被消费者调用的服务消息的契约。XML schema定义了服务提供者和服务消费者之间的request和response消息的格式。这些消息的格式可以被服务的提供者和消费者验证为合法。Document literal风格对webservice交互来完成协作来说是更受欢迎的方式。
另一方面,RPC(Remote Procedure Call)风格表明了SOAP主体包含一个XML的方法展示。为了序列化方法的参数到SOAP消息,以便于它能够被任何webservice实现反序列化。SOAP规范定义了一系列的编码规则。由于RPC通常被用在连接SOAP的编码规则,这个连接将被引用为RPC/encoded。你用养有一种RPC/literal交互风格模式,这种模式中你没有任何的编码格式,但是消息依然受限于RPC 基于方法的交互,这种方式下,消息不能被验证为合法的,因为他们与任何XML schema定义之间都没有关联。你或许应该避免开发RPC风格的webservice,因为它们有许多协作性上的问题。
下面的代码展示了@SOAPBinding
注解的使用:
@WebService(name="OrderProcess")
@SOAPBinding(parameterStyle=ParameterStyle.BARE)
public interface OrderProcess {
String processOrder(Order order);
}
javax.jws.WebMethod
@WebMethod
注解被定义在javax.jws.WebMethod
接口中。这个注解被用来自定义webservice的操作。@WebMethod
注解提供了operation的name
和action
属性,这两个属性分别被用来自定义在WSDL文档中的<wsdl:operation>
元素的name
属性和<soap:operation>
元素的soapAction
属性。@WebMethod
注解被放置在服务方法声明的上面。
@WebMethod
注解支持如下的属性:
下面的代码片段展示了@WebMethod
注解的使用:
@WebMethod (name="processOrder")
public String processOrder(Order order) {
// ...
}
JAX-WS webservice注解支持一系列的其它注解,比如:@RequestWrapper
, @ResponseWrapper
, @Oneway
等等。它们中的一些将稍后解释。
发布服务
发布服务意味着将服务组件注册到服务器上,并且通过endpoint URL使其可以被消费者使用。你将把OrderProcess
webservice发布到一个特定的endpoint URL上。在本例中的endpoint URL将会是http://localhost:8080/OrderProcess
。你将开发一个服务器组件来发布你的OrderProcess
服务。对这个例子来说,我们将使用Java 5提供的轻量级的web服务器来发布我们的服务。CXF提供了它自己独立的服务器组件--JaxWsServerFactoryBean
来发布webservice。
下面的代码展示了服务器代码:
import javax.xml.ws.Endpoint;
public class Server {
protected Server() throws Exception {
System.out.println("Starting Server ...");
OrderProcessImpl orderProcessImpl = new OrderProcessImpl();
String address = "http://localhost:8080/OrderProcess";
Endpoint.publish(address, orderProcessImpl);
}
public static void main(String[] args) {
new Server();
Thread.sleep(50000);
System.exit(0);
}
}
用于发布Endpoint
类的静态方法提供了一个简便的方式来发布和测试JAX-WS webservice。这个方法接受endpoint URL和一个OrderProcessImpl
类对象作为参数。这个publish
方法在URL http://localhost:8080/OrderProcess
上创建一个轻量级的web服务器,并且在那个位置部署这个服务。这个轻量级的web服务器在JVM中运行一分钟然后自动终止。一种能够看到服务的WSDL契约的方法是在web浏览器中访问这个URL:
http://localhost:8080/OrderProcess?wsdl
开发一个消费者
webservice的消费者调用服务方法来获得需要的结果。在这一部分,我们将开发一个Client
类来查找我们的OrderProcess
服务,并且调用它的processOrder
方法。下面的代码展示了服务消费者组件:
public class Client {
private static final QName SERVICE_NAME =
new QName("http://order.demo/", "OrderProcessService");
private static final QName PORT_NAME =
new QName("http://order.demo/", "OrderProcessPort");
private static final String WSDL_LOCATION =
"http://localhost:8080/OrderProcess?wsdl";
public static void main(String args[]) throws Exception {
URL wsdlURL = new URL(WSDL_LOCATION);
Service service = Service.create(wsdlURL, SERVICE_NAME);
OrderProcess port = service.getPort(PORT_NAME, OrderProcess.class);
Order order = new Order();
order.setCustomerID("C001");
order.setItemID("I001");
order.setPrice(100.00);
order.setQty(20);
String result = port.processOrder(order);
System.out.println("The order ID is " + result);
}
}
客户端的代码做了以下的事情:
- 它首先创建了WSDL URL。这个WSDL URL是
http://localhost:8080/OrderProcess?wsdl
。这个URL表明了WSDL 文档的位置。
在运行客户端程序之前,你可以通过访问以上的URL来验证这个服务是否可用。如果你能够看见WSDL,那么就意味着这个
OrderProcess
服务被成功地发布了。
- 接下来创建了
Service
对象。这个Service
对象使用了静态的create
方法被创建。这个方法接受WSDL URL和服务名称作为参数。这个服务名称OrderProcessService
是一个QName
,并且在WSDL文档中被映射到<wsdl:service>
元素。<wsdl:service>
元素定义了服务的endpoint。 - 使用
Service
对象,你通过调用getPort
方法获得SEI句柄的代理组件。getPort
方法接受port name和SEI类作为参数。接口名称OrderProcessPort
是一个QName
,并且在WSDL文档中被映射到<wsdl:port>
元素。这个SEI类是OrderProcess
。 - 代理组件在接下来被用来调用服务方法
processOrder
。在调用方法之前,你必须安置好Order
bean,并把它传给processOrder
方法。这个方法在服务器上被调用,并返回订单ID。