本Spike记录中内容,如无特别指出,均引用[1]。

0 引言

0.1 基本的Web服务术语

XML

业界的结构化交换信息表示的事实上的标准。

XML namespace是在XML文档中提供唯一的命名元素和属性的标准。

XML schema提供了定义XML文档结构、内容和语义的方法。

SOAP(Simple Object Access Protocol)

SOAP是在网络环境中交换XML消息的协议,通常使用HTTP协议。

SOAP消息格式是SOAP信封。SOAP信封包含了所有请求消息,由可选头部和体构成。

SOAP信封头部可选的包含上下文相关信息,如安全或交易信息;体包含实际的负载或应用数据。

WSDL(Web Service Description Language)

WSDL是描述Web服务的标准的XML语言。

在WSDL中,Web服务被描述为一组通信终端(endpoint),通信终端能够交换消息,又称为端口(port)。

终端由两部分构成:

(1) 服务和用于调用服务的消息提供的操作的抽象定义,抽象操作的定义被称为端口类型;

(2) 将操作的抽象定义与具体的网络协议、服务位置和服务消息格式的具体绑定

REST(REpresentational State Transfer)

表征状态转移(REST)不是技术或标准,是由Roy T. Fielding提出的一种架构风格,是通过Web暴露资源的一系列指南和建议。REST大致的设计考虑包括三部分:资源的URI(where),方法(主要是HTTP方法,how)和资源信息表示的格式(hypermedia, what)。

服务注册(Service Registry)

服务注册提供了Web服务查找的一种机制。UDDI规范定义了发布和查找Web服务的标准,但缺少包括服务版本控制、服务分类和编排、服务生命周期管理等能力。这部分内容需要在阅读Web服务解决方案的资料后做进一步探讨。

0.2 W3C Web of Services标准

W3C的Web of Services标准组[2]中介绍将服务Web描述为Web和企业软件中基于消息的设计,基于HTTP、XML、SAOP、WSDL、SPARQL等技术。服务Web有4个主题:(这里仅摘录了其摘要部分,从内容看似乎不大靠谱,最起码SPARQL这个中小微企业用的还不是很多,其具体内容待有所涉及再做深入探究)

(1)协议描述

依赖于在Web中交换数据的应用约束,开发者可以选择诸如HTTP、SOAP或Web Service协议。

(2)服务描述

在特定环境中,Web服务描述形式化定义了机器可读的用于访问数据的接口。内容有WSDL、SML(服务建模语言)、WS-Choreography、WS-Policy和通过语义标注的语义Web连接。

WS-Choreography描述了在没有全局控制器的情况下,全局服务协作中消息的流向。(将全局服务协作称为舞步体现了工程师们的幽默)。

WS-Policy描述了通过策略断言访问服务的策略,例如安全策略、传输协议选择等。

(3)安全性

跨域的数据传输需要安全交易和备案详细的验证保障。XML安全栈中XML加密和XML签名是关键。

(4)国际化

Web服务的国际化考虑服务描述、服务交换消息的国际化。

0.3 Apache CXF的承诺

Apache CXF起源于两个项目:Celtix和XFire。两个社区的人发现在做相互重叠的工作,在Apache孵化器20个月之后,Apache CXF真正意义上诞生了。(碎碎念:几乎每个Java应用框架可以在一到两个月之间很容易能够上手使用,但与框架设计与实现者投入的时间、精力没有什么可比性,尽管这个比较有些苛刻,当我想强调的是学习新技术不要太过于着急,实在是项目进度压力太大了,也可以拿“别人做了多少工作,而我才付出了多少”来做点心里安慰。)

支持的Web服务标准

  • JAX-WS
  • JAX-RS
  • SOAP
  • WSDL
  • MTOM(Message Transmission Optimization Mechanism)
  • WS-Basic Profile
  • WS-Addressing
  • WS-Policy
  • WS-ReliableMessaging
  • WS-Security

其他承诺

  • POJO(Plain Old Java Object)

POJO没有实现诸如JMS、EJB等框架特定的接口,便于与其他框架集成,例如Spring等。CXF实现了JAX-WS和JAX-RS规范,提供了将POJO运行时解释为SOAP、RESTful Web服务的注解。

  • 前端(frontend)编程API

Web服务开发者所面临的任务有服务实现、服务消费和服务注册、发布、管理等。CXF前端的作用是方面Web服务实现和Web服务客户端的开发。CXF提供两种前端:标准的JAX-WS前端和简单前端。

  • 工具

Java to web service

Java <-> WSDL

WSDL -> JavaScript

WSDL -> Service

WSDL -> SOAP

WSDL -> XML

WSDL验证(Validator)

XSD -> WSDL

  • REST支持

支持JAX-RS规范,支持JSON数据格式。

  • 多种传输和绑定支持

数据绑定是Web服务的关键,CXF中基于SOAP的Web服务中数据绑定的含义是Java对象与消息格式(XML)之间的映射、而RESTful Web服务中可以选择XML或JSON。CXF支持SOAP和HTTP协议绑定、JAXB(Java Architecture for XML Binding)和AEGIS数据binding。

CXF支持非XML绑定,包括JSON和CORBA,同时还支持JBI(Java Business Integration)架构和SCAs(Service Component Architectures)。

