读书人

cxf开发实践(配备服务器与客户端)

发布时间: 2012-12-25 16:18:28 作者: rapoo

cxf开发实践(配置服务器与客户端)
1 安装

CXF与其它Java类库一样,安装方法是将它的包文件复制到目标系统的${CLASSPATH}文件夹中。因为CXF依赖于其它许多Java类库,比如Spring、WSDL4J等,所以需要安装的类库很多,都可以在CXF的二进制安装包的lib文件夹中找到。如果知道系统的规模,可以对依赖的类库进行删减。

在典型的配置下,要将CXF集成到已有的Web应用程序时,只需要把包文件复制到WEB-INF/lib下。
2 开发服务

我们知道,利用Java开发一个业务系统或者Web应用系统,通常使用利用MVC模式将系统分解为四个层次:表示层、控制层、业务逻辑层和数据访问与存储层。表示层与控制层通常对应Struts或者Swing等一些MVC框架,而业务逻辑层和数据访问与存储层对应EJB或者Spring+Hibernate等框架。Web Service属于控制层,它向外部系统暴露业务逻辑层的访问。在向外部系统提供Web
Service前应该先设计好业务逻辑层。

以下示例是一个典型的过程化的业务逻辑层。

UserService.java:

package demo.cxf;



public interface UserService {

public
void changeUserState(User user, int state);

public
boolean checkUserExists(String username);

public
void createUser(User user);

public
void cusumePremiumPoint(String username, int points);

public
void deleteUser(String username) throws NotFoundException;

public
void increasePremiumPoint(String username, int points);

public
void updateUserInformation(User user);

}



UserServiceImpl.java:

package demo.cxf;



public class UserServiceImpl
implements UserService {

public
void changeUserState(User user, int state) {

print("changeUserState");

}

public
boolean checkUserExists(String username) {

print("checkUserExists");

return
false;

}

public
void createUser(User user) {

print("createUser");

print(user.getUsername());

}

public
void cusumePremiumPoint(String username, int points) {

print("consumePremiumPoint");

}

public
void deleteUser(String username) throws NotFoundException {

throw
new NotFoundException("我故意没找到");

}

public
void increasePremiumPoint(String username, int points) {

print("increasePremiumPoint");

}

public
void updateUserInformation(User user) {

print("updateUserInformation");

}

private
void print(Object o) {

System.out.println(o);

}

}



User.java:

package demo.cxf;



import java.util.Date;



public class User {

private
Date birthday;

private
int height;

private
String password;

private
String sex;

private
String sign;

private
String username;

public
Date getBirthday() {

return
birthday;

}

public
void setBirthday(Date birthday) {

this.birthday
= birthday;

}

public
int getHeight() {

return
height;

}

public
void setHeight(int height) {

this.height
= height;

}

public
String getPassword() {

return
password;

}

public
void setPassword(String password) {

this.password
= password;

}

public
String getSex() {

return
sex;

}

public
void setSex(String sex) {

this.sex
= sex;

}

public
String getSign() {

return
sign;

}

public
void setSign(String sign) {

this.sign
= sign;

}

public
String getUsername() {

return
username;

}

public
void setUsername(String username) {

this.username
= username;

}

}

NotFoundException.java

package demo.cxf;



public class NotFoundException
extends Exception {

public
NotFoundException() {

super();

}

public
NotFoundException(String message, Throwable cause) {

super(message,
cause);

}

public
NotFoundException(String message) {

super(message);

}

public
NotFoundException(Throwable cause) {

super(cause);

}

}

几个小的注意事项:

l
接口最好不包含内嵌类或者枚举类型,CXF不能识别它们

l
接口抛出的异常也将被CXF识别并导出
3添加JAX-WS标注到Java代码中

在设计好逻辑层之后,就可以动手为逻辑层添加JAX-WS标注,以便使用CXF的代码生成工具将Java代码导出为WSDL定义。

TODO 介绍JAX-WS

JAX-WS定义了很多的标注,可以对导出的服务接口进行定义。其中较常用的是

l
@WebService,必选的标注。用于导出的服务接口及其实现类

name


定义导出的服务接口的名字,对应于WSDL文档中wsdl:portType。默认是服务接口的Java类名加PortType

targetNamespace


定义导出的服务接口的名域(namespace),默认是倒置的服务接口Java包名。如demo.cxf.UserService的名域将会是http://cxf.demo/

