使用Apache的CXF框架开发WebService实例

来源:互联网 发布:淘宝金牌卖家怎么开通 编辑:程序博客网 时间:2024/05/16 14:23

6.基于 Apache CXF的Web Service开发

Web Service 支持不同语言开发,而不关心服务端或者客户端采用何种语言。这里讲解利用cxf进行Web Service开发。

这里先讲解Java中的web服务规范:

Java中共有三种WebService规范,分别是JAXM&SAAJ、JAX-WS(JAX-RPC)、JAX-RS。

JAX-WS(Java API ForXML-WebService),JDK1.6自带的版本为JAX-WS2.1,其底层支持为JAXB。早期的基于SOAP的JAVA的Web服务规范JAX-RPC(Java API For XML-RemoteProcedure Call)目前已经被JAX-WS规范取代,JAX-WS是JAX-RPC的演进版本,但JAX-WS并不完全向后兼容JAX-RPC,二者最大的区别就是RPC/encoded样式的WSDL,JAX-WS已经不提供这种支持。

这里的JAX-WS规范我们采用Apache CXF作为实现。

简单的说下注意事项:当你使用的是JDK1.5的时候,就必须要有jaxws-api-2.0.jar这个包的支持,如果使用的是JDK1.6就不用使用这个包了。因为1.6里已经有相关实现。

6.1 简单的CXF应用

6.1.1 服务端开发

1)首先下载CXF开发要用到的相关包,目前最新版本是apache-cxf-2.3.1。下载地址:

http://www.apache.org/dyn/closer.cgi?path=%2Fcxf%2F2.3.1%2Fapache-cxf-2.3.1.zip

下载解压后在apache-cxf-2.3.1目录下lib目录下,有所有要用到的jar包。

2)建一个web项目,将apache-cxf-2.1.3\lib目录下所有包添加到项目中。

注:这些包里面有jetty,cxf,spring的相关包,可以根据需要进行添加,如果不和spring进行整合,则不需要spring相关包。Jetty是一个类似于tomcat的web服务器,内置在cxf中,用于发布web服务。如果用jetty发布服务,则需要添加它的包,如果用tomcat则不需要。

3)写好一个接口和实现类(具体见demo中IHelloService和HelloServiceImpl),本demo中数据绑定方式采用cxf默认的jaxb方式,也可以采用aegis,其优点是不用jaxb中的注解方式。(基于SOAP的Web服务可用单个Java类的实现,但是最好是用“接口+实现”的方式来实现最佳。Web服务的接口称为SEI,即ServiceEndpoint Interface;而Web服务的实现称为SIB,即Service Implementation Bean。 SIB可以是一个POJO,也可以是无状态的会话EJB。)

4)发布服务

一种是通过CXF内置的Jetty应用服务器发布(见方法一,二),一种是通过tomcat发布(见方法三)。

Ø  方法一:使用SunJAX-WS 2中Endpoint.publish进行发布。(不需要其他配置与包

new HelloServiceImpl());//这里是实现类Endpoint endpoint =

Endpoint.publish("http://localhost:8080/WSCXF/helloService",

 

System.out.println("WS发布成功!");

Ø  方法二:用CXF的JaxWsServerFactoryBean类进行发布。(需要CXF相关包)

HelloServiceImpl impl = new HelloServiceImpl();

JaxWsServerFactoryBean factoryBean = newJaxWsServerFactoryBean();

factoryBean.setAddress("http://localhost:8080/WSCXF/helloService");

factoryBean.setServiceClass(IHelloService.class);//接口类

factoryBean.setServiceBean(impl);

factoryBean.create();

System.out.println("WS发布成功!");

方法一或者方法二都是发布到Jetty下。在main方法中运行方法一或者方法二代码,web服务就发布成功了。

Ø  方法三:利用cxf和spring整合在tomcat下进行发布。具体方法在后面的spring整合cxf时讲到。

Ø  方法四:重写loadBus方法。

书写一个类覆盖org.apache.cxf.transport.servlet.CXFNonSpringServlet的loadBus方法指定BUS以及发布你的web服务。

具体可查阅资料:

http://blog.csdn.net/zq9017197/archive/2010/12/26/6099684.aspx

查看web服务是否发布成功:

访问http://localhost:8080/WSCXF/helloService?wsdl 查看wsdl文件

6.1.2 客户端调用

客户端调用只需要服务端提供一个webservice的发布地址即可。不关心服务端发布方式等。

1)客户端代码生成

Ø  方法一:使用MyEclipse工具生成。

