cxf开发实践(服务器)


类型:Java,创建时间:Dec. 30, 2011, 12:39 p.m.

标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/2/。

  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属于控制层,它向外部系统暴露业务逻辑层的访问。在向外部系统提供WebService前应该先设计好业务逻辑层。

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

    //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);
        }
    }
    

    几个小的注意事项:

    • 接口最好不包含内嵌类或者枚举类型,CXF不能识别它们
    • 接口抛出的异常也将被CXF识别并导出
  3. 添加JAX-WS标注到Java代码中

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

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

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

    • @WebMethod,可选的标注,用于服务接口的方法。它有这些参数:

      参数名称参数说明
      operationName 指定方法在WSDL文档中的名字,客房端用此名字调用方法
      action 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文档时将该方法排除在外

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

      参数名称 取值范围 参数说明
      style Style.DOCUMENT (默认), Style.RPC SOAP消息风格
      use Use.LITERAL (默认), Use.ENCODED SOAP数据编码方式
      parameterStyle ParameterStyle.BARE, ParameterStyle.WRAPPED (默认) 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.

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

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

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

      参数名称参数说明
      name 异常的名字
      targetNamespace 对应的名域。默认是服务接口的名域
      faultName 实现该异类的类名

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

      参数名称参数说明
      name 在WSDL文档中的名字,默认是arg0,arg1…
      targetNamespace 对应的名域。默认是服务接口的名域
      mode 取值范围是Mode.IN (默认)、Mode.OUT、Mode.INOUT, 对于Java程序没有意义
      header true或者false(默认),指定该参数是否在SOAP消息头部发送
      partName Specifies the value of the name attribute of the wsdl:part element for the parameter when the binding is document.

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

      参数名称参数说明
      name 返回值在WSDL文件中的名字。默认是return
      targetNamespace 对应的名域。默认是服务接口的名域
      header true或者false(默认),指定该参数是否在SOAP消息头部发送
      partName 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
        );
    }
    

    实际上并没有那么复杂。因为默认值已经可以应付日常使用了。对于我们的例子,应该在服务接口与服务实现类上添加@WebService标注,如下:

    //UserService.java:
    package demo.cxf;
    
    import javax.jws.*;
    import javax.xml.ws.*;
    import javax.jws.soap.*;
    import javax.jws.soap.SOAPBinding.*;
    import javax.jws.WebParam.*;
    
    @WebService //注意这里
    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;
    
    @WebService(endpointInterface = "demo.cxf.UserService",
            serviceName = "/UserService") //注意这里
    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);
        }
    }
    
  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工具生成调用桩代码

    所谓调用桩代码是一个远程过程调用的概念。是指一些自动生成的代码,可以上客户端软件像平时那样调用远程的服务。所有的通信、数据转换功能都能在桩代码内自动完成。接下来只要生成调用桩代码,我们的WebService客户端就可以很简单地调用服务器上的WebService服务了。

    用命令行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服务器。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" />
    </beans>
    

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

    implementor服务实现类
    addressWeb 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 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(
                    newURL("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提供了很多辅助类。

标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/2/。


暂时还没有任何评论。


何不来发表一下您对本文的看法(使用Markdown语法,分段空两行):