使用CXF實現基於Soap協議的WebService

本文介紹使用CXF實現基於Soap協議的WebService(CXF的版本是3.0.0)前端

一. 前言java

Java有三種WebService規範:Jax-WS,Jax-RS,Jaxmweb

1. Jax-WS(Java Api for XML-Based WebService):實現Soap協議(Simple Object Access Protocol)(用的也很少了)
2. Jax-RS(Java Api for Resource-Based WebService):實現Rest方式(Representational State Transfer)(推薦)
3. Jaxm支持文件傳輸,暴露更多底層細節(不推薦)spring

二. 引入依賴apache

<!-- Jax-WS前端控制模塊,處理服務業務的請求 -->
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-frontend-jaxws</artifactId>
  <version>3.0.0</version>
</dependency>瀏覽器

<!-- 數據傳輸模塊(與Rest同樣) -->
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-transports-http</artifactId>
  <version>3.0.0</version>
</dependency>併發

<!-- 引入內置的Jetty,如在Tomcat中發佈服務能夠不引入(與Rest同樣)  -->
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-transports-http-jetty</artifactId>
  <version>3.0.0</version>
</dependency>app

三. 編寫SEI(Service Endpoint Interface)frontend

1. 接口ide

@WebService
public interface HelloWorld {

  //方法名和接口名能夠不一致,默認一致,若是一致,能夠省略operationName,甚至省略WebMethod
  @WebMethod(operationName = "sayHiString")
  public String sayHelloString(@WebParam(name = "name") String name);

  //對象參數User代碼略,User的定義可能不在HelloWorld的包中,在或不在,對後期調用會產生影響
  @WebMethod(operationName = "sayHiUser")
  public User sayHelloUser(User user);

}

2. 實現類

//endpointInterface的值是接口的全名

@WebService(endpointInterface = "net.jmystudio.cxf.soap.HelloWorld", serviceName = "HelloWorld")
public class HelloWorldImpl implements HelloWorld {

  @Override
  public String sayHelloString(String name) {
    return "Hello, (String) " + name;
  }

  @Override
  public User sayHelloUser(User user) {
    return new User("Soap_" + user.getName());
  }

}

四. 發佈服務

1. 發佈方式一:使用默認的Jetty時,使用Java內置的Endpoint