new-other-myeclipse-web service-web service client根据设置向导可以生成客户端,但最好使用CXF的wsdl2java来完成,因为CXF2.2+版本开始支持JAX-WS2.1规范,而MyEclipse自带的是xfire的一个插件,生成的客户端代码可能不是最新规范的。

Ø  方法二:通过wsdl2java的命令生成客户端代码。

先进入dos窗口,进入apache-cxf-2.3.1\bin所在目录,输入命令:

wsdl2java -pcom.jaxb.client -d e:/http://127.0.0.1:8080/WSCXF/helloService?wsdl

命令格式为:wsdl2java –p 包名 –d 生成代码存放目录wsdl的url

其中的wsdl的url为要调用的webservice的服务地址

附加:wsdl2java用法:

wsdl2java -p com -d src -all  aa.wsdl

-p 指定其wsdl的命名空间,也就是要生成代码的包名;

-d 指定要产生代码所在目录;

-client 生成客户端测试web service的代码;

-server 生成服务器启动web service的代码;

-impl 生成web service的实现代码;

-ant  生成build.xml文件;

-all 生成所有开始端点代码:types,serviceproxy, service interface, server mainline, client mainline, implementation object,and an Ant build.xml file。

详细用法见:http://cwiki.apache.org/CXF20DOC/wsdl-to-java.html

2)新建一个web客户端项目,将生成的客户端代码拷贝到src下。

3)调用web服务

Ø  方法一:使用标准的JAX-WS的API完成客户端调用(不需要导入任何CXF包)

//注意。此处http://service.jaxb.com/来源于wsdl文件中targetNamespace

QName qName =

 newQName("http://service.jaxb.com/","HelloServiceImplService");

HelloServiceImplService helloServiceImplService =

new HelloServiceImplService(

new URL("http://localhost:8080/WSCXF/helloService?wsdl"),qName);

IHelloService helloService

=(IHelloService)helloServiceImplService.getPort(IHelloService.class);

Ø  方法二:使用CXF中JaxWsProxyFactoryBean客户端代理工厂调用web服务(需要导入CXF相关包)

JaxWsProxyFactoryBean soapFactoryBean = newJaxWsProxyFactoryBean();

soapFactoryBean.setAddress("http://localhost:8080/WSCXF/helloService");

soapFactoryBean.setServiceClass(IHelloService.class);

Object o = soapFactoryBean.create();

IHelloService helloService = (IHelloService)o;

Ø  方法三:

String endPointAddress = "http:// localhost:8080/WSCXF/helloService";

Service service = Service.create(

newQName("http://service.jaxb.com/","IHelloService"));

service.addPort(

new QName("http://service.jaxb.com/","IHelloServicePort");,

SOAPBinding.SOAP11HTTP_BINDING, endPointAddress);

IHelloService helloService =service.getPort(IHelloService.class);

Ø  方法四:(需要导入CXF相关包)

JaxWsDynamicClientFactory dcf =JaxWsDynamicClientFactory.newInstance();

org.apache.cxf.endpoint.Client client =

dcf.createClient("http://127.0.0.1:8080/WSCXF/helloService?wsdl");

//sayHello 为接口中定义的方法名称   张三为传递的参数   返回一个Object数组

Object[] objects=client.invoke("sayHello","张三");

//输出调用结果

System.out.println(objects[0].toString());

7.CXF和Spring整合

CXF可以很好的与Spring整合,然后发布在tomcat下,只需要简单的Spring配置即可。

7.1 服务端开发

1)新建web项目,并添加相关包。(包括spring和cxf相关包)

2)写好一个接口和实现类。(见demo)

3)新建beans.xml文件。

<?xml version="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jaxws="http://cxf.apache.org/jaxws"

xsi:schemaLocation="

http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd

http://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd">

 

<!--spring发布web服务配置 -->

<importresource="classpath:META-INF/cxf/cxf.xml" />

<importresource="classpath:META-INF/cxf/cxf-extension-soap.xml" />

<importresource="classpath:META-INF/cxf/cxf-servlet.xml" />

 

<bean id="helloService"class="com.jaxb.service.HelloServiceImpl" />

<!--

<bean id="helloService"class="com.aegis.service.HelloServiceImpl" />

-->

<!--endpoint 方式发布web服务和 server方式一样 -->

<!--

<jaxws:endpointid="helloServiceWs" address="/helloService"

     implementor="#helloService"/>

-->

<!--

     另一种写法,建议不要用这种方法 ,如果实现类有的属性要通过spring依赖注入的话,

     这种方法只是简单的new个实现类,他的属性没有通过spring依赖注入给注入值

-->

<!--