(碎碎念:数据绑定应该不是Java语言实现的Web服务甚至Web中专有的功能需求,不夸张的说这种仅涉及语法层次的数据格式转换是任何一个团队在两次重复编码后都会抽象出来的基础功能)。

CXF支持诸如HTTP、HTTPS、JMS传输协议,同时提供了在单个JVM内服务之间通信的本地协议。

灵活的部署

支持部署容器/环境

  • 轻量级容器Tomcat,J2EE容器(Websphere/Weblogic/JBoss/Geronimo/JOnAS)。
  • 支持两层客户端-服务端环境部署。
  • SCA容器Tuscany
  • JBI容器ServiceMix/OpenESB/Petals

1 运行实例

订单处理应用(Order Processing Application)

处理顾客的订单功能包括:生成、验证和核实顾客订单。

应用场景

(1)顾客就购买某一产品发出订单请求;

(2)采购部接收订单请求、准备正式的购货单。购货单中包括顾客的详细信息、产品名称、数量和价格;

(3)正式的购货单会交给订单处理部门验证和核实。如果订单是有效和经核实的,订单处理部门生成唯一的订单ID,交回采购部。

(4)采购部将订单ID发回给顾客。

2 Apache CXF体系结构

总览图

2.1 总线Bus

[1]通篇只有这部分涉及总线。Apache CXF的版本间差异较大,[1]中用的是2.2.3,与spike实践中2.4.2的差别不是一般的大,一方面这是实践中较少涉及的CXF高级概念,另一方面也是懒,故这里仅记录总线的一些概念性知识。

CXF内部使用Spring配置文件定义总线,其中定义了所有端点的通用上下文、运行时基础组件。

CXF总线可以自定义,但必须在替换默认的总线行为时格外小心。

2.2前端frontend

CXF提供了前端建模的概念,使用前端API可以用简单的工厂bean、JAX-WS实现创建Web服务,以及动态的Web服务客户端。

CXF主要支持的前端是JAX-WS。JAX-WS规范建立了开发、发布和消费Web服务的语义,支持处理Web服务可操作性的WS-Basic Profile 1.1。JAX-WS同时定义了JAXB和SAAJ。JAXB规范定义了Java和XML Schema之间的绑定;SAAJ为处理SOAP消息中XML attachment提供了一种标准方式。同时,JAX-WS也定义了一个注解库可以将POJO转变为Web服务,以及WSDL定义的服务与实现服务的Java类之间详细的映射。

简单前端提供了简单组件或Java类,使用反射构建和发布Web服务,通常使用工厂组件创建服务和客户端。

2.3消息和拦截器interceptors

拦截器是拦截Web服务客户端和服务组件之间交换或传递的消息的组件,在CXF由拦截器链实现,是CXF运行时的核心功能。

可以将提供通用功能的拦截器组装入一个阶段(phase),每个阶段执行特定的消息处理,也可以将阶段添加到链中。实际上PhaseInterceptor接口继承自Interceptor,而InterceptorChain接口与Interceptor接口之间是相识关系。

一个典型的Web服务端点有三个拦截器链:inbound消息链、outbound消息链和error消息链。

在Web服务中,可以将消息视为具有输入和输出参数的操作。

2.4 服务模型

org.apache.cxf.service.model包中提供了Web服务信息建模参考类。

ServiceInfo

-- InterfaceInfo

-- OperationInfo

-- MessageInfo

-- BindingInfo

-- BindingOperationInfo

-- EndpointInfo

-- SchemaInfo

2.5 数据绑定

在Web服务场景下,数据绑定的含义是Java对象与XML元素之间的映射,CXF提供了两种数据绑定组件:JAXB和Aegis。

2.6 协议绑定

协议绑定将Web服务逻辑消息与具体的协议消息格式绑定。绑定与WSDL中端口类型相关,参见后续的解读WSDL的笔记<TODO>。

CXF支持这些绑定协议:SOAP1.1、SOAP1.2、CORBA、Pure XML。

2.7 传输transport

传输定义了传输消息的高层路由协议,与端点相关联。在WSDL中传输细节用端口元素定义。

CXF支持这些传输协议: HTTP、CORBA、JMS、Local(同一JVM中)。

3 CXF前端

3.1 JAX-WS前端

CXF支持JAX-WS 2.0,CXF的JAX-WS前端除了支持标准的基于WSDL的Web服务外,还支持用Provider、Dispatch接口形式构建基于XML的Web服务。

前者在不指定数据格式绑定的情况下,默认使用JAXB实现操作参数和消息的Java对象与XML格式之间的转换。

存在两种开发JAX-WS 基于SOAP的Web服务的方式:代码优先和契约优先。

为强调Web服务服务端和客户端代码的不同,除非特别说明,服务端代码和客户端代码在不同的Java工程中。

3.1.1 代码优先开发方式

简要说明:先编写代码,再将代码转换为WSDL。

适用性:服务实现方法的输入和输出对象格式很简单,想尽快将该方法暴露为Web服务时;需要将最初并非为服务设计而实现的遗留代码暴露为服务时。

工具支持:CXF Java2WSDL从服务实现代码生成WSDL契约和XSD。

关键步骤:

(a)创建服务端点接口SEI

(b)添加Java注解

(c)发布服务

(d)开发服务客户端

