概覽
CXF frontends 是一組編程的API,被用來開發(fā)和發(fā)布webservice。CXF支持兩種類型的frontend--JAX-WS和簡單frontend。這章將對JAX-WS frontend提供更加詳細(xì)的解釋。我們同樣也會展示如何使用簡單frontend API來構(gòu)建一個webservice。這個章節(jié)將會關(guān)注使用如下兩種frontend來開發(fā)基于SOAP的web服務(wù):
- JAX-WS frontend
- 簡單 frontend
關(guān)于JAX-WS frontend,我們將說明如下內(nèi)容:
- 使用編碼優(yōu)先的開發(fā)方式來開發(fā)webservice
- 使用契約優(yōu)先的開發(fā)方式來開發(fā)webservice
- 構(gòu)建一個動態(tài)客戶端或消費(fèi)者
- 基于Provider and Dispatch的實(shí)現(xiàn)
- 理解webservice上下文
JAX-WS frontend
CXF支持由Java Community Process(JCP)提供的JAX-WS 2.0 API 規(guī)范。JAX-WS是一個由JCP制訂的正式規(guī)范,它定義了API用來構(gòu)建,開發(fā)和部署webservice。CXF提供了它自己的JAX-WS實(shí)現(xiàn)來附加在JAX-WS標(biāo)準(zhǔn)規(guī)范上。CXF JAX-WS frontend為構(gòu)建不同種類的webservice提供了不公的API。拋開提供標(biāo)準(zhǔn)的基于WSDL的開發(fā)不講,它還在Provider and Dispatch接口的形式中提供了用于構(gòu)建基于XML服務(wù)的API。
有兩種方式來開發(fā)基于SOAP的JAX-WS webservice-編碼優(yōu)先和契約優(yōu)先。正如名字顯示的那樣,在編碼優(yōu)先的開發(fā)模式中,你將從編碼開始,然后將它轉(zhuǎn)化成WSDL。編碼優(yōu)先的開發(fā)方式適用于:在實(shí)現(xiàn)方法的輸入和輸出的對象格式較簡單時(shí),并且你想要將它們快速地暴露為webservice的時(shí)候。編碼優(yōu)先的開發(fā)方式是更簡單的,因?yàn)槟銓腏ava對象開始,而不必考慮如何生成WSDL和XSD,而這個過程會在Java對象無法被按照你的意愿映射到XML元素的時(shí)候?qū)е乱恍﹩栴}。注意,像CXF Java2WSDL 這樣基于實(shí)現(xiàn)方法的輸入輸出格式的工具,將會生成契約,包括為你生成XSD格式。比如,你不能暴露一個Map或者Collection作為一個輸出信息格式,因?yàn)闆]有標(biāo)準(zhǔn)的做法將它映射到XML schema,而這將會導(dǎo)致交互上的問題。
在契約優(yōu)先的開發(fā)方式中,開發(fā)者從一個已經(jīng)存在的WSDL artifact來構(gòu)建webservice。契約優(yōu)先的開發(fā)方式適用于:當(dāng)你已經(jīng)有了一個XML schema為webservice的操作定義了輸入輸出消息的格式的時(shí)候,或者想要更好的控制XML如何被映射到Java對象的時(shí)候。契約優(yōu)先的開發(fā)方式需要你很精通XSD和WSDL契約,因?yàn)槟銓⑹褂眠@些契約開始創(chuàng)建你的model。如果你在創(chuàng)建基于工業(yè)標(biāo)準(zhǔn)的服務(wù),你或許需要從契約優(yōu)先的開發(fā)方式開始,因?yàn)楣I(yè)消息的格式通常是XML schema的。
如果兩種方式你都熟悉,并且知道對象是如何映射到XML的,你可以使用編碼優(yōu)先的開發(fā)方式。CXF不僅支持這些開發(fā)方式,而且為多種數(shù)據(jù)綁定機(jī)制提供了支持,這些機(jī)制將幫助你把Java對象映射到XML。
我們將從編碼優(yōu)先的開發(fā)方式開始。
編碼優(yōu)先的開發(fā)方式
在這個部分,我們將以開發(fā)一個Java類,并且通過注解它將它轉(zhuǎn)化成一個service類開始。你將遵循如下的步驟完成webservice的開發(fā):
- 創(chuàng)建Service Endpoint Interface(SEI)
- 添加Java注解
- 發(fā)布服務(wù)
- 開發(fā)一個消費(fèi)者
- 運(yùn)行編碼優(yōu)先的例子
創(chuàng)建Service Endpoint Interface(SEI)
Service Endpoint Interface是一個Java接口,它定義了一個被用來暴露為服務(wù)的業(yè)務(wù)方法。這個服務(wù)方法被一個服務(wù)類實(shí)現(xiàn)。一個SEI可以通過兩種不同的方式來構(gòu)建:
- 從零構(gòu)建一個SEI組件
- 將存在的業(yè)務(wù)功能轉(zhuǎn)化成基于服務(wù)的組件
第一個方式--從零構(gòu)建一個SEI組件的方式是指,開發(fā)一個全新的webservice而不依賴于任何已經(jīng)存在的代碼或WSDL契約。它推薦你從寫服務(wù)接口開始,然后創(chuàng)建這個服務(wù)的實(shí)現(xiàn)類。寫服務(wù)接口總是好的實(shí)踐,因?yàn)樗鼮槟愕姆?wù)方法提供了一個適當(dāng)?shù)目蛻舳艘晥D。之后,實(shí)現(xiàn)類可以實(shí)現(xiàn)定義在接口中的方法。
第二個方式是指,拿到已經(jīng)存在的業(yè)務(wù)功能,然后把它轉(zhuǎn)化成基于服務(wù)的組件。大多數(shù)時(shí)候,你已經(jīng)開發(fā)過了業(yè)務(wù)邏輯,你只是想把它們作為服務(wù)方法暴露出去。你可以通過如下的方式來完成這個過程:開發(fā)一個SEI,并只定義那些你想要暴露成為一個服務(wù)方法的業(yè)務(wù)方法,然后使已經(jīng)存在的Java代碼實(shí)現(xiàn)那個SEI。另一個常用的方式是:創(chuàng)建包裝后的SEI和一個實(shí)現(xiàn)類,這個實(shí)現(xiàn)類可以使用已經(jīng)存在的實(shí)現(xiàn)類來完成自己的功能。
我們將從零創(chuàng)建一個SEI組件開始。我們開發(fā)一個OrderProcess
SEI并且實(shí)現(xiàn)它。下面的代碼顯示了這個OrderProcess
SEI:
package demo.order;
public interface OrderProcess {
String processOrder(Order order);
}
你可以看到,上面的代碼是一個簡單的POJO接口。它定義了一個抽象方法processOrder
,它接收一個Order
bean作為一個參數(shù)。OrderProcessImpl
實(shí)現(xiàn)類實(shí)現(xiàn)了processOrder
方法,并且這個方法將在之后作為一個webservice方法暴露出來。
我們接下來將實(shí)現(xiàn)這個接口的業(yè)務(wù)邏輯。你將要寫這個OrderProcessImpl
來實(shí)現(xiàn)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實(shí)現(xiàn)類,它實(shí)現(xiàn)類processOrder
方法。這個方法簡單地校驗(yàn)了訂單,并返回一個唯一的訂單ID。簡單起見,我們返回了一個靜態(tài)的訂單ID作為我們實(shí)現(xiàn)的一部分。在下一個部分,我們將通過注解它們的方式把SEI和實(shí)現(xiàn)類轉(zhuǎn)化成為webservice組件。
添加Java注解
Webservice注解被添加到一個Java類用來將其暴露成為一個服務(wù)組件。JAX-WS使用Java 5注解,它是Web Services Metadata為Java Platform規(guī)范(JSR-181)提供的,用來將一個組件轉(zhuǎn)化成為一個webservice。這些注解簡單地標(biāo)記,被用來為一個特定的組件或者方法定義一個特定上下文。每一個注解被一個或多個上下文的屬性所支持。在這個部分,我們將向我們的OrderProcess
SEI和實(shí)現(xiàn)類添加注解,并將它們轉(zhuǎn)化成一個服務(wù)組件。在這個部分,我們將覆蓋如下的webservice注解:
- javax.jws.WebService
- javax.jws.soap.SOAPBinding
javax.jws.WebService
一個Java組件可以通過添加一個@WebService
注解的方式被轉(zhuǎn)化成為一個服務(wù)。這個注解必須被同時(shí)定義在SEI和實(shí)現(xiàn)類中。@WebService
注解被定義在javax.jws.WebService
接口。
@WebService
注解支持如下的屬性:
讓我們注解我們的OrderProcess
SEI和OrderProcessImpl
實(shí)現(xiàn)類。下面的代碼展示了@WebService
注解的使用:
package demo.order;
import javax.jws.WebService;
@WebService
public interface OrderProcess {
String processOrder(Order order);
}
@WebService
被直接聲明在接口或者類之上。它注解了這個類或者接口作為一個webservice的類或者接口。在上面的代碼中,OrderProcess
接口通過@WebService
注解,來被定義成為一個webservice接口。
讓我們看一下OrderProcessImpl
實(shí)現(xiàn)類。下面的代碼展示了被注解的OrderProcessImpl
實(shí)現(xiàn)類:
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
。這個服務(wù)名稱被消費(fèi)者用來獲取遠(yuǎn)程接口的句柄來調(diào)用服務(wù)方法。端口名稱簽名了endpoint名稱。服務(wù)的endpoint也同樣作為一個服務(wù)端口在服務(wù)發(fā)布的地方被調(diào)用。這里的名字是OrderProcessPort
。
有許多其它可選的注解被用來和@WebService
搭配使用,用來更加完整地描述一個webservice。其它注解可以添加更精細(xì)的細(xì)節(jié)到一個服務(wù)上。總是推薦你使用這些注解來描述你的webservice,以便于生成的WSDL文檔有更多的被這些注解指定的細(xì)節(jié)。如果你沒有使用這些可選的注解,那么WSDL將被按照默認(rèn)的規(guī)則生成,正如前面表格中討論的一樣。
javax.jws.soap.SOAPBinding
@SOAPBinding
注解被定義在javax.jws.soap.SOAPBinding
接口中。這個注解在你想要為你的服務(wù)制定SOAP綁定的時(shí)候使用。
這個注解支持如下的屬性:
SOAP綁定在webservice交互中扮演著重要的角色。讓我們更加詳細(xì)地看看兩種風(fēng)格的SOAP綁定。
RPC 風(fēng)格 v.s Document 風(fēng)格
Webservice的SOAP交互風(fēng)格在服務(wù)提供者和消費(fèi)者之間交互SOAP XML消息中扮演著重要的角色。有兩種SOAP消息風(fēng)格--Document和RPC。SOAP消息的風(fēng)格被作為SOAP綁定定義在WSDL文檔中。一個SOAP綁定能夠擁有一個基于編碼的使用(encoded use) 或者 基于字面的使用(encoded use or a literal use)。編碼,這個詞暗示了消息將使用某種格式被編碼,而字面的表示使用純文本消息,而不做任何編碼邏輯。
正如名字顯示的那樣,Document風(fēng)格將XML文檔作為有效的載荷來添加到定義良好的契約中,通常的做法是使用XML schema定義來創(chuàng)建。XML schema格式指定了被消費(fèi)者調(diào)用的服務(wù)消息的契約。XML schema定義了服務(wù)提供者和服務(wù)消費(fèi)者之間的request和response消息的格式。這些消息的格式可以被服務(wù)的提供者和消費(fèi)者驗(yàn)證為合法。Document literal風(fēng)格對webservice交互來完成協(xié)作來說是更受歡迎的方式。
另一方面,RPC(Remote Procedure Call)風(fēng)格表明了SOAP主體包含一個XML的方法展示。為了序列化方法的參數(shù)到SOAP消息,以便于它能夠被任何webservice實(shí)現(xiàn)反序列化。SOAP規(guī)范定義了一系列的編碼規(guī)則。由于RPC通常被用在連接SOAP的編碼規(guī)則,這個連接將被引用為RPC/encoded。你用養(yǎng)有一種RPC/literal交互風(fēng)格模式,這種模式中你沒有任何的編碼格式,但是消息依然受限于RPC 基于方法的交互,這種方式下,消息不能被驗(yàn)證為合法的,因?yàn)樗麄兣c任何XML schema定義之間都沒有關(guān)聯(lián)。你或許應(yīng)該避免開發(fā)RPC風(fēng)格的webservice,因?yàn)樗鼈冇性S多協(xié)作性上的問題。
下面的代碼展示了@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
注解被放置在服務(wù)方法聲明的上面。
@WebMethod
注解支持如下的屬性:
下面的代碼片段展示了@WebMethod
注解的使用:
@WebMethod (name="processOrder")
public String processOrder(Order order) {
// ...
}
JAX-WS webservice注解支持一系列的其它注解,比如:@RequestWrapper
, @ResponseWrapper
, @Oneway
等等。它們中的一些將稍后解釋。
發(fā)布服務(wù)
發(fā)布服務(wù)意味著將服務(wù)組件注冊到服務(wù)器上,并且通過endpoint URL使其可以被消費(fèi)者使用。你將把OrderProcess
webservice發(fā)布到一個特定的endpoint URL上。在本例中的endpoint URL將會是http://localhost:8080/OrderProcess
。你將開發(fā)一個服務(wù)器組件來發(fā)布你的OrderProcess
服務(wù)。對這個例子來說,我們將使用Java 5提供的輕量級的web服務(wù)器來發(fā)布我們的服務(wù)。CXF提供了它自己獨(dú)立的服務(wù)器組件--JaxWsServerFactoryBean
來發(fā)布webservice。
下面的代碼展示了服務(wù)器代碼:
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);
}
}
用于發(fā)布Endpoint
類的靜態(tài)方法提供了一個簡便的方式來發(fā)布和測試JAX-WS webservice。這個方法接受endpoint URL和一個OrderProcessImpl
類對象作為參數(shù)。這個publish
方法在URL http://localhost:8080/OrderProcess
上創(chuàng)建一個輕量級的web服務(wù)器,并且在那個位置部署這個服務(wù)。這個輕量級的web服務(wù)器在JVM中運(yùn)行一分鐘然后自動終止。一種能夠看到服務(wù)的WSDL契約的方法是在web瀏覽器中訪問這個URL:
http://localhost:8080/OrderProcess?wsdl
開發(fā)一個消費(fèi)者
webservice的消費(fèi)者調(diào)用服務(wù)方法來獲得需要的結(jié)果。在這一部分,我們將開發(fā)一個Client
類來查找我們的OrderProcess
服務(wù),并且調(diào)用它的processOrder
方法。下面的代碼展示了服務(wù)消費(fèi)者組件:
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);
}
}
客戶端的代碼做了以下的事情:
- 它首先創(chuàng)建了WSDL URL。這個WSDL URL是
http://localhost:8080/OrderProcess?wsdl
。這個URL表明了WSDL 文檔的位置。
在運(yùn)行客戶端程序之前,你可以通過訪問以上的URL來驗(yàn)證這個服務(wù)是否可用。如果你能夠看見WSDL,那么就意味著這個
OrderProcess
服務(wù)被成功地發(fā)布了。
- 接下來創(chuàng)建了
Service
對象。這個Service
對象使用了靜態(tài)的create
方法被創(chuàng)建。這個方法接受WSDL URL和服務(wù)名稱作為參數(shù)。這個服務(wù)名稱OrderProcessService
是一個QName
,并且在WSDL文檔中被映射到<wsdl:service>
元素。<wsdl:service>
元素定義了服務(wù)的endpoint。 - 使用
Service
對象,你通過調(diào)用getPort
方法獲得SEI句柄的代理組件。getPort
方法接受port name和SEI類作為參數(shù)。接口名稱OrderProcessPort
是一個QName
,并且在WSDL文檔中被映射到<wsdl:port>
元素。這個SEI類是OrderProcess
。 - 代理組件在接下來被用來調(diào)用服務(wù)方法
processOrder
。在調(diào)用方法之前,你必須安置好Order
bean,并把它傳給processOrder
方法。這個方法在服務(wù)器上被調(diào)用,并返回訂單ID。