<jaxws:endpointid="helloServiceWs" address="/helloService"

     implementor="com.jaxb.service.HelloServiceImpl"/>

-->

 

<!—下面这个是wss4j的配置,后面会讲到相关知识,需要配置在spring配置文件中 -->

<!--wss4j 服务端配置 -->

<bean id="wss4jInInterceptor"

class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">

     <constructor-arg>

         <map>

             <entrykey="action" value="UsernameToken" />

             <!--<entry key="passwordType" value="PasswordText" />-->

             <!--密码使用MD5密文发送 -->

             <entrykey="passwordDigest" value="PasswordText" />

             <entrykey="passwordCallbackClass"

                 value="com.security.service.ServerPasswordCallbackHandler"/>

         </map>

     </constructor-arg>

</bean>

 

<jaxws:serverid="helloServiceWs" address="/helloService">

     <jaxws:serviceBean>

         <refbean="helloService" />

     </jaxws:serviceBean><!--使用这种方法发布web服务 -->

     <jaxws:inInterceptors>

         <refbean="wss4jInInterceptor" />

     </jaxws:inInterceptors><!—wss4j配置 -->

     <!--<jaxws:serviceFactory>

         <refbean="jaxWsServiceFactoryBean" />

     </jaxws:serviceFactory>  --><!—数据绑定方式配置 -->

</jaxws:server>

<!-- 通过Spring创建数据绑定的类-->

   <!--<bean id="aegisBean"class="org.apache.cxf.aegis.databinding.AegisDatabinding" />

   <bean id="jaxWsServiceFactoryBean"

class="org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean">

       <property name="wrapped" value="true" />

       <property name="dataBinding" ref="aegisBean"/>

   </bean> -->

</beans>

4)配置web.xml

<context-param>

     <param-name>contextConfigLocation</param-name>

     <param-value>/WEB-INF/beans.xml</param-value>

</context-param>

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

 

<!—在tomcat中发布需要配置servlet -->

<servlet>

     <servlet-name>CXFServlet</servlet-name>

     <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>

     <load-on-startup>2</load-on-startup>

</servlet>

