歷史:
Axis:-->Axis2
XFire:-->(WebService框架)
Celtrix:(ESB框架)
CXF(XFire+Celtrix)
優點:
CXF號稱是SOA框架,我們做WS只會用到XFire。
CXF內置Jetty Web服務器。
使用CXF開發WebServer端組件都需要“接口”和“實現類”兩部分。
支持多種數據格式:xml和JSON(Restful)。
并可以與SPRing進行快速無縫的整合
靈 活的 部 署 : ant(build.xml) maven(pom.xml)
可 以運 行 有Tomcat,Jboss,Jetty(內 置web 服 務器),IBMWebsphere,BeaWebLogic上面。
官網地址:http://cxf.apache.org/
為了方便使用CXF下的工具,把CXF/bin配置到PATH里去。當然最好先配置CXF_HOmE,然后在Path里面直接引用。
常用jar庫文件:
除了jetty服務器相關的類外,有7個必須的包:
asm是字節碼相關的包;
logging是日志相關的包;
cxf是框架核心包;
geronimo-servlet是servlet相關包;
neethi是網絡安全相關包;
wsdl4j是wsdl解析用的包;
xmlschema是schema相關的包。
在cxf中,也提供了一個用于生成客戶端調用代碼的工具。它的功能就如同wsimport一樣。
先讓我們了解一下cxf的wsdl2java工具,可以生成一堆客戶端調用的代碼。
此工具位于cxf_home/bin目錄下。參數與wsimport有所不同。
它主要包含以下參數:
-d參數,指定代碼生成的目錄。
-p參數,指定生成的新的包結構。
需要說明的是,由于wsdl2java是根據jdk1.7生成的本地代碼,所以,需要對生成的代碼做一點點修改。
Wsdl2java -d . -p cn.itsource.ws.cxf.aahttp://localhost:9998/hi?wsdl
1、注意:由于使用的是apache-cxf較新版本,所以支持jdk1.7的。對于生成的Service要進行稍微的修改。
在jdk1.6中 的javax.xml.ws.Service的 構 造 方 法 接 收 二個 參 數 為:(URLurl,QName qname);
在jdk1.7中的javax.xml.ws.Service的構造方法中接 收三個參數為:(URLurl,QName qname,WebServiceFeature... features);
2、將生成的代碼,拷貝到項目目錄,然后使用以前相同方法調用。
注意:如果你對@WebMethod設置了header=true參數,將會在調用時多傳遞一個參數。它參數可以直接傳null值。對于這種情況,可以將header=true修改成header=false然后再重要獲取客戶端源代碼。
3、大家可能發現了,使用cxf生成的客戶端代碼與wsimport差不多,甚至是一樣,
那為什么還要使用cxf呢?它較好的發布方式、較容易的與其他服務器集成、及與Spring的完美結合都不得不讓我們使用cxf。
此時服務類和接口,都可以不使用@WebService標注。(因為這套標準在JDK1.6之前就有了)
//服務端發布
1.創建ServerFactoryBean的對象,用于發布服務
2.設置服務發布地址
3.設置服務發布的接口
4.設置服務的發布對象
5.使用create方法發布服務
JaxWsServerFactoryBeanbean=newJaxWsServerFactoryBean();
// 2.設置服務發布地址
bean.setAddress("http://localhost:9998/hi");
// 3.設置服務發布的接口
bean.setServiceClass(IHelloService.class);//接口設置ServiceClass
// 4.設置服務的發布對象
bean.setServiceBean(new HelloServiceImpl());
//============攔截器==========//
bean.getInInterceptors().add(newLoggingInInterceptor());
bean.getInInterceptors().add(new AuthInInterceptor());
//============攔截器==========//
// 5.使用create方法發布服務
bean.create();
System.out.println("服務發布成功!");
其中設置接口實現類或設置服務類對象任意一個都可以。
//客戶端:
1.創建ClientProxyFactoryBean的對象,用于接收服務
2.設置服務的發布地址,表示去哪里過去服務
3.設置服務的發布接口,使用本地的代理接口
4.通過create方法返回接口代理實例
5.調用遠程方法
JaxWsProxyFactoryBeanbean=newJaxWsProxyFactoryBean();
// 2.設置服務的發布地址,表示去哪里過去服務
bean.setAddress("http://192.168.1.254:9999/hi");//不能加?wsdl
// 3.設置服務的發布接口,使用本地的代理接口
bean.setServiceClass(IHelloService.class);
//===================攔截器=======================//
bean.getOutInterceptors().add(newLoggingOutInterceptor()); //先獲取框子,直接往里面放
//自定義攔截器
bean.getOutInterceptors().add(new AuthOutInterceptor("admin","1")); //先獲取框子,直接往里面放
//===================攔截器=======================//
// 4.通過create方法返回接口代理實例
IHelloServicehelloService= (IHelloService)bean.create();
System.out.println(helloService.getClass().getName());
System.out.println(helloService.sayHello("小豬"));
支持@WebService等注解
JaxWsServerFactoryBean是ServerFactoryBean的子類,也是功能擴展類。
但在CXF的API文檔中沒有提供此類API,請通過查看源代碼的方式獲取此類的幫助。此類,必須要在被發布為服務的類和接口上添加@WebService注解,如果不加注解,雖然不出錯,但也不會對外暴露任何方法。使用此類生成的wsdl文件更加規范,建議使用該類。
服務端:
1.創建JaxWsServerFactoryBean的對象,用于發布服務
2.設置服務發布地址
3.設置服務發布的接口
4.設置服務的發布對象
5.使用create方法發布服務
這種方式發布必須使用接口。
客戶端:
1.創建JaxWsProxyFactoryBean的對象,用于接收服務
2.設置服務的發布地址,表示去哪里過去服務
3.設置服務的發布接口,使用本地的代理接口
4.通過create方法返回接口代理實例
5.調用遠程方法
使用自帶的日志攔截器:可以用來捕獲soap的消息
LoggingInInterceptor–信息輸入時的攔截器-請求
LoggingOutInterceptor–信息輸出時的攔截器-響應
JaxWsServerFactoryBean bean = newJaxWsServerFactoryBean();
// 2.設置服務發布地址
bean.setAddress("http://localhost:9998/hi");
// 3.設置服務發布的接口
bean.setServiceClass(IHelloService.class);//接口設置ServiceClass
// 4.設置服務的發布對象
bean.setServiceBean(new HelloServiceImpl());
//============攔截器==========//
bean.getInInterceptors().add(new LoggingInInterceptor());
bean.getInInterceptors().add(new AuthInInterceptor());
//============攔截器==========//
// 5.使用create方法發布服務
bean.create();
System.out.println("服務發布成功!")
// 1.創建JaxWsProxyFactoryBean的對象,用于接收服務
JaxWsProxyFactoryBeanbean=newJaxWsProxyFactoryBean();
// 2.設置服務的發布地址,表示去哪里過去服務
bean.setAddress("http://localhost:9998/hi");//不能加?wsdl
// 3.設置服務的發布接口,使用本地的代理接口
bean.setServiceClass(IHelloService.class);
//===================攔截器=======================//
bean.getOutInterceptors().add(newLoggingOutInterceptor()); //先獲取框子,直接往里面放
//自定義攔截器
bean.getOutInterceptors().add(new AuthOutInterceptor("admin","1")); //先獲取框子,直接往里面放
//===================攔截器=======================//
// 4.通過create方法返回接口代理實例
IHelloServicehelloService= (IHelloService)bean.create();
System.out.println(helloService.getClass().getName());
System.out.println(helloService.sayHello("小豬"));
場景就是做服務的訪問權限判斷
攔截器使用的步驟:
1、聲明一個類,來充當攔截器。這個類需要繼承或實現某個接口或基類。
2、完成攔截器的邏輯。
3、把攔截器配置到訪問或者發布邏輯中。
自 定 義 攔 截 器 需 要 實 現Interceptor接 口 , 一 般 的 我 們 直 接 繼承AbstractPhaseInterceptor類。
做一個權限控制例子。
要求客戶端把請求發送到服務端的時候,服務端根據客戶端發送的SAOP消息,從<Header>里取出登錄信息,以做權限驗證。那么應該在客戶端添加一個自定義的輸出攔截器,在服務端添加一個自定義的輸入攔截器。
1.1.1 客戶端添加權限攔截器
得在攔截器里拼裝一個SOAP消息的Header片段,如下:
<soap:Header>
<authHeader>
<username>用戶名</username>
<passWord>密碼</password>
</authHeader>
</soap:Header>
AuthOutInterceptor類:
publicclass AuthOutInterceptor
extendsAbstractPhaseInterceptor<SoapMessage>{
private Stringusername;
private Stringpassword;
publicAuthOutInterceptor(String username,String password) {
//在哪個階段攔截
super(Phase.PREPARE_SEND);//準備發送SOAP消息的時候,啟用攔截器,添加
this.username=username;
this.password=password;
}
public voidhandleMessage(SoapMessage msg) throws Fault {
//使用工具創建dom對象
Documentdoc = DOMUtils.createDocument();
//使用DOM對象創建符合XML規范的子元素
//創建頭信息中認證信息的根
Element root =doc.createElement("autherHeader");
//創建頭信息中認證信息的內容
Element nameEle =doc.createElement("username");
Element pwdEle =doc.createElement("password");
nameEle.setTextContent(username);
pwdEle.setTextContent(password);
root.appendChild(nameEle);
root.appendChild(pwdEle);
Headerheader =new Header(new QName("sirius"),root);
msg.getHeaders().add(header);
}
}
客戶端代碼:
publicvoid testCxf() {
JaxWsProxyFactoryBeanbean = new JaxWsProxyFactoryBean();
//設置訪問地址
bean.setAddress("http://localhost:9527/hello");
//設置服務接口,直接使用本項目中的接口
bean.setServiceClass(IHelloKity.class);
//通過create方法返回接口實例
bean.getOutInterceptors().add(
new AuthOutInterceptor("sirius","1314"));
bean.getOutInterceptors().add(newLoggingOutInterceptor());
IHelloKity hello =(IHelloKity) bean.create();
String ret =hello.sayHi("sirius", 18);
System.out.println(ret);
}
添加攔截后SOAP內容如下:
信息:Outbound Message
---------------------------
ID:1
Address:http://localhost:9527/hello
Encoding:UTF-8
Content-Type:text/xml
Headers:{Accept=[*/*], SOAPAction=[""]}
Payload:
<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<autherHeader>
<username>sirius</username>
<password>1314</password>
</autherHeader>
</soap:Header>
<soap:Body>
<ns2:sayHixmlns:ns2="http://cxf.ws.itc
<arg0>sirius</arg0>
<arg1>18</arg1>
</ns2:sayHi>
</soap:Body>
</soap:Envelope>
服務端從SOAP中去取Header消息,若取到,再根據以下格式解析XML,得到username和password數據。
<soap:Header>
<authHeader>
<username>用戶名</username>
<password>密碼</password>
</authHeader>
</soap:Header>
AuthInInterceptor類:
publicclass AuthInInterceptor
extendsAbstractPhaseInterceptor<SoapMessage> {
publicAuthInInterceptor() {
super(Phase.PRE_INVOKE);
System.out.println("構造器");
}
publicvoid handleMessage(SoapMessage msg) throws Fault {
List<Header>headers = msg.getHeaders();
if (headers == null|| headers.isEmpty()) {
throw new Fault(newIllegalArgumentException("沒有header"));
}
Header header=headers.get(0);
//在客戶端的過濾器里,把element放入了header中,所以這里可以
Element root =(Element) header.getObject();
String username =
root.getElementsByTagName("username").item(0).getTextContent();
String password =
root.getElementsByTagName("password").item(0).getTextContent();
//查詢數據庫驗證
if (!"sirius".equals(username) || !"1314".equals(password)) {
thrownewFault(newIllegalArgumentException("用戶名或密碼不對!"));
}
System.out.println("恭喜你可以開始了");
}
}
服務端代碼:
publicstatic void main(String[] args) {
JaxWsServerFactoryBeanbean = new JaxWsServerFactoryBean();
//綁定到發布地址的端口
bean.setAddress("http://localhost:9527/hello");
//設置服務接口,如果沒有接口,則為本類
bean.setServiceClass(IHelloKity.class);
//設置服務類對象
bean.setServiceBean(newHelloKityWS());
//發布服務
bean.getInInterceptors().add(newAuthInInterceptor());
bean.create();
System.out.println("啟動OK");
}
package cn.itsource.ws.cxf.server;
import java.util.List;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
importorg.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
public class AuthInInterceptor extendsAbstractPhaseInterceptor<SoapMessage> {
publicAuthInInterceptor() {
super(Phase.PRE_INVOKE);//必須在執行之前,接收之后
//TODO Auto-generated constructor stub
}
@Override
publicvoid handleMessage(SoapMessage soapMessage) throws Fault {
//獲取所有的頭信息
List<Header>headers = soapMessage.getHeaders();
if(null == headers || headers.size()==0) {
thrownew Fault(new IllegalaccessException("請攜帶認證信息!"));
}
//很確定認證頭就是第一個
Headerheader = headers.get(0);
if(null == header) {
thrownew Fault(new IllegalAccessException("請攜帶認證信息!"));
}
//獲取AuthInfo節點
Elementele = (Element) header.getObject();
StringuserName = ele.getElementsByTagName("userName").item(0).getTextContent();
Stringpassword =ele.getElementsByTagName("password").item(0).getTextContent();
//可以使用用戶名和密碼到數據庫中做驗證,現在寫死。
if("admin".equals(userName)&&"1".equals(password)) {
return;//直接返回就是放行
}
thrownew Fault(new IllegalAccessException("用戶名和密碼不正確!"));
}
}
package cn.itsource.ws.cxf.client;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
importorg.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
*
*@author Administrator
*
*/
public class AuthOutInterceptor extendsAbstractPhaseInterceptor<SoapMessage> {
privateString userName;
privateString password;
publicAuthOutInterceptor(String userName,String password) {
super(Phase.PREPARE_SEND);//設置攔截器的階段
this.userName= userName;
this.password= password;
}
/**
* 把權限認證的信息加到SOAP的Header里面
* <soap:Header>
<authInfo>
<userName>admin</userName>
<password>1</password>
</authInfo>
</soap:Header>
*/
@Override
publicvoid handleMessage(SoapMessage message) throws Fault {
//第一步:通過工具類獲取Document對象,就可以使用對象創建權限信息的節點
Documentdocument = DOMUtils.createDocument();
//第二步:創建權限信息的節點
ElementauthInfoEle = document.createElement("authInfo");
ElementuserNameEle = document.createElement("userName");
userNameEle.setTextContent(userName);
ElementpasswordEle = document.createElement("password");
passwordEle.setTextContent(password);
authInfoEle.appendChild(userNameEle);
authInfoEle.appendChild(passwordEle);
//localPart是一個唯一標識,可以隨便寫
QNameqName = new QName("xxxx");// <autoInfo></authInfo>
//QNameqName = new QName(namespaceURI, localPart)//<autoInfoxmlns="http://itsource.cn"></authInfo>
//QNameqName = new QName(namespaceURI, localPart, prefix)//<autoInfoxmlns:xxx="http://itsource.cn"></authInfo>
//有兩個參數:后面的一個為要添加節點,第一個為修改該節點的Namespace
Headerheader = new Header(qName , authInfoEle);
//第三步:把權限信息的節點添加到header里面
message.getHeaders().add(header);
}
publicString getUserName() {
returnuserName;
}
publicvoid setUserName(String userName) {
this.userName= userName;
}
publicString getPassword() {
returnpassword;
}
publicvoid setPassword(String password) {
this.password= password;
}
}
使用CXF的最大好處就是能快速的Spring進行集成,現在在服務端來做一個和集成Spring應用。分為如下6步:
l 建立標準的Web工程結構;
l 拷貝CXF的核心jar包;
l 拷貝Spring的核心jar包;
l 在web.xml中配置cxf的核心servlet--CXFServlet;
l 在Spring文件中配置CXF核心bean;
l 服務器端發布服務;
官方提供的lib里有75個包,但不是所有的我們都需要,在這里抽取一個mini版的CXF庫,一共7個包。
導入17spring依賴包:
在resource中添加:
applicationContext.xml
在web.xml中配置spring上下文:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在web.xml中配置cxf的核心servlet—CXFServlet
在web.xml中添加CXF核心servlet
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
通過查看前面配置的CXFServlet,可以看出,如果沒有在工程WEB-INF下有個cxf-servlet.xml,在servlet代碼執行時不會創建spring環境。
這時我們直接在applicationContext中手動集成CXF和Spring
<!-- cxf的核心bean -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<!-- SOAP相關配置 -->
<importresource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
服務端的發布分為兩種情況:
<!—1、簡單發布(不支持接口,實現類不能直接實例化)-->
<beanid="hellobean" class="test.HelloKityWSImpl"/>
<jaxws:endpointid="hello" implementor="#hellobean" address="/hi"/>
<!-- 2、通過接口的方式發布服務 -->
<beanid="hellobean" class="test.HelloKityWSImpl" />
<jaxws:server id="hello"address="/hi" serviceClass="test.IHelloKity">
<jaxws:serviceBean>
<refbean="hellobean" />
</jaxws:serviceBean>
<!-- 添加攔截器 -->
<jaxws:inInterceptors>
<beanclass="org.apache.cxf.interceptor.LoggingInInterceptor" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<beanclass="org.apache.cxf.interceptor.LoggingOutInterceptor" />
</jaxws:outInterceptors>
</jaxws:server>
第一種達到的效果和最初使用JDK中的Endpoint.publish效果相同,可以直接理解為這樣配置,由Spring容器自動調用上面方法發布服務。這種發布不支持接口,應用不廣。
第二種方式配置更為細膩,做到了接口和實現類的分離,配置靈活性更大
紅色部分是攔截器配置,與之前代碼中添加的攔截器效果相相同。
完成了上面配置,整個服務端服務就發布成功了。
使用CXF的最大好處就是能快速的Spring進行集成,現在在客戶端來做一個和集成Spring應用。分為如下5步:
l 建立標準的Web工程結構;
l 拷貝CXF的核心jar包;
l 拷貝Spring的核心jar包;
l 在Spring文件中配置服務代理接口;
l 客戶端使用服務;
官方提供的lib里有75個包,但不是所有的我們都需要,在這里抽取一個mini版的CXF庫,一共7個包。
導入17spring依賴包:
在resource中添加:
applicationContext.xml
在web.xml中配置spring上下文:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在客戶端先找到需要消費服務的WSDL文件地址,使用wsimport導入服務生成客戶端代碼,保留接口和接口所依賴的domain。其他都刪掉。
這時我們直接在applicationContext中手動配置需要消費的接口
<!-- 客戶端通過jaxws:client標簽配置以后,就可以當成本地bean來進行使用 -->
<jaxws:client id="hello"
serviceClass="ws.client.test.IHelloKity"
wsdlLocation="http://localhost:8080/ws/hi?wsdl"/>
在客戶端的編寫測試代碼:
public class TestClient {
@Test
public void testCall() throws Exception {
ApplicationContext ctx =
newClassPathXmlApplicationContext("applicationContext.xml");
IHelloKityservice = ctx.getBean("hello", IHelloKity.class);
System.out.println("service: " +service.sayHi("sirius"));
}
}
得到打印結果:
service : sirius,你好!
新聞熱點
疑難解答