CXF是什么
Apache CXF 是一个开源的、全功能的WebService框架,它提供了一套工具和API来帮助开发和构建WebService,像 JAX-WS 和 JAX-RS。它也支持许多WebService标准,例如:
和许多其他的特性。[更多的请参考 http://en.wikipedia.org/wiki/Apache_CXF]
demo.wsdl 是本文中使用的示例wsdl文件. |
使用CXF调用WebService
每个WebService服务都需要提供一套用于外部调用的接口,也需要提供对应于这些接口调用的输入输出数据格式。用于规范这些接口和消息格式定义的标准就是 WSDL 。在本节里,将讨论如何使用CXF 来开发一个基于某个WSDL的WebService客户端。 (这里假设使用的传输协议为 HTTP,更多的可以参考本博客里的关于CXF的介绍的文章).
本质上,每个基于 HTTP 的WebService服务都等价于一个接收 HTTP-POST 请求的HTTP服务,为了调用这个服务,用户只需要知道如何构造一个期望的HTTP POST请求格式,例如:
POST http://localhost:8040/services/WebService HTTP/1.1 Accept-Encoding: gzip,deflate Content-Type: text/xml;charset=UTF-8 SOAPAction: "http://www.talend.org/service/WebServiceOperation1" Content-Length: 307 Host: localhost:8040 Connection: Keep-Alive <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://www.talend.org/service/"> <soapenv:Header/> <soapenv:Body> <ser:WebServiceOperationRequest1> <in>Hello</in> </ser:WebServiceOperationRequest1> </soapenv:Body> </soapenv:Envelope>可以使用Java HTTP API 发送以上请求:
//request content String content = "<soapenv:Envelope" + " xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"" + " xmlns:ser=\"http://www.talend.org/service/\">" + "<soapenv:Body><ser:WebServiceOperationRequest1><in>Hello</in>" + "</ser:WebServiceOperationRequest1></soapenv:Body>" + "</soapenv:Envelope>"; //service url URL url = new URL("http://localhost:8040/services/WebService"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); //set soapaction connection.addRequestProperty("SOAPAction", "http://www.talend.org/service/WebServiceOperation1"); connection.setDoOutput(true); connection.setDoInput(true); //send out request OutputStream os = connection.getOutputStream(); os.write(content.getBytes()); os.flush(); //read response and print out InputStream is = connection.getInputStream(); int available = is.available(); byte[] response = new byte[available]; is.read(response); System.out.println(new String(response)); os.close(); is.close();
从以上代码里可以看出,为了使得调用成功,我们需要关心从请求信息到连接信息的几乎每个细节。因此这里,我们将介绍如果使用CXF 来简化请求,从而让用户可以只关注业务逻辑部分,再底层细节由CXF 处理。
CXF 提供了好几种调用WebService的方法,详细的可以参考本博客里的相关文章。这里就介绍两种:Provider-Dispatch 和 JAX-WS
Provider-Dispatch
Provider-Dispatch 是一种类似于 Java HTTP 的方式,好处就是灵活、方便、强大,缺点就是和Java HTTP 一样,用户需要知道如何构建一个合格的消息。更多的可以看 http://liugang594.iteye.com/blog/1964510 。
JAX-WS
JAX-WS 是 Java API for XML Web Services 的简称,它是用于创建WebService的Java API,它定义了一个用于Java-WSDL映射的标准,例如WSDL接口如何绑定到Java方法,以及SOAP消息如何匹配到方法的参数等。
为了简化 WSDL 和 Java的映射, CXF 提供了一些工具和API用于Java和WSDL之间的转换,这里只展示如何从WSDL转成Java.
WSDL2Java 命令行
CXF 提供了一个 wsdl2java 命令行工具用于从 WSDL 转换到 Java,有很多可用的选项,以下列出了主要的几个选项:[更多的可以访问 https://cxf.apache.org/docs/wsdl-to-java.html ]:
|
Interpretation |
|
Specifies zero, or more, package names to use for the generated code. Optionally specifies the WSDL namespace to package name mapping. |
|
Specifies the directory into which the generated code files are written. |
wsdlurl |
The path and name of the WSDL file to use in generating the code. |
例如要生成对应于以上 demo.wsdl的代码,可以使用以下命令:
wsdl2java -p org.talend.esb.camel.cxf -d output demo.wsdl
完成以后就可以看到生成的代码列表:
更进一步的,后 6 类分别对应于WSDL中的 Service(s), DataTypes 和 PortType(s)。从Java对象到 WSDL 和 XML 元素的映射关系由annotation决定。这里,生成的代码里有两类annotation:
JAX-WS annotations
这类annotation主要用于Java和WSDL之间的映射These annotations are mainly used to map a java class to Web Service.
Annotation | Description |
WebService | Every Class who wants to be published as a Web Service should be annotated by this annotation |
SOAPBinding | Define the SOAP Binding style of this service |
WebResult | Define the mapping from method return object to WSDL operation output |
WebMethod | Define the mapping between Java method to WSDL operation |
WebParam | Define the mapping between method argument to WSDL operation Input |
访问 https://jax-ws.java.net/jax-ws-ea3/docs/annotations.html 以了解每个annotation的更多细节。
JAXB Annotations
JAX-WS annotations用于java到 WSDL之间的映射,而JAXB annotation则是用于Java对象到XML结构的映射。 JAXB 是 CXF 默认的数据映射方式,用户也可以使用以下参数指定其他的映射方式:
-db
下面是生成代码里用到的 JAXB 相关的annotation说明:
Annotation | Description |
XmlAccessorType | Used on a class to specify which members will be mapped to xml, for example FIELD, PUBLIC |
XmlElement | Use to indicate the member will be mapped to an xml element |
XmlType | Used on a class to specify the generated xml type of this class |
XmlRootElement | Used to indicate it can be a root element of xml |
有了这些类后,可以很容易的用他们来调用WebService了:
//create service instance Service service = Service.create(new URL("http://localhost:8040/services/WebService?wsdl"), new QName("http://www.talend.org/service/", "WebService")); //create port from service WebServicePortType port = service.getPort(WebServicePortType.class); //input parameter WebServiceOperationRequest1 operationRequest1 = new WebServiceOperationRequest1(); operationRequest1.setIn("World"); //invoke, and get response WebServiceOperationResponse1 operationResponse1 = port.webServiceOperation1(operationRequest1); //print out the result System.out.println(operationResponse1.getOut());
只需要知道wsdl的路径和service的qname,其他的都是普通的Java对象。
注意:返回值也是一个对象,而不是普通的XML内容,可以使用以下代码进行转换:
Object to XML
|
WSDL2Java API
除了命令行,也可使用代码来进行wsdl到java的转换:
WSDLToJava wsdlToJava = new WSDLToJava(new String[]{"-p", "org.talend.esb.camel.cxf", "-d", "output", "demo.wsdl"}); wsdlToJava.run(new ToolContext());
使用camel-cxf调用WebService
camel-cxf 组件是基于 CXF的,它提供了在Camel中访问WebService的能力。
URI 格式
cxf:bean:cxfEndpoint[?options]当 cxfEndpoint 代表一个已经注册了的Bean的ID。使用这种格式的URI,节点的大部分内容在Bean里定义。
或者:
cxf://someAddress[?options]这里 someAddress 指定了 CXF 端点的地址。使用这种格式的URI,节点的大部分定义是由选项指定的。本文里只讨论这种格式。
选项
camel-cxf组件提供了很多可用的选项,对于consumer端,大部分有用的选项都在下表中:[ 更多的请看 http://camel.apache.org/cxf.html ]:
|
Required |
Description |
|
No |
The location of the WSDL. It is obtained from endpoint address by default. |
|
Yes |
The name of the SEI (Service Endpoint Interface) class. This class can have, but does not require, JSR181 annotations. |
|
No |
The service name this service is implementing, it maps to the |
|
No |
The port name this service is implementing, it maps to the |
|
No |
The data type messages supported by the CXF endpoint. |
DataFormat的描述如下:
|
Description |
|
POJOs (Plain old Java objects) are the Java parameters to the method being invoked on the target server. Both Protocol and Logical JAX-WS handlers are supported. |
|
|
|
RAW is the raw message that is received from the transport layer. It is not suppose to touch or change Stream, some of the CXF interceptor will be removed if you are using this kind of DataFormat so you can't see any soap headers after the camel-cxf consumer and JAX-WS handler is not supported. |
|
New in Camel 2.8.2, |
DataFormats
RAW
camel-cxf支持4种DataFormat,下面将显示每种DataFormat的特点与不同:
from("timer:foo?repeatCount=1") .setBody(constant(content)) //set request body .to("cxf:" + "http://localhost:8040/services/WebService" //service address + "?" + "wsdlURL=http://localhost:8040/services/WebService?wsdl" //wsdl url + "&" + "dataFormat=RAW" //dataformat type ) .convertBodyTo(String.class) .to("log:output");
以上代码示例的作用和最上的Java HTTP API效果是一样的,这里用的 dataFormat 为 RAW,请求内容为:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://www.talend.org/service/"> <soapenv:Body> <ser:WebServiceOperationRequest1> <in>Hello</in> </ser:WebServiceOperationRequest1> </soapenv:Body> </soapenv:Envelope>
对于RAW,可以看出,整个soap envelope的结构都需要指定,返回值也是一个完整的Envelope的结构:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <tns:WebServiceOperationResponse1 xmlns:tns="http://www.talend.org/service/"> <out>Hello</out> </tns:WebServiceOperationResponse1> </soap:Body> </soap:Envelope>
PAYLOAD
不同的DataFormat,请求内容的格式也是不一样,例如上例中,把RAW 换成 PAYLOAD,则内容需要指定为:
<ser:WebServiceOperationRequest1 xmlns:ser="http://www.talend.org/service/"> <in>Hello</in> </ser:WebServiceOperationRequest1>
只有soap body节点内的内容需要指定,返回的结果也是只有soap body内的内容:
<tns:WebServiceOperationResponse1 xmlns:tns="http://www.talend.org/service/"> <out>Hello</out> </tns:WebServiceOperationResponse1>
CXF_MESSAGE
CXF_MESSAGE 类似于 RAW,它也要求整个soap envelope的结构,但是内容对象需要是一个 javax.xml.transform.Source 实例,因此需要先把内容转成一个 javax.xml.transform.Source 对象:
from("timer:foo?repeatCount=1") .setBody(constant(content)) //set request body .convertBodyTo(DOMSource.class) //convert content to a DOMSource instance .to("cxf:" + "http://localhost:8040/services/WebService" //service address + "?" + "wsdlURL=http://localhost:8040/services/WebService?wsdl" //wsdl url + "&" + "dataFormat=CXF_MESSAGE" //dataformat type ) .convertBodyTo(String.class) .to("log:output");可以看到除了把 RAW 变成 CXF_MESSAGE,还添加了 " .convertBodyTo(DOMSource.class)" 用于转换 " setBody(constant(content))" 里的内容,否则会得到异常,返回的结果和 RAW一样:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"/> <soap:Body> <tns:WebServiceOperationResponse1 xmlns:tns="http://www.talend.org/service/"> <out>Hello</out> </tns:WebServiceOperationResponse1> </soap:Body> </soap:Envelope>
除了请求内容实例外,RAW 和 CXF_MESSAGE 主要区别在于:CXF_MESSAGE 拥有完全的 CXF interceptor功能,而他们中的一些则不能用在 RAW 类型上。
POJO
POJO 意味着请求的对象内容是一个普通的java对象,而不是xml结构的内容。camel-cxf 在底下会把这个对象转换成一个xml结构的内容,这点像我们之上JAX-WS一节介绍的内容。例如重用以上生成的 WebServiceOperationRequest1 类去创建请求:
from("timer:foo?repeatCount=1") .process(new Processor() { //use processor to set body public void process(Exchange arg0) throws Exception { WebServiceOperationRequest1 request1 = new WebServiceOperationRequest1(); request1.setIn("Hello1"); arg0.getIn().setBody(request1); } }) .to("cxf:" + "http://localhost:8040/services/WebService" // service address + "?" + "wsdlURL=http://localhost:8040/services/WebService?wsdl" // wsdl url + "&" + "serviceClass=org.talend.esb.camel.cxf.WebServicePortType" //serviceClass + "&" + "dataFormat=POJO" // dataformat type ).to("log:output");
和之前的代码比较,有一些变化:
- setBody() 换成一个 Processor
根据我们的讨论,一个 POJO 对象需要设置成body,因些这里使用 Processor 去设置body - 选项 serviceClass 需要被指定
当使用POJO时,需要指定 serviceClass 这个选项用来指定Service类。
输出对象不能被转成一个string,因此这里直接打印出来:
Exchange[ExchangePattern: InOnly, BodyType: org.apache.cxf.message.MessageContentsList, Body: [org.talend.esb.camel.cxf.WebServiceOperationResponse1@19aa5882]]
可以看出,返回的值是一个 org.talend.esb.camel.cxf.WebServiceOperationResponse1 实例。
Headers
有一些headers可以用来影响camel-cxf的行为,例如如果没有指定operation,那么在WSDL中定义的第一个会被使用到,例如在我们的示例 demo.wsdl 里有两个operation,总是会使用第一个。如果检测一下camel-cxf可用的选项,有两个和operation相关的:
|
Required |
Description |
|
No |
New in 2.4, this option will set the default operationName that will be used by the CxfProducer which invokes the remote service. |
|
No |
New in 2.4. This option will set the default operationNamespace that will be used by the CxfProducer which invokes the remote service. |
可以指定缺省值,而不是总使用第一个。除了选项,有两个header可以用来指定operation,一旦指定,缺省值就会被覆盖:
|
Description |
|
Specify the operation name which you are calling |
|
Specify the operation namespace which you are calling |
另外,默认 SOAPAction 的值是空的,可以通过指定header的方式指定它: setHeader("SOAPAction", constant("http://www.talend.org/service/WebServiceOperation2")) ,这个值会作为HTTP的头发送。实际上所有自定义的头都会作为HTTP的头发送,例如:
setHeader("CorrelationID", constant("ADLKLWE-SADFA-EWEFFSAD_SADFASLFKAF"))
在传输时会变成:CorrelationID: ADLKLWE-SADFA-EWEFFSAD_SADFASLFKAF
Interceptor & Feature
当使用 CXF两个重要的部分需要提到: Interceptor and Feature。以下是从CXF网站上拷过来的:
Feature: in CXF is a way of adding capabilities to a Server, Client or Bus. For example, you could add the ability to log messages for each of these objects, by configuring them with a LoggingFeature. To implement a Feature, you must subclass AbstractFeature. //cxf.apache.org/docs/features.html
Interceptor: the fundamental processing unit inside CXF. When a service is invoked, an InterceptorChain is created and invoked. Each interceptor gets a chance to do what they want with the message. This can include reading it, transforming it, processing headers, validating the message, etc.
在 camel-cxf中,我们通过CxfEndpoint 来安装 features 和 Interceptors,例如安装loggingFeature:
//create Endpoint Endpoint cxfEndpoint = endpoint("cxf:" + "http://localhost:8040/services/WebService" // service address + "?" + "wsdlURL=http://localhost:8040/services/WebService?wsdl" // wsdl url + "&" + "dataFormat=PAYLOAD" // dataformat type ); //add feature ((CxfEndpoint)cxfEndpoint).getFeatures().add(new LoggingFeature()); //create route from("timer:foo?repeatCount=1").setBody(constant(content)) // set request body .to(cxfEndpoint);
LoggingFeature会记录在client端和server端传输的输入输出消息。我们也可以使用Interceptors来达到同样的功能,如果看LoggingFeature的源码就会发现,在底层,它使用了LoggingInInterceptor 和 LoggingOutInterceptor,这就意味着我们可以用他们替换 LoggingFeature:
//create Endpoint Endpoint cxfEndpoint = endpoint("cxf:" + "http://localhost:8040/services/WebService" // service address + "?" + "wsdlURL=http://localhost:8040/services/WebService?wsdl" // wsdl url + "&" + "dataFormat=PAYLOAD" // dataformat type ); //add Interceptor ((CxfEndpoint)cxfEndpoint).getInInterceptors().add(new LoggingInInterceptor()); ((CxfEndpoint)cxfEndpoint).getOutInterceptors().add(new LoggingOutInterceptor()); //create route from("timer:foo?repeatCount=1").setBody(constant(content)) // set request body .to(cxfEndpoint);
结果和使用Feature是一样的。