<servlet-mapping>

     <servlet-name>CXFServlet</servlet-name>

     <url-pattern>/ws/*</url-pattern>

</servlet-mapping>

5)发布web项目

因为在web.xml里面配置了servlet,则可以将项目发布到tomcat下,启动tomcat即可。

6)访问wsdl

http://localhost:8080/WSCXF/ws/helloService?wsdl

7.2 客户端调用

1)新建一个web客户端项目,用wsdl2java生成客户端代码。将生成的客户端代码拷贝到src下。添加spring相关包。

2)配置spring配置文件。

beans.xml存放在src目录下,具体配置如下:

<?xml version="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jaxws="http://cxf.apache.org/jaxws"

xsi:schemaLocation="

http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd

http://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd">

 

<!-- wss4j 配置在客户端,后面有讲到相关知识 -->

<!--wss4j 客户端配置 -->

<beanid="wss4jOutInterceptor"

class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">

     <constructor-arg>

         <map>

             <entrykey="action" value="UsernameToken" />

             <entrykey="user" value="Fetion" />

             <!--<entry key="passwordType" value="PasswordText" />-->

             <!--密码使用MD5密文发送 -->

             <entrykey="passwordDigest" value="PasswordText" />

             <entrykey="passwordCallbackClass"

                 value="com.security.client.ClientPasswordCallbackHandler"/>

         </map>

     </constructor-arg>

</bean>

<jaxws:client id="helloServeiceClient"

     address="http://localhost:8080/WSCXF/ws/helloService"serviceClass="com.jaxb.client.IHelloService">

     <jaxws:outInterceptors>

         <refbean="wss4jOutInterceptor" />

        </jaxws:outInterceptors><!--wss4j客户端配置-->

</jaxws:client>

</beans>

 

2)调用web服务

在main方法中运行:

ClassPathXmlApplicationContext app = newClassPathXmlApplicationContext("beans.xml");//此处beans.xml放在src下,也需要放在其他目录下,但需要注明清楚

//获取webservice服务的操作接口

IHelloServicehelloService = (IHelloService)app.getBean("helloServeiceClient");

8.基于soap的web服务的WS-Security安全规范

8.1 传统的用户名令牌机制

Apache WSS4J(WebServiceSecurity For Java)实现了JAVA语言的WS-Security。WSS4J依赖于SAAJ。CXF中使用拦截器机制完成WSS4J功能的支持。只需要初始化WSS4JInInterceptor(对应还有一个WSS4JOutInterceptor)实例并添加相关信息即可。CXF2.1.x之后的版本可以完成SAAJInInterceptor(它也有对应的一个SAAJOutInterceptor)拦截器的自动注册,否则需要再注册一下SAAJ拦截器。

1)服务器端实现

在spring配置文件中配置:

<bean id="wss4jInInterceptor"

class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">

     <constructor-arg>

         <map>

             <entrykey="action" value="UsernameToken" />

             <!--<entry key="passwordType" value="PasswordText" />-->

             <!--密码使用MD5密文发送-->

             <entrykey="passwordDigest" value="PasswordText" />

             <entrykey="passwordCallbackClass"

                 value="com.security.service.ServerPasswordCallbackHandler"/>

         </map>

     </constructor-arg>

</bean>

<jaxws:server id="helloServiceWs" address="/helloService">

     <jaxws:serviceBean>

         <refbean="helloService" />

     </jaxws:serviceBean><!--使用这种方法发布web服务 -->

     <jaxws:inInterceptors>

         <refbean="wss4jInInterceptor" />

     </jaxws:inInterceptors>

     <!--<jaxws:serviceFactory>

         <refbean="jaxWsServiceFactoryBean" />

     </jaxws:serviceFactory>  -->

</jaxws:server>

这里我们使用构造方法参数初始化了一个输入的WSS4J拦截器。到底有哪些参数的键值对,可以再org.apache.ws.security.handler.WSHandlerConstants和

org.apache.ws.security.WSConstants中的产量列表中查找。例如:上面的第一组键值对action和UsernameToken都是WSHandlerConstants中的常量,表示验证机制是用户姓名令牌,也就是使用传统的用户名和密码机制。第二组的键值对分别是WSHandlerConstants和WSConstants中的常量,表示密码类型是文本,还可以是WSConstants.PASSWORD_DIGEST(密码会被加密为MD5)。第三组键值对的键表示服务器端验证密码的回调处理类,这个类必须实现JAVA安全认证框架中的

javax.security.auth.callback.CallbackHandler类,你也可以用passwordCallbackRef指向一个Bean的名字。

下面试服务器端具体密码回调处理类,它负责接收并处理客户端提交的用户名和密码,这个方法没有返回值,显示,如果验证失败,需要抛出异常进行表示。

package com.security.service;

 

import java.io.IOException;

import javax.security.auth.callback.Callback;

importjavax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSConstants;

import org.apache.ws.security.WSPasswordCallback;

importorg.apache.ws.security.WSSecurityException;

 

public class ServerPasswordCallbackHandlerimplements CallbackHandler {

 

public finalstatic String USER = "liu";

public finalstatic String PASSWORD = "lius";//设置用户名密码

public voidhandle(Callback[] callbacks) throws IOException,

         UnsupportedCallbackException{

     WSPasswordCallbackwspassCallback = (WSPasswordCallback) callbacks[0];

     System.out.println(wspassCallback.getIdentifier()+ "\t"

             +wspassCallback.getPassword());

    

     if(WSConstants.PASSWORD_TEXT.equals(wspassCallback.getPasswordType())){

         if(wspassCallback.getIdentifier().equals(USER)

                 &&wspassCallback.getPassword().equals(PASSWORD)) {

             System.out.println(".................验证通过!");  

             System.out.println(".................identifier= " + USER);  

             System.out.println(".................password= " + PASSWORD);

         }else {

             throw newWSSecurityException("............未通过验证!");

         }

     } else{

         //密码使用MD5密文发送

         System.out.println(wspassCallback.getIdentifier());

         wspassCallback.setPassword(PASSWORD);

     }

}

}

2)客户端实现

在spring配置文件中配置:(所以要使用wss4j,则需要和spring结合)

<bean id="wss4jOutInterceptor"

class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">

     <constructor-arg>

         <map>

             <entrykey="action" value="UsernameToken" />

             <entrykey="user" value="Fetion" />

             <!--<entry key="passwordType" value="PasswordText" /> -->

             <!--密码使用MD5密文发送-->

             <entrykey="passwordDigest" value="PasswordText" />

             <entrykey="passwordCallbackClass"

                 value="com.security.client.ClientPasswordCallbackHandler"/>

         </map>

     </constructor-arg>

</bean>

<jaxws:clientid="helloServeiceClient"

     address="http://127.0.0.1:8080/WSCXF/helloService"serviceClass="com.jaxb.client.IHelloService">

     <jaxws:outInterceptors>

         <refbean="wss4jOutInterceptor" />

     </jaxws:outInterceptors>

</jaxws:client>

 

可以看出,这里使用了WSS4J的输出拦截器,因为要将用户名和密码输出到服务端进行验证处理。另外与服务端不同的是多了一个user参数初始化WSS4J的输出拦截器,用于初始化用户名,这是一个必选项,稍后将在下面的客户端密码回调处理类进行重新设定值。

 

package com.security.client;

import java.io.IOException;

import javax.security.auth.callback.Callback;

importjavax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

 

public class ClientPasswordCallbackHandlerimplements CallbackHandler{

public finalstatic String USER = "liu";

public finalstatic String PASSWORD = "lius";//设置用户名密码

public voidhandle(Callback[] callbacks) throws IOException,

         UnsupportedCallbackException{

     WSPasswordCallbackwspassCallback = (WSPasswordCallback)callbacks[0];

     wspassCallback.setIdentifier(USER);

     wspassCallback.setPassword(PASSWORD);

}

}

然后我们访问这个web服务,从控制台查看日志,可以看到在SOAP信封的Header中包装了<wsse:Security…等元素,元素包括了WS-Seurity的一些信息和设置的用户名和密码。如果使用MD5密文发送,则密码显示为null,只能显示用户名。

如果客户端和服务端的用户名或者密码不对应,则登录不上,会显示“............未通过验证!“。

8.2 数字签证方式

除了UsernameToken这种传统的安全机制,常用的验证动作还有一个就是使用数字签证技术,action的取值为Signature这需要你了解JAAS中相关内容,由于商业证书都是收费的,可以采用JDK自带的keytool命令自签名。具体实现请参考其他资料文档。

9.CXF数据绑定

CXF有多种数据绑定方式,如:AegisDatabinding,JAXB,MTOMAttachments。其中JAXB(JavaTMArchitecture for XML Binding)是其默认的数据绑定方式。

9.1 JAXB方式

JAXB是一套自动映射XML和Java实例的开发接口和工具。

如果web Service发布的接口为:

StringsayUserHello(User user);

List<User>findUsers();

且传入参数类型是类,而且返回的为List ,String 等,这样,发布webservice与普通java的没有区别,是因为JAXB都能支持。

但JAXB不能将一些 Java 类型自然映射到 XML 表示形式,例如,HashMap 或其他非 JavaBean 类。如参数类型为接口,以及Map,这样就要定义一个适配器使java类型适应自定义编组。一般有两步:

1)编写一个类继承XmlAdapter,以实现此抽象类的适配器。

2)安装使用注释XmlJavaTypeAdapter 的适配器。

具体实现见demo。

类XmlAdapter的说明:

类XmlAdapter<ValueType,BoundType>

BoundType - JAXB 不知道如何处理的一些类型。编写一个适配器,以便允许通过 ValueType 将此类型用作内存表示形式。

ValueType - JAXB 无需其他操作便知道如何处理的类型。

其两个抽象方法:

marshal(...):编组过程中,JAXB 绑定框架调用 XmlAdapter.marshal(..) 将 bound类型修改为 value 类型,然后将 value 类型编组为 XML 表示形式。

unmarshal(...):解组过程中,JAXB 绑定框架首先将 XML 表示形式解组为 value 类型,然后调用 XmlAdapter.unmarshal(..) 将 value 类型修改为 bound 类型。

常用的几个注释说明:

a. @XmlJavaTypeAdapter注释可以与下列编程元素一起使用: JavaBean 属性 、字段、参数 、包 、XmlJavaTypeAdapters 内部的元素。用来表示使用实现 XmlAdapter 的适配器,告诉其如何如何转换。

b. @XmlType 注释可以与以下程序元素一起使用:顶层类、枚举类型 。表示将类或枚举类型映射到 XML 模式类型。

c. @XmlAccessorType注释可以与以下程序元素一起使用:包、顶层类。表示控制默认情况下是否对字段或 Javabean 属性进行序列化。

@XmlAccessorType(XmlAccessType.FIELD)

FIELD :JAXB 绑定类中的每个非静态、非瞬态字段将会自动绑定到 XML,除非由 XmlTransient 注释。

d. @XmlElement 注释可以与以下程序元素一起使用: JavaBean 属性、非 static、非 transient 字段 、XmlElements 中的程序元素 。表示将 JavaBean 属性映射到派生于属性名称的 XML 元素。

9.2 Aegis Databinding方式

Aegis是一个快速,基于STAX(Streaming API for XML)的数据绑定,它能使采用代码优先方式发布webservice的情况更简单。Aegis 支持接口,集合类(包括Map)和各种数据类型。它不需要注释,也不需要写适配器(xmlAdapter)。