javax.xml.ws.Endpoint.publish("http://localhost:8088/testcxf/cxf/soap/hello1", new HelloWorldImpl());

 (http://localhost:8088/testcxf/cxf/soap/hello1?wsdl)

 2. 發佈方式二:使用默認的Jetty時,使用CXF提供的JaxWsServerFactoryBean(與Rest相似)

// 1). 服務端工廠類
JaxWsServerFactoryBean server = new JaxWsServerFactoryBean();

// 2). 設置了二個屬性(setServiceClass可省)
server.setAddress("http://localhost:8088/testcxf/cxf/soap/hello2");
server.setServiceBean(new HelloWorldImpl());

// 添加輸入&輸出日誌(可選)
server.getInInterceptors().add(new LoggingInInterceptor());
server.getOutInterceptors().add(new LoggingOutInterceptor());

// 3). 建立併發布服務,會發起一個http服務,默認使用Jetty
server.create();

(http://localhost:8088/testcxf/cxf/soap/hello2?wsdl)

3. 發佈方式三:在Web應用中,使用CXFNonSpringServlet發佈(實際是顯式調用了發佈方式一(或二)

a. 在web.xml中添加CXFNonSpringServlet的實現類(與Rest同樣)

<servlet>
  <servlet-name>CXFNonSpring</servlet-name>
  <servlet-class>net.jmystudio.servlet.WebServiceNonSpringServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>CXFNonSpring</servlet-name>
  <url-pattern>/cxfns/*</url-pattern>
</servlet-mapping>

b. 實現WebServiceNonSpringServlet類(與Rest相似)

import org.apache.cxf.transport.servlet.CXFNonSpringServlet;

public class WebServiceNonSpringServlet extends CXFNonSpringServlet {

  @Override
  protected void loadBus(ServletConfig servletConfig) {
    super.loadBus(servletConfig);
    //使用Endpoint,代碼相似發佈方式一,也能夠使用JaxWsServerFactoryBean,代碼相似發佈方式二,此處略
    javax.xml.ws.Endpoint.publish("/soap/hello3", new HelloWorldImpl());

  }

}

(http://localhost:8090/testcxf/cxfns/soap/hello3?wsdl)(應用包名是testcxf)

4. 發佈方式四:在Web應用中,整合Spring+CXFServlet發佈(實際是隱式調用了發佈方式一(或二)(最經常使用)

a. 須要引入Spring相關的Jar包(與Rest同樣)

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.1.6.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>4.1.6.RELEASE</version>
</dependency>

b. 在web.xml中添加Spring配置和CXFServlet(與Rest同樣)

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring*.xml</param-value>
</context-param>
<listener>

  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<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>/cxf/*</url-pattern>
</servlet-mapping>

c.添加關於cxf的spring配置文件(例spring-cfx-soap.xml)(與Rest相似)

<!-- 初始化cxf servlet -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<!-- 日誌攔截器bean -->
<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>

<!-- 發佈方式1:使用Endpoint -->
<jaxws:endpoint address="/soap/hello5" implementor="net.jmystudio.cxf.soap.HelloWorldImpl" >
  <jaxws:inInterceptors>
    <ref bean="loggingInInterceptor"/>
  </jaxws:inInterceptors>
  <jaxws:outInterceptors>
    <ref bean="loggingOutInterceptor"/>
  </jaxws:outInterceptors>
</jaxws:endpoint>

<!-- 發佈方式2:使用JaxWsServerFactoryBean -->
<jaxws:server address="/soap/hello6" serviceClass="net.jmystudio.cxf.soap.HelloWorldImpl" >
  <jaxws:inInterceptors>
    <ref bean="loggingInInterceptor"/>
  </jaxws:inInterceptors>
  <jaxws:outInterceptors>
    <ref bean="loggingOutInterceptor"/>
  </jaxws:outInterceptors>
</jaxws:server>

(http://localhost:8090/testcxf/cxf/soap/hello5?wsdl) (應用包名是testcxf)
(http://localhost:8090/testcxf/cxf/soap/hello6?wsdl) 

五. 調用方式

不管使用哪一種發佈方式,發佈成功後,在瀏覽器中輸入 $address+"?wsdl",都可看到該WebService的接口定義的詳細內容

例如 http://localhost:8088/testcxf/cxf/soap/hello1?wsdl

代碼調用主要有2種方式

1. 調用方式一:使用JaxWsProxyFactoryBean得到靜態Client,顯式依賴WebService接口,須要引入服務提供方提供的jar包)(不推薦)(與Rest相似)

 // 1). 客戶端工廠類
JaxWsProxyFactoryBean proxy = new JaxWsProxyFactoryBean();

 // 2). 設置了兩個屬性
proxy.setAddress("http://localhost:8088/testcxf/cxf/soap/hello1");
proxy.setServiceClass(HelloWorld.class);

// 添加輸入&輸出日誌(可選)
proxy.getInInterceptors().add(new LoggingInInterceptor());
proxy.getOutInterceptors().add(new LoggingOutInterceptor());

// 3). 建立會發起一個http請求
HelloWorld  staticClient =  (HelloWorld) proxy.create();

// 4). 調用方法,得到數據
System.out.println(staticClient.sayHelloString("Jimmy"));
System.out.println(staticClient.sayHelloUser(new User("Tony")));

2. 調用方式二:使用JaxWsDynamicClientFactory得到動態Client,(無需引入服務提供方提供的jar包)

// 1). 根據接口定義url,得到Client
JaxWsDynamicClientFactory clientFactory = JaxWsDynamicClientFactory.newInstance();
Client dynamicClient = clientFactory.createClient("http://localhost:8088/testcxf/cxf/soap/hello1?wsdl");

// 2). 調用接口,得到數據 
//sayHiString是WebMethod中定義的接口名,不是方法名
Object[] result = dynamicClient.invoke("sayHiString", "Kevin"); 
System.out.println(result[0]);
result = dynamicClient.invoke("sayHiUser", new User("Cherry"));
System.out.println(result[0]);

Note: 調用方式二中,參數是對象的狀況須要特別注意,User的定義是否在SEI(此例是HelloWorld)的包中?

若是發佈時不指定特定的targetNamespace的話,默認的targetNamespace是SEI的所在包名(逆序),而不是參數User所在包名(2個包名能夠不同的),這時調用的時候,必須傳遞定義在targetNamespace的User(不是發佈時代碼中的User,具體可查看該接口的wsdl,會發現此User非彼User),否則會報兩個User的java.lang.ClassCastException

解決辦法是:

a.最簡單:發佈時,定義的User和SEI同在一個包中(推薦)
b.動態解析wsdl,須要得到ServiceInfo/BindingInfo/BindingOperationInfo/BindingMessageInfo/MessagePartInfo/PropertyDescriptor等一系列對象,逐步得到接口定義的詳細內容後,動態生成參數對象,難度很大(不推薦)

3. 其它調用方式:使用HttpClient或HttpURLConnection等鏈接,與常規的URL得到數據的方式一致,詳情略。(與Rest同樣)

相關文章
相關標籤/搜索