(e)测试

(a)创建服务端点接口SEI

(b)添加Java注解

根据应用场景不同,创建SEI有两种方式:从无到有,将遗留代码暴露为服务。

Order.java

package com.spike.cxf.runningexample.codefirst;

public class Order {
    private String customerID;//顾客id
    private String itemID;//商品项id
    private Integer qty;//数量
    private Double price;//价格

    public Order() {
    }

    public String getCustomerID() {
        return customerID;
    }

    public void setCustomerID(String customerID) {
        this.customerID = customerID;
    }

    public String getItemID() {
        return itemID;
    }

    public void setItemID(String itemID) {
        this.itemID = itemID;
    }

    public Integer getQty() {
        return qty;
    }

    public void setQty(Integer qty) {
        this.qty = qty;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

}

OrderProcess.java

package com.spike.cxf.runningexample.codefirst;

import javax.jws.WebService;

/**
 * Description: 订单处理服务接口SEI<br/>
 * Date: 2014-6-2 下午9:46:40
 */

@WebService(name = "OrderProcess")
public interface OrderProcess {

    String processOrder(Order order);//Web服务操作
}

OrderProcessImpl.java

package com.spike.cxf.runningexample.codefirst;

import java.util.UUID;

import javax.jws.WebService;

/**
 * Description: 订单服务SEI实现<br/>
 * Date: 2014-6-2 下午9:54:34
 */
@WebService(serviceName = "OrderProcessService", portName = "OrderProcessPort")
public class OrderProcessImpl implements OrderProcess {

    /* @see com.sipke.cxf.introduction.OrderProcess#processOrder(com.sipke.cxf.introduction.Order) */
    //    @Override
    public String processOrder(Order order) {
        String result = Validate(order);
        return result;
    }

    /**
     * Description: 验证订单<br/>
     * PRE: <br/>
     * POST: <br/>
     */
    private String Validate(Order order) {
        String customerID = order.getCustomerID();
        String itemID = order.getItemID();
        int qty = order.getQty();
        double price = order.getPrice();

        if (customerID != null && itemID != null && !"".equals(customerID) && !"".equals(itemID) && qty > 0 && price > 0.0d) {
            return "ORD" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
        } else {
            return null;
        }
    }
}

(c)发布服务

Server.java

package com.spike.cxf.runningexample.codefirst;

import javax.xml.ws.Endpoint;

/**
 * Description: 发布服务<br/>
 * Date: 2014-6-7 下午2:17:05<br/>
 * 通过http://localhost:8082/OrderProcess?wsdl访问验证
 */
public class Server {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Starting Server...");
        OrderProcessImpl orderProcessImpl = new OrderProcessImpl();
        String address = "http://localhost:8082/OrderProcess";//端口可自定义
        Endpoint.publish(address, orderProcessImpl);//发布服务,启动内嵌的Web服务器jetty

        Thread.sleep(15 * 60 * 1000L);
        System.exit(0);
    }
}

(d)开发服务客户端

Client.java

package com.spike.cxf.runningexample.codefirst;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

/**
 * Description: 代码优先实现Web服务的客户端<br/>
 * Date: 2014-6-7 下午2:28:10
 */
public class Client {
    private static final QName SERVICE_NAME = new QName("http://codefirst.runningexample.cxf.spike.com/", "OrderProcessService");//找WSDL文件中的targetNamespace
    private static final QName PORT_NAME = new QName("http://codefirst.runningexample.cxf.spike.com/", "OrderProcessPort");
    private static final String WSDL_LOCATION = "http://localhost:8082/OrderProcess?wsdl";

    public static void main(String[] args) throws MalformedURLException {
        URL wsdlURL = new URL(WSDL_LOCATION);
        Service service = Service.create(wsdlURL, SERVICE_NAME);
        OrderProcess port = service.getPort(PORT_NAME, OrderProcess.class);

        Order order = new Order();
        order.setCustomerID("C001");
        order.setItemID("I001");
        order.setPrice(1.0d);
        order.setQty(2);

        String result = port.processOrder(order);
        System.out.println(result);

    }
}

Order.java、OrderProcess.java同服务端代码

(e)测试

客户端输出:

Client output