serviceName


定义服务名,与名域一起唯一标识一个服务。默认是其Java类名

wsdlLocation


其WSDL文档URL。可由服务器容器自动产生

endpointInterface


指定服务接口的Java类。通常用于服务实现类的标注。应当指定类的全名,如demo.cxf.UserService

portName


对应WSDL文档中的wsdl:port元素。默认是Java类名加Port



l
@WebMethod,可选的标注,用于服务接口的方法

operationName


指定方法在WSDL文档中的名字,客房端用此名字调用方法

action


TODO 翻译此说明

Specifies the value of the soapAction attribute of the soap:operation
element generated for the method. The default value is an empty string.

exclude


生成WSDL文档时将该方法排除在外



l
@SOAPBinding,可选的标注,用于指定生成的SOAP定义文档风格。关于此标注再详细的信息请查阅SOAP标准等参考资料

style


Style.DOCUMENT (默认)

Style.RPC


SOAP消息风格

use


Use.LITERAL (默认)

Use.ENCODED


SOAP数据编码方式

parameterStyle


ParameterStyle.BARE

ParameterStyle.WRAPPED (默认)


TODO 翻译此说明

Specifies how the method parameters, which correspond to message
parts in a WSDL contract, are placed into the SOAP message body. A parameter
style of BARE means that each parameter is placed into the message body as a
child element of the message root. A parameter style of WRAPPED means that
all of the input parameters are wrapped into a single element on a request
message and that all of the output parameters are wrapped into a single
element in the response message. If you set the style to RPC you must use the
WRAPPED parameter style.

l
@RequestWrapper,可选的标注,用于指定如何包装客户端调用服务方法使用的参数

l
@ResponseWrapper,可选的标注,用于指定如何包装客户端调用服务方法的返回值

l
@WebFault,可选的标注,用于注解服务接口方法抛出的异常

name


异常的名字

targetNamespace


对应的名域,默认是服务接口的名域

faultName


实现该异类的类名

l
@WebParam,可选的标注,用于指定方法参数的使用方式

name


在WSDL文档中的名字,默认是arg0,arg1…

targetNamespace


对应的名域。默认是服务接口的名域

mode


Mode.IN (默认)、Mode.OUT、Mode.INOUT

对于Java程序没有意义

header


true或者false(默认),指定该参数是否在SOAP消息头部发送

partName


TODO 翻译此说明

Specifies the value of the name attribute of the wsdl:part element
for the parameter when the binding is document.



l
@WebResult,可选的标注,用于指定返回值的使用方式

name


返回值在WSDL文件中的名字。默认是return

targetNamespace


对应的名域。默认是服务接口的名域

header


true或者false(默认),指定该参数是否在SOAP消息头部发送

partName


TODO 翻译此说明

Specifies the value of the name attribute of the wsdl:part element
for the parameter when the binding is document.



一个使用了所有标注的示例:

package org.apache.cxf;



import javax.jws.*;

import javax.xml.ws.*;

import javax.jws.soap.*;

import
javax.jws.soap.SOAPBinding.*;

import javax.jws.WebParam.*;



@WebService(name="quoteReporter")

@SOAPBinding(style=Style.RPC,
use=Use.LITERAL)

public interface quoteReporter

{

@WebMethod(operationName="getStockQuote")

@RequestWrapper(targetNamespace="http://demo.iona.com/types",className="java.lang.String")

@ResponseWrapper(targetNamespace="http://demo.iona.com/types",
className="org.eric.demo.Quote")

@WebResult(targetNamespace="http://demo.iona.com/types",name="updatedQuote")

public Quote getQuote(

@WebParam(targetNamespace="http://demo.iona.com/types",name="stockTicker",mode=Mode.IN)

String ticker

);

}


4.使用java2wsdl工具生成WSDL文件

一旦为服务接口与服务实现类添加好适当的标注后,有两种方法可以得到对应的WSDL文件。一是使用CXF提供的java2wsdl命令行。二是把服务发布到Web容器,由Web容器自动生成WSDL文件。后者比较简单。比如本文的例子发布到http://server/cxf/UserService,那么可以使用http://server/cxf/UserService?wsdl这个URL得到对应的WSDL文档。下面是一个使用java2wsdl工具的例子:

WEB-INF\classes>
path D:\ develop\resource\apache-cxf-2.0.1-incubator\bin;%PATH%
WEB-INF\classes>
set CLASSPATH=.: D:\ develop\resource\apache-cxf-2.0.1-incubator\lib
WEB-INF\classes>
java2wsdl demo.cxf.UserService
WEB-INF\classes>
copy UserServiceSerivce.wsdl d:\ws

运行完毕,可以看到生成名为UserServiceService.wsdl的WSDL文件。
5使用wsdl2java工具生成调用桩代码

用命令行java2wsdl生成的WSDL文件并不一定是正确的,因为它可能没有包含正确的WebService地址,但是没关系,我们先生成调用桩代码。

D:\ws>
path D:\ develop\resource\apache-cxf-2.0.1-incubator\bin;%PATH%
D:\ws>
set CLASSPATH=.: D:\ develop\resource\apache-cxf-2.0.1-incubator\lib
D:\ws>
wsdl2java ?client ?compile UserServiceService.wsdl

此时ws将有一个文件夹demo包含调用桩代码及编译后的class文件

wsdl2java还有其它命令行选项:

-server


生成服务接口

-client


生成调用桩代码

-impl


生成服务实现代码

-compile


生成代码后编译

-ant


生成ant自动构建脚本

-quiet


静默模式,不输出警告与错误信息


6 配置服务运行环境

Web Service一般使用SOAP协议将服务暴露为Web服务器[1]。CXF对此提供了广泛的支持,可以使用TOMCAT、这样的Web服务器,也可以使用CXF集成的Jetty Web服务器。两者的配置过程是类似的。因为Web应用系统比较常见,所以这里介绍前一种方式。

将代码编译成功并发布到Web容器上,然后定义一个Spring的配置文件,假如名字为beans.xml,内容是:

<beans
xmlns="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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd

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



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

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

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



<jaxws:endpoint
id="UserService" implementor="demo.cxf.UserServiceImpl"
address="/UserService"></jaxws:endpoint>

</beans>

这个配置文件首先定导入CXF预定义的配置文件,然后利用<jaxws:endpoint/>定义一个Web Service。它有如下属性:

implementor


服务实现类

address


Web Service地址,实际地址还要加上Web应用程序的地址。如本例中Web应用程序配置在/cxf下,那么UserService的地址是http://server:8080/cxf/UserService

接着在Web应用程序的web.xml配置文件中加入下列配置:

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

<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"

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

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<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>

<servlet>

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

<servlet-class>

org.apache.cxf.transport.servlet.CXFServlet

</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

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

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

</servlet-mapping>

</web-app>

可以看到,CXF使用了Spring来发布服务,因此需要在WEB-INF/lib中加入Spring的类库。重启Web应用程序后可以用 http://localhost:8080/<应用程序名>/UserService?wsdl 得到服务接口的WSDL文档。之前用工具生成的WSDL文档可能包含不正确的服务地址,现在把它更新一下。在WSDL文档中查找<soap:address/>标签。更改为:

<soap:address
location="http://server:8080/cxf/UserService"/>
7 编写Web Service客户端

至此,我们已经可以写一个UserService的客户端了。代码如下:

UserServiceTest.java:

import
java.net.MalformedURLException;

import java.net.URL;

import javax.xml.namespace.QName;

import
demo.cxf.NotFoundException_Exception;

import demo.cxf.User;

import demo.cxf.UserService;

import
demo.cxf.UserServiceService;



public class UserServiceTest {

public
static void main(String[] args) throws MalformedURLException {

UserServiceService
uss=new UserServiceService(new
URL("file:d:\\ws\\UserServiceService.wsdl"),

new QName("http://cxf.demo/","UserServiceService"));

UserService
myus=uss.getUserServicePort();

User
u=new User();

u.setUsername("fish");

myus.createUser(u);

}

}

客户端程序首先根据WSDL文档(file:d\ws\UserServiceService.wsdl)和一个WSDL服务全名(http://cxf.demo/UserServiceService)生成代理,然后取得UserService的代理。利用这个代理,我们就可以像本地代码一样调用服务器的业务逻辑层了。这个代码没有什么稀奇之处,通常可以完全满足使用。如果需要深入桩代码内部,CXF提供了很多辅助类。

读书人网 >编程

热点推荐