2014-6-7 21:37:51 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
信息: Creating Service {http://codefirst.runningexample.cxf.spike.com/}OrderProcessService from WSDL: http://localhost:8082/OrderProcess?wsdl
ORDFAB0A073F9C14ADAB8560E9F1BCAD0CB

3.1.2 契约优先开发方式

简要说明:基于已存在的WSDL契约构建Web服务。

适用性:已存在定义Web服务操作的输入和输出消息格式的XML Schema;需要细粒度的控制XML到Java对象的映射;需要遵循规范的工业标准。

工具支持:CXF wsdl2java根据WSDL文件生成JAXB绑定的输入和输入消息类、服务接口和实现类、服务启动类等。

关键步骤:

(a)生成服务组件

(b)实现服务方法

(c)发布服务

(d)开发服务客户端

(e)测试

(a)生成服务组件

修改3.1.1节中生成的WSDL文件,将http://codefirst.runningexample.cxf.spike.com/修改为http://contractfirst.runningexample.cxf.spike.com/

右键修改后的WSDL文件:

选择Web服务运行时和服务定义文件(即工程中WSDL文件文中)

设置包名称和服务名称:

勾选上"Generate Server"(实现发布和启动Web服务的类):

生成的类

ObjectFactory.java 对象工厂,便于服务模型和数据绑定
Order.java 应用领域类
OrderProcess.java 服务接口类
OrderProcessImpl.java 服务实现类
OrderProcessService.java 服务表示类            
OrderProcess_OrderProcessPort_Server.java 服务发布和启动类
package-info.java 包说明类
ProcessOrder.java 服务操作processOrder的复杂类型类
ProcessOrderResponse.java 服务操作processOrder的响应复杂类型类

(b)实现服务方法

修改中OrderProcessImpl.java中processOrder方法,将@WebService中wsdlLocation值注释掉

/**
 * Please modify this class to meet your needs
 * This class is not complete
 */

package com.spike.cxf.runningexample.contractfirst;

import java.util.UUID;
import java.util.logging.Logger;

/**
 * This class was generated by Apache CXF 2.4.2
 * 2014-06-07T15:21:59.830+08:00
 * Generated source version: 2.4.2
 */

@javax.jws.WebService(serviceName = "OrderProcessService", portName = "OrderProcessPort", targetNamespace = "http://contractfirst.runningexample.cxf.spike.com/",
// wsdlLocation = "http://localhost:8082/OrderProcess?wsdl",
endpointInterface = "com.spike.cxf.runningexample.contractfirst.OrderProcess")
public class OrderProcessImpl implements OrderProcess {

    private static final Logger LOG = Logger.getLogger(OrderProcessImpl.class.getName());

    /* (non-Javadoc)
     * @see
     * com.spike.cxf.runningexample.contractfirst.OrderProcess#processOrder(com.spike.cxf.runningexample.contractfirst
     * .Order arg0 )* */
    public java.lang.String processOrder(com.spike.cxf.runningexample.contractfirst.Order arg0) {
        LOG.info("Executing operation processOrder");
        System.out.println(arg0);
        try {
            java.lang.String _return = "ORDER" + UUID.randomUUID().toString().replaceAll("-", "").toLowerCase();//修改服务实现类
            return _return;
        } catch (java.lang.Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
    }

}

(c)发布服务

已有OrderProcess_OrderProcessPort_Server.java实现

(d)开发服务客户端

除Client.java中QName中namespaceURI修改为http://contractfirst.runningexample.cxf.spike.com/,其他与3.1.1中客户端一致。

Client.java

package com.spike.cxf.runningexample.contractfirst;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

/**
 * Description: 契约优先实现Web服务的客户端<br/>
 * Date: 2014-6-7 下午2:28:10
 */
public class Client {
    private static final QName SERVICE_NAME = new QName("http://contractfirst.runningexample.cxf.spike.com/", "OrderProcessService");//找WSDL文件中的targetNamespace
    private static final QName PORT_NAME = new QName("http://contractfirst.runningexample.cxf.spike.com/", "OrderProcessPort");
    private static final String WSDL_LOCATION = "http://localhost:8082/OrderProcess?wsdl";

    public static void main(String[] args) throws MalformedURLException {
        URL wsdlURL = new URL(WSDL_LOCATION);
        Service service = Service.create(wsdlURL, SERVICE_NAME);
        OrderProcess port = service.getPort(PORT_NAME, OrderProcess.class);

        Order order = new Order();
        order.setCustomerID("C001");
        order.setItemID("I001");
        order.setPrice(1.0d);
        order.setQty(2);

        String result = port.processOrder(order);
        System.out.println(result);

    }
}

(e)测试

客户端输出

2014-6-7 22:02:25 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
信息: Creating Service {http://contractfirst.runningexample.cxf.spike.com/}OrderProcessService from WSDL: http://localhost:8082/OrderProcess?wsdl
ORDERb625ff16302e4eda88b91c0cd98e0eca

3.2 简单前端

用基于反射的API开发和部署Web服务,没有任何规范支持,不应该用于生产代码中,但作为功能性测试还是比较方便的。

Order.java与3.1.1中一致

服务接口和服务实现类不使用JAX-WS注解:

OrderProcess.java

package com.spike.cxf.runningexample.simplefrontend;

public interface OrderProcess {
    String processOrder(Order order);
}

OrderProcessImpl.java

package com.spike.cxf.runningexample.simplefrontend;

import java.util.UUID;

public class OrderProcessImpl implements OrderProcess {

    public String processOrder(Order order) {
        System.out.println("Processing order...");
        String orderID = validate(order);
        return orderID;
    }

    private String validate(Order order) {

        String custID = order.getCustomerID();
        String itemID = order.getItemID();
        int qty = order.getQty();
        double price = order.getPrice();

        if (custID != null && itemID != null && !custID.equals("") && !itemID.equals("") && qty > 0 && price > 0.0) {
            return "ORDER" + UUID.randomUUID().toString().replaceAll("-", "").toLowerCase();
        } else {
            return null;
        }
    }

}

服务发布和启动类Server.java

package com.spike.cxf.runningexample.simplefrontend;

import org.apache.cxf.frontend.ServerFactoryBean;

public class Server {

    public static void main(String[] arg) {
        //服务实现类
        OrderProcessImpl orderProcessImpl = new OrderProcessImpl();

        //用服务工厂创建服务实例
        ServerFactoryBean serverFactoryBean = new ServerFactoryBean();
        serverFactoryBean.setServiceClass(OrderProcess.class);
        serverFactoryBean.setAddress("http://localhost:8082/SimpleOrderProcess");
        serverFactoryBean.setServiceBean(orderProcessImpl);
        serverFactoryBean.create();
    }

}

客户端(Order.java、OrderProcess.java与服务端一致)

Client.java

package com.spike.cxf.runningexample.simplefrontend;

import org.apache.cxf.frontend.ClientProxyFactoryBean;

/**
 * Description: Simple Frontend Client<br/>
 * Date: 2014-6-7 下午5:26:34
 */
public class Client {
    public static void main(String[] args) {
        //客户端代理工厂
        ClientProxyFactoryBean clientProxyFactoryBean = new ClientProxyFactoryBean();
        clientProxyFactoryBean.setServiceClass(OrderProcess.class);
        clientProxyFactoryBean.setAddress("http://localhost:8082/SimpleOrderProcess");

        //创建客户端服务代理
        OrderProcess client = (OrderProcess) clientProxyFactoryBean.create();
        Order order = new Order();
        order.setCustomerID("C001");
        order.setItemID("I001");
        order.setQty(1);
        order.setPrice(2d);

        //调用服务
        String result = client.processOrder(order);
        System.out.println(result);
    }
}

3.3 动态客户端

通常Web服务客户端应用使用SEI或服务接口代理调用服务方法。动态客户端在运行时生成,使用动态客户端不需要生成和维护创建客户端的stub服务实现类。

动态客户端根据WSDL定义动态的创建输入和输出对象,同时也可以作为不实际调用Web服务情况下验证WSDL文件和输入、输出消息格式的一种单元测试方法。

CXF提供了JaxWsDynamicClientFactory和DynamicClientFactory工厂类用于创建动态客户端。这里只涉及JAX-WS版本的动态客户端。

JaxWsDynamicClientFactory类根据WSDL文件,在内存中运行时生成SEI和数据类(应用领域类,用于交互的消息)。

服务端采用3.1.1节中的服务端。

JAXWSDynamicClient.java

package com.spike.cxf.runningexample.dynamicclient;

import java.lang.reflect.Method;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;

/**
 * Description: JAX-WS动态客户端<br/>
 * Date: 2014-6-7 下午4:58:08
 */
public class JAXWSDynamicClient {
    public JAXWSDynamicClient() {
    }

    public static void main(String[] args) throws Exception {
        JaxWsDynamicClientFactory dynamicClientFactory = JaxWsDynamicClientFactory.newInstance();
        Client client = dynamicClientFactory.createClient("http://localhost:8082/OrderProcess?wsdl");
        Object order = Thread.currentThread().getContextClassLoader().loadClass("com.spike.cxf.runningexample.codefirst.Order").newInstance();
        Method setCustomerID = order.getClass().getMethod("setCustomerID", String.class);
        Method setItemID = order.getClass().getMethod("setItemID", String.class);
        Method setQty = order.getClass().getMethod("setQty", Integer.class);
        Method setPrice = order.getClass().getMethod("setPrice", Double.class);
        setCustomerID.invoke(order, "C001");
        setItemID.invoke(order, "I001");
        setQty.invoke(order, new Integer(1));
        setPrice.invoke(order, new Double(2));

        Object[] response = client.invoke("processOrder", order);
        System.out.println(response[0]);
    }
}

3.4 Provider和Dispatch服务

Provider和Dispatch接口,隶属于JAX-WS API,用于建立将消息作为原始XML处理的Web服务,而不是简单的通过方法调用的服务。其中,消息不再需要用数据绑定转换为XML格式,而是本身以原始XML格式存在,这在消息本身比较大时数据绑定成为负担时很有用。

javax.xml.ws.Provider接口提供了创建处理XML消息的服务provider,从dispatcher客户端接收XML格式的消息、处理消息并生成响应。provider实现类将作为服务端点发布。

javax.xml.ws.Dispatch接口用于处理XML消息,并将XML格式的响应发给服务provider。

3.4.1 消息模式mode

Provider和Dispatch接口允许两种类型的消息:消息(Message)和负载(Payload)。Message模式由实际数据和控制信息构成,而Payload模式仅包含实际的数据。Message模式适合用于访问Web服务请求中SOAP头部中的信息,大部分Web服务规范,如WS-Security、WS-Policy、WS-authorization使用SOAP头部来传播Web服务的上下文信息。Payload模式是Provider实现的默认模式。

两个常量:javax.xml.ws.Service.Mode.MESSAGE、javax.xml.ws.Service.Mode.PAYLOAD

3.4.2 消息对象类型

Provider和Dispatch接口使用的输入和输出消息对象可以是:javax.xml.transform.Source、javax.xml.soap.SOAPMessage、javax.activation.DataSource(这在JAX-WS规范[3]P.71中是Conformance MUST要求)。

javax.xml.transform.Source

Source对象是XML文档的直接表示,允许访问和处理XML文档内容。

其实现类包括:

DOMSource: 表示DOM树中XML元素或消息,提供了访问和创建DOM树中节点的方法。

SAXSource: 表示基于SAX(Simple API for XML)模型的XML元素或消息,是事件驱动的,使用InputSource和XMLReader对象访问和处理XML元素。

StreamSource: XML元素或消息的比特流表示。

StAXSource: XML流API,可以理解为传统意义上XML树、事件API的折中,见JSR 173。

javax.xml.soap.SOAPMessage

在使用SOAP绑定传输时,SOAPMessage是Provider实现时输入和输出消息自然的选择。SOAPMessage在Message模式下工作,消息是完整的SOAP信封,有SOAPPart和AttachmentPart构成。其中,SOAPPart封装了SOAP信封,包括SOAP头部和SOAP消息体;AttachmentPart封装了附加到SOAP消息的二进制数据。

javax.activation.DataSource

DataSource对象表示支持MIME类型的任意数据集合,以InputStream和OutputStream方式访问数据类型和数据本身。适用于Message模式下http绑定的消息,例如图片内容消息。

3.4.3 Put them together

服务端和客户端在同一个Java工程内。

主要目的:展示消息在DOMSource与SOAPMessge之间的转换和封装。

OrderProcessDOMProvider.java

package com.spike.cxf.runningexample.providerdispatch;

import java.io.IOException;

import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.dom.DOMSource;
import javax.xml.ws.Provider;
import javax.xml.ws.Service;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Description: 订单处理DOM Provider<br/>
 * Date: 2014-6-7 下午7:16:55
 */
@WebServiceProvider()
@ServiceMode(value = Service.Mode.MESSAGE)
public class OrderProcessDOMProvider implements Provider<DOMSource> {
    private static final String NS = "http://providerdispatch.runningexample.cxf.spike.com/";

    public OrderProcessDOMProvider() {
    }

    /* @see javax.xml.ws.Provider#invoke(java.lang.Object) */
    @Override
    public DOMSource invoke(DOMSource request) {
        DOMSource result = new DOMSource();

        try {
            // 1 创建SOAPMessage封装DOMSource XML请求
            MessageFactory messageFactory = MessageFactory.newInstance();
            SOAPMessage soapRequest = messageFactory.createMessage();
            soapRequest.getSOAPPart().setContent(request);//SOAPPart
            System.out.println("Incoming Client Request as a DOMSource data in Message Mode!!!");
            soapRequest.writeTo(System.out);
            System.out.println("\n");

            // 2  展示XML请求内容
            Node processOrderNode = soapRequest.getSOAPBody().getFirstChild();
            Node order = processOrderNode.getChildNodes().item(0);
            NodeList list = order.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                //获取节点名称和节点值
                System.out.println(list.item(i).getNodeName() + "=" + list.item(i).getFirstChild().getNodeValue());
            }

            // 3 创建SOAPMessage响应
            SOAPMessage orderResponse = messageFactory.createMessage();
            QName processorOrderQName = new QName(NS, "processOrder");
            QName responseQName = new QName(NS, "return");
            SOAPElement processOrderResponse = orderResponse.getSOAPBody().addChildElement(processorOrderQName);
            processOrderResponse.addChildElement(responseQName).addTextNode("ORDER1234");

            // 4 将SOAPMessage转换为DOMSource XML响应
            result.setNode(orderResponse.getSOAPPart());
        } catch (SOAPException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

}

Server.java

package com.spike.cxf.runningexample.providerdispatch;

import javax.xml.ws.Endpoint;

/**
 * Description: 发布Provider服务<br/>
 * Date: 2014-6-7 下午7:35:51
 */
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("Starting Server...");
        Object implementor = new OrderProcessDOMProvider();
        String address = "http://localhost:8082/OrderProcessDOMProvider";
        Endpoint.publish(address, implementor);

        System.out.println("Server ready...");
        Thread.sleep(5 * 60 * 1000L);
        System.out.println("Server stopping...");
        System.exit(0);
    }
}

DispatchClient.java

package com.spike.cxf.runningexample.providerdispatch;

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.dom.DOMSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.Service.Mode;

/**
 * Description: Dispatcher<br/>
 * Date: 2014-6-7 下午7:38:21
 */
public class DispatchClient {
    private static final String NS = "http://providerdispatch.runningexample.cxf.spike.com/";

    public static void main(String[] args) throws Exception {
        // 1 创建服务对象
        MessageFactory factory = MessageFactory.newInstance();
        QName providerServiceQName = new QName(NS, "OrderProcessDOMProviderService");
        QName providerPortQName = new QName(NS, "OrderProcessDOMProviderPort");
        Service service = Service.create(new URL("http://localhost:8082/OrderProcessDOMProvider?wsdl"), providerServiceQName);

        // 2 准备请求消息
        SOAPMessage soapRequest = factory.createMessage();
        QName processOrderQName = new QName(NS, "processOrder");
        SOAPElement processOrderRequest = soapRequest.getSOAPBody().addChildElement(processOrderQName);
        SOAPElement order = processOrderRequest.addChildElement("arg0");
        order.addChildElement("customerID").addTextNode("Naveen");
        order.addChildElement("itemID").addTextNode("I001");
        order.addChildElement("price").addTextNode("2.0");
        order.addChildElement("qty").addTextNode("1");
        DOMSource domRequest = new DOMSource(soapRequest.getSOAPPart());

        // 3 分发请求消息,获取响应
        Dispatch<DOMSource> dispatch = service.createDispatch(providerPortQName, DOMSource.class, Mode.MESSAGE);
        DOMSource domResponse = dispatch.invoke(domRequest);

        // 4 解析响应内容
        System.out.println("Client Request as DOMSource data in MESSAGE Mode");
        soapRequest.writeTo(System.out);
        System.out.println("\n");

        System.out.println("Response from server: ");
        System.out.println(domResponse.getNode().getLastChild().getTextContent());
    }
}

3.5 服务上下文

Web服务客户端与服务提供者之间交换的消息可以携带一些上下文信息,服务上下文信息是关于服务消息的信息,又称元数据,提供了两类信息:有关消息的数据、信息路由的传输协议。

CXF使用javax.xml.ws.handler.MessageContext接口访问服务上下文信息,这个接口与两个作用域相关:

(a)application

Application作用域中定义的消息上下文属性可以被服务提供者、服务消费之和JAX-WS handler实现共享。是默认作用域。

(b) handler

只适用于JAX-WS handler实现。

两个作用域常量: MessageContext.Scope.APPLICATION、MessageContext.Scope.HANDLER。

除OrderProcessImpl.java中注入WebServiceContext资源、客户端中QName的namespaceURI外,与3.1.1中服务端和客户端代码均一致。

OrderProcessImpl.java

package com.spike.cxf.runningexample.context;

import java.util.UUID;

import javax.annotation.Resource;
import javax.jws.WebService;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;

import org.apache.cxf.message.Message;

/**
 * Description: 订单服务SEI实现,访问服务上下文<br/>
 * Date: 2014-6-2 下午9:54:34
 */
@WebService(serviceName = "OrderProcessService", portName = "OrderProcessPort")
public class OrderProcessImpl implements OrderProcess {

    @Resource
    WebServiceContext webServiceContext;//有CXF运行时注入Web服务上下文

    /* @see com.sipke.cxf.introduction.OrderProcess#processOrder(com.sipke.cxf.introduction.Order) */
    //    @Override
    public String processOrder(Order order) {
        //访问Web服务应用上下文
        System.out.println("Getting the operation info from message context");
        MessageContext context = webServiceContext.getMessageContext();
        Object ENDPOINT_ADDRESS = context.get(Message.ENDPOINT_ADDRESS);
        Object HTTP_REQUEST_METHOD = context.get(Message.HTTP_REQUEST_METHOD);
        Object QUERY_STRING = context.get(Message.QUERY_STRING);
        Object MTOM_ENABLED = context.get(Message.MTOM_ENABLED);
        Object CONTENT_TYPE = context.get(Message.CONTENT_TYPE);
        Object WSDL_SERVICE = context.get(Message.WSDL_SERVICE);
        Object WSDL_PORT = context.get(Message.WSDL_PORT);
        Object WSDL_INTERFACE = context.get(Message.WSDL_INTERFACE);
        Object WSDL_OPERATION = context.get(Message.WSDL_OPERATION);

        System.out.println("ENDPOINT_ADDRESS=" + ENDPOINT_ADDRESS);
        System.out.println("HTTP_REQUEST_METHOD=" + HTTP_REQUEST_METHOD);
        System.out.println("QUERY_STRING=" + QUERY_STRING);
        System.out.println("MTOM_ENABLED=" + MTOM_ENABLED);
        System.out.println("CONTENT_TYPE=" + CONTENT_TYPE);
        System.out.println("WSDL_SERVICE=" + WSDL_SERVICE);
        System.out.println("WSDL_PORT=" + WSDL_PORT);
        System.out.println("WSDL_INTERFACE=" + WSDL_INTERFACE);
        System.out.println("WSDL_OPERATION=" + WSDL_OPERATION);

        String result = Validate(order);
        return result;
    }

    /**
     * Description: 验证订单<br/>
     * PRE: <br/>
     * POST: <br/>
     */
    private String Validate(Order order) {
        String customerID = order.getCustomerID();
        String itemID = order.getItemID();
        int qty = order.getQty();
        double price = order.getPrice();

        if (customerID != null && itemID != null && !"".equals(customerID) && !"".equals(itemID) && qty > 0 && price > 0.0d) {
            return "ORD" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
        } else {
            return null;
        }
    }
}

Client.java

package com.spike.cxf.runningexample.context;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

/**
 * Description: 代码优先实现Web服务的客户端,访问服务上下文<br/>
 * Date: 2014-6-7 下午2:28:10
 */
public class Client {
    private static final QName SERVICE_NAME = new QName("http://context.runningexample.cxf.spike.com/", "OrderProcessService");//找WSDL文件中的targetNamespace
    private static final QName PORT_NAME = new QName("http://context.runningexample.cxf.spike.com/", "OrderProcessPort");
    private static final String WSDL_LOCATION = "http://localhost:8082/OrderProcess?wsdl";

    public static void main(String[] args) throws MalformedURLException {
        URL wsdlURL = new URL(WSDL_LOCATION);
        Service service = Service.create(wsdlURL, SERVICE_NAME);
        OrderProcess port = service.getPort(PORT_NAME, OrderProcess.class);

        Order order = new Order();
        order.setCustomerID("C001");
        order.setItemID("I001");
        order.setPrice(1.0d);
        order.setQty(2);

        String result = port.processOrder(order);
        System.out.println(result);

    }
}

参考文献

[1]Balani N., Hathi R..Apache CXF web service development Develop and deploy SOAP and RESTful web service[M]. Birmingham: Packet Publishing. 2009.

[2] W3C Web of Services. http://www.w3.org/standards/webofservices/[EB/OL].[2014-05-31]

[3] The Java API for XML-Based Web Services (JAX-WS) 2.2 Rev a. JAX-WS Specification.

Apache CXF 103 CXF Basics - partial的更多相关文章

  1. Apache CXF 102 CXF with REST

    前言 续上篇Apache CXF 101,摘抄部分REST概念性知识,以运行实例考察CXF对REST的支持. 目录 1 REST简介 2 工具 3 运行实例 内容 本Spike记录中内容,如无特别指出 ...

  2. WebService -- Java 实现之 CXF ( 使用CXF工具生成client 程序)

    1. 下载CXF 工具解压到磁盘 2.添加工具bin目录到PATH环境变量 3.创建一个CXF client新项目 4. run -> cmd 到指定目录,并运行工具目录下的批处理 “wadl2 ...

  3. CXF之四 cxf集成Spring

    CXF原生支持spring,可以和Spring无缝集成.WebService框架CXF实战一在Tomcat中发布WebService(二)通过Spring Web实现CXFServlet.下面将Spr ...

  4. Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service

    实现目标 http://localhost:9000/rs/roomservice 为入口, http://localhost:9000/rs/roomservice/room为房间列表, http: ...

  5. Apache CXF 例子

    来自:http://www.cnblogs.com/frankliiu-java/articles/1641949.html Apache CXF 是一个开放源代码框架,是在Xfire 跟Celtix ...

  6. JAVAEE——BOS物流项目07:WebService入门、apache CXF入门、基于CXF发布CRM服务

    1 学习计划 1.WebService入门 n 什么是WebService n 调用网络上的WebService服务 n SOAP和WSDL概念 n 基于JDK1.7发布一个简单的WebService ...

  7. Apache cxf暴露接口以及客户端调用之WebService初步理解

    在我们真实的项目中,经常会调用别人提供给我们的接口,或者在自己的团队中, restful风格的前后端分离也经常会提供一个后端接口暴露出去供app,或者.net/C/C++程序员去调用,此时就需要使用到 ...

  8. Web Service与Apache CXF 框架

    一.WebService简介 为了支持跨网络的机器间相互操作交互而设计,用于开发分布式的互操作的应用程序组件. Web Service服务通常被定义为一组模块化的API,它们可以通过网络进行调用,来执 ...

  9. apache CXF quickstart

    1下载 官网: cxf.apache.org 下载 CXF 的开发包: 解压上面的 zip 文件 : 2介绍 1什么是cxf Apache CXF™ is an open source service ...

随机推荐

  1. ViewPager滑动页面的实现方法

    package com.lixu.pagerview; import java.util.ArrayList; import android.app.Activity; import android. ...

  2. iOS解决两个静态库的冲突 duplicate symbol

    http://blog.163.com/023_dns/blog/static/118727366201391544630380/ 场景: 解决TencentOpenAPI.framework与Zba ...

  3. WEKA使用教程(界面工具的用法)

    WEKA使用教程 目录 1. 简介2. 数据格式3.数据准备4. 关联规则(购物篮分析)5. 分类与回归6. 聚类分析 1. 简介 WEKA的全名是怀卡托智能分析环境(Waikato Environm ...

  4. 安装xampp后,遇到的各种问题

    一.apache无法启动 1.查看端口是否被占用 80端口冲突,解决方法:打开目录C:\xampp\apache\conf(我的安装目录为C:\xampp)下的httpd.conf文件,将Listen ...

  5. JS创建自定义对象

    普通对象的创建: 创建对象: 1.people = new Object(); people.name = "lin"; people.age = "26“; 2.创建字 ...

  6. 【django入门教程】Django的安装和入门

    很多初学django的朋友,都不知道如何安装django开发以及django的入门,今天小编就给大家讲讲django入门教程. 注明:python版本为3.3.1.Django版本为1.5.1,操作系 ...

  7. SVN不能提交时的处理

    下面的是我的截图: EMZ3.0 qrh$ svn commit -m ""svn: E155010: Commit failed (details follow):svn: E1 ...

  8. (转)iOS消息推送机制的实现

    原:http://www.cnblogs.com/qq78292959/archive/2012/07/16/2593651.html iOS消息推送机制的实现 iOS消息推送的工作机制可以简单的用下 ...

  9. alloc和初始化的定义

    1.alloc是为原始实例进行分配内存,但是还不能使用 2.初始化的作用就是将一个对象的初始状态(即它的实例变量和属性)设定为合理的值,然后返回对象.它的目的就是返回一个有用的值

  10. php大力力 [015节]兄弟连高洛峰php教程(土豆网栏目地址)

    兄弟连高洛峰php教程 兄弟连高洛峰php教程(土豆网栏目地址) [2014]兄弟连高洛峰 PHP教程1.1.1 新版视频形式介绍 [2014]兄弟连高洛峰 PHP教程1.1.2 BS结构软件类型介绍 ...