java 私塾课堂笔记——WebService

一:什么是WebService
WebService(Web服务)是使应用程序可以以与平台和编程语言无关的方式进行相互通信的一项技术。Web 服务是一个软件接口,它描述了一组可以在网络上通过标准化的 XML 消息传递访问的操作。它使用基于 XML 语言的协议来描述要执行的操作或者要与另一个 Web 服务交换的数据。一组以这种方式交互的 Web 服务在面向服务的体系结构(Service-Oriented Architecture,SOA)中定义了特殊的 Web 服务应用程序。
1:Web 服务体系结构
Web 服务体系结构描述了一个框架,在这个框架中服务可在分布式计算环境中被动态地描述、发布、发现和调用。这个体系结构定义了四个基本构件(它们的名称在接下去的两段中用粗体字来标明)和三个决定这些构件之间如何交互的标准操作。
服务这个概念处于 Web 服务模型的核心,它被定义为执行某种任务(如订购图书或翻译信件)的一组操作。在 Web 服务的上下文中,可以使用标准的 XML 技术(如 SOAP、WSDL 和 UDDI)来描述、发现和调用服务。
服务由服务提供者来实现和发布。服务由服务请求者来发现和调用。有关服务的信息可被保存在服务注册中心。下面这张图说明了这三个构件之间是如何交互的。
【此处有图,可以到java 私塾官网下载完整笔记:www.javass.cn】

从企业角度看,服务提供者是服务的所有者。从技术角度看,它是提供对服务的访问的平台。同样,服务请求者既是一个需要完成一项具体任务的企业,也是发现和调用服务所需的工具。
可以从如下角度对上图所示的三种基本操作进行描述:
a.发布(Publish):由服务提供者来执行以使大家知道服务的存在和功能。
b.发现(Find):由服务请求者来执行以定位能满足某种需要的服务。
c.绑定(Bind):由服务请求者来执行以调用由服务提供者提供的服务。

二:能干什么
1:成为各孤立的站点之间的信息能够相互通信、共享的一种接口
2:由于webservice基于标准的协议和xml,使得大量异构程序和平台之间就能够互相操作,大家都面向webservice,而不用去关心具体的实现是由什么开发语言提供的。

三:有什么
1:基本 Web 服务规范
Web 服务规范通常归为两类:基本 Web 服务规范和扩展 Web 服务规范。基本规范有:
(1)SOAP(Simple Object Access Protocol):
简单对象访问协议是在分散或分布式的环境中交换信息并执行远程过程调用的轻量级协议,是一个基于XML的协议。使用SOAP,不用考虑任何特定的传输
协议(最常用的还是HTTP协议),可以允许任何类型的对象或代码,在任何平台上,以任何一种语言相互通信。
SOAP包括四个部分:
a.SOAP封装(envelop),封装定义了一个描述消息中的内容是什么,是谁发送的,谁应当接受并处理它以及如何处理它们的框架;
b.SOAP编码规则(encoding rules),用于表示应用程序需要使用的数据类型的实例;
c.SOAP RPC表示(RPC representation),表示远程过程调用和应答的协定;
d.SOAP绑定(binding),使用底层协议交换信息。
*应用中比较关注的是envelop,由一个或多个Header和一个Body组成。
(2)WSDL(Web Services description Language):
Web服务描述语言是详细说明描述基于 SOAP 的 Web 服务的标准方式的规范,由Ariba、Intel、IBM、MS等共同提出,包括消息应采用的形式以及应将其发送到何处。其中还详细说明了此类消息的响应。当与相应的工具结合使用时,WSDL 允许以编程方式创建对 Web 服务的调用,甚至不用知道所查找的 Web 服务是什么;应用程序可以从 WSDL 文件中提取这些详细信息,并提供要使用的编程接口。
可描述Web服务的三个基本属性:
?服务做些什么——服务所提供的操作(方法)
?如何访问服务——和服务交互的数据格式以及必要协议
?服务位于何处——协议相关的地址,如URL
WSDL文档以端口集合的形式来描述Web服务,WSDL 服务描述包含对一组操作和消息的一个抽象定义,绑定到这些操作和消息的一个具体协议,和这个绑定的一个网络端点规范。
WSDL描述语言一般包含三部分
(1)服务做些什么部分:包括了type、message和portType元素
Type:定义了Web Service使用的数据结构(使用XML Schema定义)
Message:一个Message是SOAP的基本通信元素。每个Message可以有一个或多个Part,每个Part代表一个参数。
PortType:消息汇总为不同的操作并归入到一个被称为portType的实体中。一个portType代表一个接口(Web Service支持的操作集合),每个Web Service可以有多个接口,它们都使用portType表示。每个操作又包含了input和output部分。
(2)如何访问服务部分:包含binding元素
binding元素将portType绑定到特定的通信协议上(如HTTP上的SOAP协议)
(3)服务位于何处部分:由service元素组成
它将portType,binding以及Web Service实际的位置(URI)放在一起描述
<?xml version="1.0" ebcoding="UTF-8"?>

-<wsdl:definitiongs xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:ns1="http://org.apache.axis2/xsd"

xmlsn:ns="http://ws.javasscn" xmlns:wsaw="http://www.w3org/2006/05/addressing/wsdl"

xmlsn:http="http://schemas.xmlsoap.org/wsdl/http" xmlsn:xs="http://www.w3org/2001/XMLSchema"

xmlsn:mime=" http://schemas.xmlsoap.org/wsdl/mime" xmlsn:soap=" http://schemas.xmlsoap.org/wsdl/soap"

xmlsn:soap12="http://schemas.xmlsoap.org/wsdl/soap12" targetNamespace="http://ws.javass.cn">

<wsdl:documentation>HelloWorldService</wsdl:documentation>

+<wsdl:types>

+<wsdl:message name="helloWorldServiceRequest">

+<wsdl:message name="helloWorldServiceResponse">

+<wsdl:portType name="HelloWorldService portType">

+<wsdl:binding name="HelloWorldServiceSoap11Binding"type="ns: HelloWorldService portType">

+<wsdl:binding name="HelloWorldServiceSoap12Binding" type="ns: HelloWorldService portType">

+<wsdl:binding name="HelloWorldServiceHttpBinding" type="ns: HelloWorldService portType">

+<wsdl:service name="HelloWorldService

</wsdl:definitiongs>
复制代码

(3)WSDD(Web Service Deployment Descriptor)就是WEB服务分布描述, 它定义了WEB服务的接口,如服务名、提供的方法、方法的参数等信息。
(4)UDDI(Universal Description, Discovery and Integration):
统一描述、发现和集成是一项从最初提出后发生了一系列变化的标准。UDDI用于集中存放和查找WSDL描述文件,起着目录服务器的作用。
其最初的目的是为了给各个公司提供在全球注册中心中注册服务并在此注册中心中搜索可能想使用的服务的机制。不过,由于很多公司对于将其系统对外开放的问题上都相当保守,这个目标并没有完全实现。但是,很多公司已将 UDDI 作为内部的服务及服务信息注册中心使用
2:扩展 Web 服务规范
总共有数十种 WS-* 规范,其中几种对企业尤为有用。即:
WS-Security:此规范处理加密和数字签名,允许创建特定类型的应用程序,以防止消息,且能实现不可否认功能。
WS-Policy:此规范对 WS-Security 进行了扩展,允许更具体地说明谁可以采用何种方式使用服务。
WS-I:尽管 Web 服务应设计成具有互操作性,但在实际中,各个规范对不同实现的解释的灵活性常常足以导致出现问题。WS-I 提供了一组可用于防止出现各种问题的标准和实践,并提供了标准化测试来检查问题。
WS-BPEL:单个服务很好处理,但应用程序在大多数情况下则较难处理。企业级计算要求至少将多个服务组合为一个完整的系统,而 WS-BPEL 提供了用于指定创建此类系统所必需的交互(如分支和并发处理)。

====================================================================
四:怎么做
(一)目前常见的实现方式:
开源最流行的是Apache的Axis(Apache Extensible Interaction System),可以跟所有的主流服务器结合。
SUN,早期时JAX-RPC,现在是JAX-WS
IBM采用的是自行开发的WSDK(IBM WebSphere SDK for Web Services)
我们采用:tomcat+axis2
(二):HelloWorld一
1:构建运行环境
(1)把axis2-1.4.1-war.zip中的axis2.war包放到tomcat的webapps中,然后启动tomcat,会发布axis2,如果启动没有错误,就可以下一步
(2)验证:在地址栏输入http://localhost:8080/axis2 ,如果能正常显示,说明axis2的运行环境已经OK了。可以点击Administration,然后输入用户名admin和密码axis2进入管理台。
2:构建开发环境
把axis2-1.4.1-bin.zip中lib里面的都引入到当前的开发环境中就好了
3:写自己的第一个服务的类package cn.javass.ws.helloworld.server;


import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

public class MyService {

/**

* 读取client端传递来的参数,然后执行自己的功能,并返回值给客户端

*/

public OMElement helloWorldService(OMElement in) {

//1:接收参数,将in转换为String

String requestMessage = in.getText();


//2:这里添加服务端的具体处理



//3:准备返回

//3.1 创建响应用的SOAP包。

OMFactory f = OMAbstractFactory.getOMFactory();

//3.2 OMNamespace指定此SOAP文档名称空间,可以随便写

OMNamespace omNs = f.createOMNamespace("http://www.javass.cn/", "cc");

//3.3 创建返回元素,并指定其在omNs指代的名称空间中。

OMElement retElement = f.createOMElement("myRet", omNs);

//3.4 指定返回元素的文本内容

String retMessage = requestMessage + ",这是第一个WebService,已经处理!";

retElement.setText(retMessage);



return retElement;

}

}
复制代码4:在src目录下面建立一个META-INF的文件夹,在里面放Services.xml文件<service name="HelloWorldService">

<parameter name="ServiceClass" locked="xsd:false">

cn.javass.ws.helloworld.server.MyService

</parameter>

<operation name="helloWorldService">

<messageReceiver

class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver" />

</operation>

</service>
复制代码5:运行cmd,到classes的目录,然后打包,输入:jar cvf hello.aar . ,注意后面的”.”不能少,表示输出到当前位置,然后打出一个hello.aar的包来。
6:然后可以直接把这个文件拷贝到tomcat的webapps\axis2\WEB-INF\services文件夹下面,就会自动发布了。当然也可以进到axis2的控制台,然后使用upload service的方式也行。如果发布没有错误,那么服务就做好了。
7:开始写客户端package cn.javass.ws.helloworld;


import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import org.apache.axis2.AxisFault;

import org.apache.axis2.Constants;

import org.apache.axis2.addressing.EndpointReference;

import org.apache.axis2.clustering.MessageSender;

import org.apache.axis2.client.Options;

import org.apache.axis2.client.ServiceClient;


public class Client {


public static void main(String[] args) {

try {

//1:targetEPR指定打包的Service(hello.aar文件)在容器中的物理位置

EndpointReference targetEPR = new EndpointReference(

"http://localhost:9000/axis2/services/HelloWorldService");



//2:准备客户端调用

ServiceClient sender = new ServiceClient();

//2.1 准备options,设置要访问的web服务在什么地方

Options options = new Options();

options.setTo(targetEPR);

sender.setOptions(options);



//2.2 创建request的SOAP包

OMFactory f = OMAbstractFactory.getOMFactory();

//2.3 OMNamespace指定此SOAP文档名称空间,要跟服务那边的对应上

OMNamespace omNs = f.createOMNamespace("http://www.javass.cn/","cc");

//2.4 创建元素reqElement,并指定其在omNs指代的名称空间中,第一个参数就是要访问的operation

OMElement reqElement = f.createOMElement("helloWorldService", omNs);

//2.5 指定元素的文本内容。

reqElement.setText("测试第一个WebService");


//3:发出request SOAP,同时将得到的远端由helloWorldService方法返回的信息保存到result

OMElement result = sender.sendReceive(reqElement);


System.out.println(result);

} catch (Exception axisFault) {

axisFault.printStackTrace();

}

}

}
复制代码8:运行结果如下:
客户端:
<cc:myRet xmlns:cc="http://www.javass.cn/">测试第一个WebService,这是第一个WebService,已经处理!</cc:myRet>
(三):HelloWorld二(Pojo)
1:把axis2-1.4.1-bin包解开,然后把下面直到bin的路径设置到path中去,还要设置AXIS2_HOME的值
2:到axis2-1.4.1-bin包的samples文件夹中,找到quickstart,建立一个工程来测试和运行它,类文件如下:package samples.quickstart.service.pojo;

import java.util.HashMap;

public class StockQuoteService {

private HashMap map = new HashMap();

public double getPrice(String symbol) {

Double price = (Double) map.get(symbol);

if(price != null){

return price.doubleValue();

}

return 42.00;

}

public void update(String symbol, double price) {

map.put(symbol, new Double(price));

}

}
复制代码3:运行cmd,把路径设置到classes下面,然后运行:java2wsdl -cp . -cn samples.quickstart.service.pojo.StockQuoteService -of META-INF/StockQuoteService.wsdl,就会生成相应的wsdl文件
4:在META-INF中建立services文件,内容如下:<service name="StockQuoteService" scope="application">

<description>

Stock Quote Sample Service

</description>

<messageReceivers>

<messageReceiver

mep="http://www.w3.org/2004/08/wsdl/in-only"

class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>

<messageReceiver

mep="http://www.w3.org/2004/08/wsdl/in-out"

class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>

</messageReceivers>

<parameter name="ServiceClass">

samples.quickstart.service.pojo.StockQuoteService

</parameter>

</service>
复制代码5:运行cmd,把路径设置到classes下面,然后运行:jar -cvf ht.aar.,注意后面有个”.”,然后打好包,文件名称叫ht.aar
6:把ht.aar拷贝到tomcat的webapps\axis2\WEB-INF\services
7:启动tomcat,然后运行http://localhost:8080/axis2/services/listServices就可以查看所有发布的服务了
8:可以通过http://localhost:8080/axis2/services/StockQuoteService?wsdl来查看wsdl的文件,对应的schema在http://localhost:8080/axis2/services/StockQuoteService?xsd<?xml version="1.0" ebcoding="UTF-8"?>

-<wsdl:definitiongs xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:ns1="http://org.apache.axis2/xsd"

xmlsn:ns="http://pojo.javasscn" xmlns:wsaw="http://www.w3org/2006/05/addressing/wsdl"

xmlsn:http="http://schemas.xmlsoap.org/wsdl/http" xmlsn:xs="http://www.w3org/2001/XMLSchema"

xmlsn:mime=" http://schemas.xmlsoap.org/wsdl/mime" xmlsn:soap=" http://schemas.xmlsoap.org/wsdl/soap"

xmlsn:soap12="http://schemas.xmlsoap.org/wsdl/soap12" targetNamespace=http://pojo.javass.cn>

<wsdl:documentation>MyPojoService</wsdl:documentation>

+<wsdl:types>

+<wsdl:message name="testRequest">

+<wsdl:message name="testResponse">

+<wsdl:portType name="MyPojoService portType">

+<wsdl:binding name="MyPojoServiceSoap11Binding"type="ns: MyPojoService portType">

+<wsdl:binding name="MyPojoServiceSoap12Binding" type="ns:MyPojoService portType">

+<wsdl:binding name="MyPojoServiceHttpBinding" type="ns:MyPojoService portType">

+<wsdl:service name="MyPojoService

</wsdl:definitiongs>
复制代码9:可以在IE里面进行测试,测试步骤如下:
(1)运行下述语句进行查看
http://localhost:8080/axis2/services/StockQuoteService/getPrice?symbol=javasscn会得到如下结果:
<ns:getPriceResponse xmlns:ns="http://pojo.service.quickstart.samples">
<ns:return>42.0</ns:return>
</ns:getPriceResponse>
(2)然后运行下述语句进行修改
http://localhost:8080/axis2/services/StockQuoteService/update?symbol=javasscn&price=100
(3)再次运行(1),你会发现结果变成了:
<ns:getPriceResponse xmlns:ns="http://pojo.service.quickstart.samples">
<ns:return>100.0</ns:return>
</ns:getPriceResponse>

====================================================================
(四):HelloWorld二改进版(AXIOM)
1:类文件修改成为package samples.quickstart.service.axiom;


import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;


import java.util.HashMap;


public class StockQuoteService {

private HashMap map = new HashMap();



public OMElement getPrice(OMElement element) throws XMLStreamException {

//构建节点本身

element.build();

//从父节点删除本节点

element.detach();


//获取节点的第一个子节点

OMElement symbolElement = element.getFirstElement();

//获取这个子节点的文本值

String symbol = symbolElement.getText();


//准备返回的值

String returnText = "42";

Double price = (Double) map.get(symbol);

if (price != null) {

returnText = "" + price.doubleValue();

}


//组装需要返回的xml,值也包含在这个xml当中

//<getPriceResponse>

// <price>

// returnText

// </price>

//</getPriceResponse>

OMFactory fac = OMAbstractFactory.getOMFactory();

OMNamespace omNs = fac.createOMNamespace(

"http://axiom.service.quickstart.samples/xsd", "tns");

OMElement method = fac.createOMElement("getPriceResponse", omNs);

OMElement value = fac.createOMElement("price", omNs);

value.addChild(fac.createOMText(value, returnText));

method.addChild(value);


return method;

}


public void update(OMElement element) throws XMLStreamException {

//构建节点本身

element.build();

//从父节点删除本节点

element.detach();


//获取节点的第一个子节点——symbol

OMElement symbolElement = element.getFirstElement();

//获取这个子节点的文本值

String symbol = symbolElement.getText();

//获取节点的第二个字节点——price

OMElement priceElement = (OMElement) symbolElement.getNextOMSibling();

//获取price的值

String price = priceElement.getText();


//把值放置到缓存中

map.put(symbol, new Double(price));

}

}
复制代码2:service.xml如下:<service name="StockQuoteService" scope="application">

<description>

Stock Quote Service

</description>

<operation name="getPrice">

<messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>

</operation>

<operation name="update">

<messageReceiver class="org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver"/>

</operation>

<parameter name="ServiceClass">samples.quickstart.service.axiom.StockQuoteService</parameter>

</service>
复制代码3:重新打包,然后拷贝到webapps\axis2\WEB-INF\services下面,覆盖ht.aar
4:客户端:package samples.quickstart.service.axiom;


import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import org.apache.axis2.AxisFault;

import org.apache.axis2.Constants;

import org.apache.axis2.addressing.EndpointReference;

import org.apache.axis2.clustering.MessageSender;

import org.apache.axis2.client.Options;

import org.apache.axis2.client.ServiceClient;


public class Client {

//指定打包的Service在容器中的物理位置

private static EndpointReference targetEPR =

new EndpointReference("http://localhost:8080/axis2/services/StockQuoteService");

/**

* 准备访问getPrice方法时需要传递的xml节点

* <getPrice>

* <symbol>

* value

* </symbol>

* </getPrice>

* @param symbol

* @return

*/

public static OMElement getPricePayload(String symbol) {

OMFactory fac = OMAbstractFactory.getOMFactory();

OMNamespace omNs = fac.createOMNamespace("http://axiom.service.quickstart.samples/xsd", "tns");


OMElement method = fac.createOMElement("getPrice", omNs);

OMElement value = fac.createOMElement("symbol", omNs);

value.addChild(fac.createOMText(value, symbol));

method.addChild(value);

return method;

}

/**

* 准备访问update方法时需要传递的xml节点

* <update>

* <symbol>

* value

* </symbol>

* <price>

* price value

* </price>

* </update>

* @param symbol

* @return

*/

public static OMElement updatePayload(String symbol, double price) {

OMFactory fac = OMAbstractFactory.getOMFactory();

OMNamespace omNs = fac.createOMNamespace("http://axiom.service.quickstart.samples/xsd", "tns");


OMElement method = fac.createOMElement("update", omNs);


OMElement value1 = fac.createOMElement("symbol", omNs);

value1.addChild(fac.createOMText(value1, symbol));

method.addChild(value1);


OMElement value2 = fac.createOMElement("price", omNs);

value2.addChild(fac.createOMText(value2, Double.toString(price)));

method.addChild(value2);

return method;

}

public static void main(String[] args) {

try {

//获或得先从服务上查找WSO的值的节点

OMElement getPricePayload = getPricePayload("WSO");

//获得修改WSO的值的节点

OMElement updatePayload = updatePayload("WSO", 123.42);



//准备options,设置要访问的web服务在什么地方

Options options = new Options();

options.setTo(targetEPR);

options.setTransportInProtocol(Constants.TRANSPORT_HTTP);


ServiceClient sender = new ServiceClient();

sender.setOptions(options);



//直接调用In-Only的服务

sender.fireAndForget(updatePayload);



System.err.println("price updated");

//直接调用In-Out的服务

OMElement result = sender.sendReceive(getPricePayload);

//获取到的返回值也是xml

String response = result.getFirstElement().getText();

System.err.println("Current price of WSO: " + response);


} catch (Exception e) {

e.printStackTrace();

}

}

}
====================================================================
Axis2的客户端调用
Web services提供的服务多种多样,有的可以马上获得结果,有的要消耗很长的时间。所以,如果我们需要多种调用方式来对付不同的情况。
大多数的Web services都提供阻塞(Blocking)和非阻塞(Non-Blocking)两种API。
Blocking API - 调用端要等被调用的函数运行完毕才继续往下走。
Non-Bloking API - 调用端运行完调用函数以后就直接往下走了,调用端和被调用端是异步执行的。返回值是用回调函数来实现的。
这种异步叫做API层异步(API Level Asynchrony)。他们只用到一个连接来发送和接收消息,而且,如果是那种需要运行很长时间的函数,还会碰到Time Out 错误,如果用两个连接分别处理发送和接收消息,调用的时间就可以缩短,也可以解决Time Out 问题。用两个连接来分别处理发送和接收消息,叫做传输层异步(Transport Level Asynchrony)。
将前面的 2 种异步结合起来,就有了四种不同的调用模式
服务的调用代码:
blocking invocationtry {

OMElement payload = ClientUtil.getEchoOMElement();

Options options = new Options();

options.setTo(targetEPR); // this sets the location of MyService service

ServiceClient serviceClient = new ServiceClient();

serviceClient.setOptions(options);

OMElement result = sender.sendReceive(payload);

System.out.println(result);

} catch (AxisFault axisFault) {

axisFault.printStackTrace();

}
复制代码IN-ONLYtry {

OMElement payload = ClientUtil.getPingOMElement();

Options options = new Options();

options.setTo(targetEPR);

ServiceClient serviceClient = new ServiceClient();

serviceClient.setOptions(options);

serviceClient.fireAndForget(payload);

} catch (AxisFault axisFault) {

axisFault.printStackTrace();

}
复制代码EchoBlockingClient
将第一段代码的调用代码改为 serviceClient.sendReceiveNonblocking(payload, callback); 具体的例子在 "Axis2Home/samples/userguide/src/userguide/clients" 中
Axis 提供三个方法来接收 callback 对象
public abstract void onComplete(AsyncResult result);
public abstract void onError(Exception e);
public boolean isComplete() {}
其中,前面两个是需要用户来实现的try {

OMElement payload = ClientUtil.getEchoOMElement();

Options options = new Options();

options.setTo(targetEPR);

options.setAction("urn:echo");

//Callback to handle the response

Callback callback = new Callback() {

public void onComplete(AsyncResult result) {

System.out.println(result.getResponseEnvelope());

}

public void onError(Exception e) {

e.printStackTrace();

}

};

//Non-Blocking Invocation

sender = new ServiceClient();

sender.setOptions(options);

sender.sendReceiveNonBlocking(payload, callback);

//Wait till the callback receives the response.

while (!callback.isComplete()) {

Thread.sleep(1000);

}

} catch (AxisFault axisFault) {

axisFault.printStackTrace();

} catch (Exception ex) {

ex.printStackTrace();

} finally {

try {

sender.cleanup();

} catch (AxisFault axisFault) {

//

}

}
复制代码EchoNonBlockingDualClienttry {

OMElement payload = ClientUtil.getEchoOMElement();

Options options = new Options();

options.setTo(targetEPR);

options.setTransportInProtocol(Constants.TRANSPORT_HTTP);

options.setUseSeparateListener(true);

options.setAction("urn:echo"); // this is the action mapping we put within the service.xml

//Callback to handle the response

Callback callback = new Callback() {

public void onComplete(AsyncResult result) {

System.out.println(result.getResponseEnvelope());

}

public void onError(Exception e) {

e.printStackTrace();

}

};

//Non-Blocking Invocation

sender = new ServiceClient();

sender.engageModule(new QName(Constants.MODULE_ADDRESSING));

sender.setOptions(options);

sender.sendReceiveNonBlocking(payload, callback);

//Wait till the callback receives the response.

while (!callback.isComplete()) {

Thread.sleep(1000);

}

//Need to close the Client Side Listener.

} catch (AxisFault axisFault) {

axisFault.printStackTrace();

} catch (Exception ex) {

ex.printStackTrace();

} finally {

try {

sender.finalizeInvoke();

} catch (AxisFault axisFault) {

//have to ignore this

}

}
复制代码在 Server 端添加 Addressing 支持的方式是,在 Addressing Module 中,将 Handlers 的描述放在 ”pre-dispatch” 语句中,那么它的加载则需要通过在 "/webapps/axis2/WEB-INF" 文件夹下的 axis2.xml 中增加一句话来完成: <module ref="addressing"/>
客户端支持 Addressing 的方式,一种是将 addressing-<version>.mar 放在 classpath 中,另一种就是根据给定的库位置创建一个 ConfigurationContext
具体的做法是在 sender = new ServiceClient(); 之前加上
ConfigurationContext configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(< Axis2RepositoryLocation >, null);
然后将 "sender = new ServiceClient();" 改为 "sender = new ServiceClient(configContext, null);"
(五):HelloWorld二改进版(ADB)
1:要想使用Axis2 Databinding Framework (ADB),首先需要根据WSDL来生成在服务端的skeleton文件。进入cmd,设置路径到工程的根下面,运行如下语句:
WSDL2Java -uri classes\META-INF\StockQuoteService.wsdl -p samples.quickstart.service.adb -d adb -s -ss -sd -ssi -o build\service
运行完成后,会生成一个build的文件夹,里面有很多生成的东西。
其中:-d:表示是adb;
-s:表示是同步调用;
-ss:表示创建服务端的代码(包括skeleton和相关文件);
-sd:表示创建services.xml;
-ssi:表示为skeleton创建接口;
2:然后就需要开发人员去实现skeleton的接口了,实现类的架子已经生成了,只要去实现就好了,当然,需要将build\service\src的文件夹添加到Eclipse中工程的源代码来源中。实现类如下:package samples.quickstart.service.adb;

import java.util.HashMap;

import samples.quickstart.service.pojo.*;

public class StockQuoteServiceSkeleton implements

StockQuoteServiceSkeletonInterface {

private static HashMap map = new HashMap();

public void update(Update param0) {

map.put(param0.getSymbol(), new Double(param0.getPrice()));

}

public GetPriceResponse getPrice(GetPrice param1) {

Double price = (Double) map.get(param1.getSymbol());

double ret = 42;

if (price != null) {

ret = price.doubleValue();

}

GetPriceResponse res = new GetPriceResponse();

res.set_return(ret);

return res;

}

}
复制代码3:客户端的Stub也同样需要生成,进入cmd,设置路径到工程的根下面,运行如下语句:
WSDL2Java -uri classes\META-INF\StockQuoteService.wsdl -p samples.quickstart.clients -d adb -s -o build\client
运行完成后,会生成一个build的文件夹,里面有client文件夹。
需要将build\ client\src的文件夹添加到Eclipse中工程的源代码来源中。
4:同样需要services.xml文件来描述,如下:<service name="StockQuoteService" scope="application">

<messageReceivers>

<messageReceiver mep="ht tp://w ww.w3.org/2004/08/wsdl/in-out"

class="samples.quickstart.service.adb.StockQuoteServiceMessageReceiverInOut"/>

<messageReceiver mep="htt p://w ww.w3.org/2004/08/wsdl/in-only"

class="samples.quickstart.service.adb.StockQuoteServiceMessageReceiverInOnly"/>

</messageReceivers>

<parameter name="ServiceClass">samples.quickstart.service.adb.StockQuoteServiceSkeleton

</parameter>

<operation name="update" mep="ht tp://ww w.w3.org/2004/08/wsdl/in-only">

<actionMapping>urn:update</actionMapping>

</operation>

<operation name="getPrice" mep="ht tp://ww w.w3.org/2004/08/wsdl/in-out">

<actionMapping>urn:getPrice</actionMapping>

<outputActionMapping>ht tp://quickstart.samples/StockQuoteServicePortType/getPriceResponse</outputActionMapping>

</operation>

</service>[code]

5:然后写客户端来调用服务,客户端如下:

[code]

package samples.quickstart.service.adb;

import samples.quickstart.clients.StockQuoteServiceStub;

public class Client {

public static void main(java.lang.String args[]){

try{

StockQuoteServiceStub stub =

new StockQuoteServiceStub

("ht tp://localhost:8080/axis2/services/StockQuoteService");

getPrice(stub);

update(stub);

getPrice(stub);

} catch(Exception e){

e.printStackTrace();

System.err.println("\n\n\n");

}

}

/* fire and forget */

public static void update(StockQuoteServiceStub stub){

try{

StockQuoteServiceStub.Update req = new StockQuoteServiceStub.Update();

req.setSymbol ("ABC");

req.setPrice (42.35);

stub.update(req);

System.err.println("price updated");

} catch(Exception e){

e.printStackTrace();

System.err.println("\n\n\n");

}

}

/* two way call/receive */

public static void getPrice(StockQuoteServiceStub stub){

try{

StockQuoteServiceStub.GetPrice req = new StockQuoteServiceStub.GetPrice();

req.setSymbol("ABC");

StockQuoteServiceStub.GetPriceResponse res =

stub.getPrice(req);

System.err.println(res.get_return());

} catch(Exception e){

e.printStackTrace();

System.err.println("\n\n\n");

}

}

}
复制代码6:跟ADB的方式类似的还有:XMLBeans、JiBX进行服务邦定的方式,都是通过WSDL2Java来生成服务端的Skeleton和客户端的Stub,还有相应的接口和实现的架子,具体的服务和客户端的调用还是需要开发人员来写的。

======================================================================
五:认识Axis2
Axis2是什么?
Apache Axis2 是Axis的后续版本,是新一代的SOAP引擎。Axis2是通过用Java语言开发Web Service的工具,Axis2封装了SOAP消息的处理,同时还有做了其他的大量的工作来简化Web Sercice的开发者的工作。
Axis2能做什么?
Axis2的主要特点有:
(1)提供了一个处理SOAP消息的框架,这个框架是极易扩展的,用户可以在每个服务或操作上扩展它。用户也可以在这个框架的基础上对不同的消息交换模型(Message Exchange Patterns)MEPs进行建模
(2)采用名为 AXIOM(AXIs Object Model)的新核心 XML 处理模型,利用新的XML解析器提供的灵活性按需构造对象模型。
(3)支持不同的消息交换模式。目前Axis2支持三种模式:In-Only、Robust-In和In-Out。In-Only消息交换模式只有SOAP请求,而不需要应答;Robust-In消息交换模式发送SOAP请求,只有在出错的情况下才返回应答;In-Out消息交换模式总是存在SOAP请求和应答。
(4)提供阻塞和非阻塞客户端 API。
(5)支持内置的 Web服务寻址 (WS-Addressing)
(6)灵活的数据绑定,可选择直接使用 AXIOM,ADB,或使用 XMLBeans、JiBX 或 JAXB 2.0 等专用数据绑定框架。
(7)新的部署模型,支持热部署,可以用或者不用WSDL来部署
(8)支持HTTP,SMTP,JMS,TCP传输协议。
(9)支持REST (Representational State Transfer)。
(10)提供了代码生成器,可以生成服务器端和客户端的代码
除了上述的这些功能,在内存和速度等性能方面也是Axis2的重点。Axis2的核心框架是构建在WSDL,SOAP和WS-Addressing上,其他的如JAX-RP,SAAJ和WS-Policy是在核心框架之上的层

六:WSDL
1:WSDL 的用途
创建服务时,通常的原因都是因为希望其他人使用此服务。为了使用服务,需要知道向服务发送什么信息、服务将发送回什么信息以及在何处能找到此服务。当然,可以将这些放入字处理文档中,但相比之下,如果此信息采用标准的、最好为人机均可读的格式,则要有用得多。
WSDL 就提供了这样的标准格式。除了不会造成混淆不清外,其主要优势是,由于 WSDL 是事实标准,且采用 XML 格式,因而可由计算机进行处理,便于自动创建客户机(甚至自动创建服务的框架)。
注意这里我们讲述的还是wsdl1.0,但是wsdl2.0已经出来了,2007年推出wsdl2.0。
Wsdl文档结构:
WSDL 文档是利用这些主要的元素来描述某个 web service 的:
<portType>: web service 执行的操作
<message>: web service 使用的消息
<types> :web service 使用的数据类型
<binding> :web service 使用的通信协议
一个 WSDL 文档的主要结构是类似这样的:
<definitions>
<types>
definition of types........
</types>
<message>
definition of a message....
</message>
<portType>
definition of a port.......
</portType>
<binding>
definition of a binding....
</binding>

</definitions>WSDL 文档可包含其它的元素,比如 extension 元素,以及一个 service 元素,此元素可把若干个 web services 的定义组合在一个单一的 WSDL 文档中。

1:<portType> 元素是最重要的 WSDL 元素。它可描述一个 web service、可被执行的操作,以及相关的消息。可以把 <portType> 元素比作传统编程语言中的一个函数库(或一个模块、或一个类)。
2:<message> 元素定义一个操作的数据元素。每个消息均由一个或多个部件组成。可以把这些部件比作传统编程语言中一个函数调用的参数。
3:<types> 元素定义 web service 使用的数据类型。为了最大程度的平台中立性,WSDL 使用 XML Schema 语法来定义数据类型。
4:<binding> 元素为每个端口定义消息格式和协议细节
WSDL的PortType:
操作类型:WSDL中定义了四种类型:
One-way :此操作可接受消息,但不会返回响应。
Request-response: 此操走可接受一个请求并会返回一个响应
Solicit-response :此操作可发送一个请求,并会等待一个响应。
Notification :此操作可发送一条消息,但不会等待响应。
1:一个 one-way 操作的例子:<message name="newTermValues">

<part name="term" type="xs:string"/>

<part name="value" type="xs:string"/>

</message>


<portType name="glossaryTerms">

<operation name="setTerm">

<input name="newTerm" message="newTermValues"/>

</operation>

</portType >
复制代码2:一个 request-response 操作的例子:<message name="getTermRequest">

<part name="term" type="xs:string"/>

</message>


<message name="getTermResponse">

<part name="value" type="xs:string"/>

</message>


<portType name="glossaryTerms">

<operation name="getTerm">

<input message="getTermRequest"/>

<output message="getTermResponse"/>

</operation>

</portType>
复制代码WSDL的绑定:
绑定到 SOAP
一个请求 - 响应操作的例子:<message name="getTermRequest">

<part name="term" type="xs:string" />

</message>


<message name="getTermResponse">

<part name="value" type="xs:string" />

</message>


<portType name="glossaryTerms">

<operation name="getTerm">

<input message="getTermRequest" />

<output message="getTermResponse" />

</operation>

</portType>


<binding type="glossaryTerms" name="b1">

<soap:binding style="document"

transport="http://schemas.xmlsoap.org/soap/http" />

<operation>

<soap:operation

soapAction="http://example.com/getTerm" />

<input>

<soap:body use="literal" />

</input>

<output>

<soap:body use="literal" />

</output>

</operation>

</binding>
复制代码binding 元素有两个属性 - name 属性和 type 属性。
name 属性定义 binding 的名称,而 type 属性指向用于 binding 的端口,在这个例子中是 "glossaryTerms" 端口。
soap:binding 元素有两个属性 - style 属性和 transport 属性。
style 属性可取值 "rpc" 或 "document"。在这个例子中我们使用 document。transport 属性定义了要使用的 SOAP 协议。在这个例子中我们使用 HTTP。

operation 元素定义了每个端口提供的操作符。
对于每个操作,相应的 SOAP 行为都需要被定义。同时您必须如何对输入和输出进行编码。在这个例子中我们使用了 "literal"。

例子:
下面是前面的HelloWorld生成的wsdl文件:<?xml version="1.0" encoding="UTF-8"?>


<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:ns1="http://org.apache.axis2/xsd" xmlns:ns="http://pojo.service.quickstart.samples" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" targetNamespace="http://pojo.service.quickstart.samples">

<wsdl:types>

<xs:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://pojo.service.quickstart.samples">

<xs:element name="getPrice">

<xs:complexType>

<xs:sequence>

<xs:element minOccurs="0" name="symbol" nillable="true" type="xs:string"/>

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element name="getPriceResponse">

<xs:complexType>

<xs:sequence>

<xs:element minOccurs="0" name="return" type="xs:double"/>

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element name="update">

<xs:complexType>

<xs:sequence>

<xs:element minOccurs="0" name="symbol" nillable="true" type="xs:string"/>

<xs:element minOccurs="0" name="price" type="xs:double"/>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:schema>

</wsdl:types>

<wsdl:message name="getPriceRequest">

<wsdl:part name="parameters" element="ns:getPrice"/>

</wsdl:message>

<wsdl:message name="getPriceResponse">

<wsdl:part name="parameters" element="ns:getPriceResponse"/>

</wsdl:message>

<wsdl:message name="updateRequest">

<wsdl:part name="parameters" element="ns:update"/>

</wsdl:message>

<wsdl:portType name="StockQuoteServicePortType">

<wsdl:operation name="getPrice">

<wsdl:input message="ns:getPriceRequest" wsaw:Action="urn:getPrice"/>

<wsdl:output message="ns:getPriceResponse" wsaw:Action="urn:getPriceResponse"/>

</wsdl:operation>

<wsdl:operation name="update">

<wsdl:input message="ns:updateRequest" wsaw:Action="urn:update"/>

</wsdl:operation>

</wsdl:portType>

<wsdl:binding name="StockQuoteServiceSoap11Binding" type="ns:StockQuoteServicePortType">

<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>

<wsdl:operation name="getPrice">

<soap:operation soapAction="urn:getPrice" style="document"/>

<wsdl:input>

<soap:body use="literal"/>

</wsdl:input>

<wsdl:output>

<soap:body use="literal"/>

</wsdl:output>

</wsdl:operation>

<wsdl:operation name="update">

<soap:operation soapAction="urn:update" style="document"/>

<wsdl:input>

<soap:body use="literal"/>

</wsdl:input>

</wsdl:operation>

</wsdl:binding>

<wsdl:binding name="StockQuoteServiceSoap12Binding" type="ns:StockQuoteServicePortType">

<soap12:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>

<wsdl:operation name="getPrice">

<soap12:operation soapAction="urn:getPrice" style="document"/>

<wsdl:input>

<soap12:body use="literal"/>

</wsdl:input>

<wsdl:output>

<soap12:body use="literal"/>

</wsdl:output>

</wsdl:operation>

<wsdl:operation name="update">

<soap12:operation soapAction="urn:update" style="document"/>

<wsdl:input>

<soap12:body use="literal"/>

</wsdl:input>

</wsdl:operation>

</wsdl:binding>

<wsdl:binding name="StockQuoteServiceHttpBinding" type="ns:StockQuoteServicePortType">

<http:binding verb="POST"/>

<wsdl:operation name="getPrice">

<http:operation location="StockQuoteService/getPrice"/>

<wsdl:input>

<mime:content type="text/xml" part="getPrice"/>

</wsdl:input>

<wsdl:output>

<mime:content type="text/xml" part="getPrice"/>

</wsdl:output>

</wsdl:operation>

<wsdl:operation name="update">

<http:operation location="StockQuoteService/update"/>

<wsdl:input>

<mime:content type="text/xml" part="update"/>

</wsdl:input>

</wsdl:operation>

</wsdl:binding>

<wsdl:service name="StockQuoteService">

<wsdl:port name="StockQuoteServiceHttpSoap11Endpoint" binding="ns:StockQuoteServiceSoap11Binding">

<soap:address location="http://localhost:8080/axis2/services/StockQuoteService"/>

</wsdl:port>

<wsdl:port name="StockQuoteServiceHttpSoap12Endpoint" binding="ns:StockQuoteServiceSoap12Binding">

<soap12:address location="http://localhost:8080/axis2/services/StockQuoteService"/>

</wsdl:port>

<wsdl:port name="StockQuoteServiceHttpEndpoint" binding="ns:StockQuoteServiceHttpBinding">

<http:address location="http://localhost:8080/axis2/services/StockQuoteService"/>

</wsdl:port>

</wsdl:service>

</wsdl:definitions>
=====================================================================
七:SOAP
1:简单的soap示例<?xml version='1.0' ?>

<env:Envelope xmlns:env="ht tp://ww w.w3.org/2003/05/SOAP-envelope">

<env:Header>

</env:Header>

<env:Body>

<cms:getNumberOfArticles xmlns:cms="ht tp://w ww.daily-moon.com/cms">

<cms:category>classifieds</cms:category>

<cms:subcategory>forsale</cms:subcategory>

</cms:getNumberOfArticles>

</env:Body>

</env:Envelope>
复制代码2:soap信封
Web 服务消息的基本单元是实际的 SOAP 信封。这是包含处理消息所必需的所有信息的 XML 文档<?xml version='1.0' ?>

<env:Envelope xmlns:env="ht tp://w ww.w3.org/2003/05/SOAP-envelope">

<env:Header>

</env:Header>

<env:Body>

</env:Body>

</env:Envelope>[code]


3:soap的Header

SOAP 消息中的 Header 用于提供有关消息本身的信息,与用于应用程序的信息相对。例如,Header 可以包括路由信息

[code]

<?xml version='1.0' ?>

<env:Envelope xmlns:env="ht tp://ww w.w3.org/2003/05/SOAP-envelope">

<env:Header>

<wsa:ReplyTo xmlns:wsa=

"ht tp://schemas.xmlSOAP.org/ws/2004/08/addressing">

<wsa:Address>

ht tp://schemas.xmlSOAP.org/ws/2004/08/addressing/role/anonymous

</wsa:Address>

</wsa:ReplyTo>

<wsa:From>

<wsa:Address>

h ttp://localhost:8080/axis2/services/MyService</wsa:Address>

</wsa:From>

<wsa:MessageID>ECE5B3F187F29D28BC11433905662036</wsa:MessageID>

</env:Header>

<env:Body>

</env:Body>

</env:Envelope>
复制代码本例中有一个 WS-Addressing 元素,其中包含有关消息将送达何处以及应将应答送达何处的信息。Header 可包含关于消息本身的所有类型的消息。事实上,SOAP 规范中使用了大量篇幅说明哪些元素可以放入 Header以及应由“SOAP 中间层”如何对其进行处理。也就是说,SOAP 规范并不假定消息将直接从一个点传递到另一个点(从客户机到服务器)。
规范考虑了 SOAP 消息在送达最终目的地的过程中可能实际由多个中间层处理的情况,很清楚地说明了中间层应如何对待在 Header 中找到的信息。不过,对此的讨论不在本教程的范围之内。因此,目前只要知道 Header 可以提供许许多多的功能(如果您需要)即可。

4:soap的Body
发送 SOAP 消息时,都是有目的性的。您在尝试告诉接收者执行某种操作,或尝试向服务器传递相关信息。此信息称为“有效负载”。有效负载位于 Envelope 的 Body 中。它还具有自己的命名空间,在本例中其命名空间与内容管理系统对应。在此情况下,可以完全随意地选择命名空间。只需要与 SOAP 命名空间相异即可<?xml version='1.0' ?>

<env:Envelope xmlns:env="ht tp://w ww.w3.org/2003/05/SOAP-envelope">

<env:Header>

...

</env:Header>

<env:Body>

<cms:addArticle xmlns:cms="ht tp://w ww.daily-moon.com/cms">

<cms:category>classifieds</category>

<cms:subcategory>forsale</cms:subcategory>

<cms:articleHeadline></cms:articleHeadline>

<cms:articleText>Vintage 1963 T-Bird. Less than 300 miles.

Driven by my daughter until I took it away. Serious inquires only.

555-3264 after 7 PM.</cms:articleText>

</cms:addArticle>

</env:Body>

</env:Envelope>
复制代码在此例中,有效负载很简单,其中包含将文章添加到 Daily Moon 的内容管理系统的指令。

4:样式和编码
这个主要是在WSDL(Web服务描述语言)中讲述。
简单来说,有两种不同的主流 SOAP消息编程样式。第一种是 RPC 样式,基于使用 SOAP 消息创建远程过程调用(Remote Procedure Call)的概念。在此样式中,基本思路是在向服务器发送命令(如“添加文章”),并将该命令的参数(如要添加的文章和应该添加到的类别)作为整个方法的子元素包含在其中<env:Envelope xmlns:env="ht tp://w ww.w3.org/2003/05/SOAP-envelope">

<env:Header>
复制代码RPC 样式的替代方法将数据直接作为 SOAP 体的内容处理,并在应用服务器对消息进行路由的信息中包含有关其所属的过程或函数的信息<env:Envelope xmlns:env="ht tp://ww w.w3.org/2003/05/SOAP-envelope">

<env:Header>

</env:Header>

<env:Body>

<cms:addArticle xmlns:cms="h ttp://w ww.daily-moon.com/cms">

<cms:category>classifieds</category>

<cms:subcategory>forsale</cms:subcategory>

<cms:articleHeadline></cms:articleHeadline>

<cms:articleText>Vintage 1963 T-Bird. Less than 300

miles. Driven by my daughter until I took it away.

Serious inquires only. 555-3264 after 7 PM.</cms:articleText>

</cms:addArticle>

</env:Body>

</env:Envelope>
复制代码第二个样式称为 document/literal 样式,即将相应的数据直接添加到消息中<?xml version='1.0' ?>

<env:Envelope xmlns:env="ht tp://w ww.w3.org/2003/05/SOAP-envelope">

<env:Header>

</env:Header>

<env:Body>

<category>classifieds</category>

<subcategory>forsale</subcategory>

<articleHeadline></articleHeadline>

<articleText>Vintage 1963 T-Bird. Less than 300 miles.

Driven by my daughter until I took it away. Serious

inquires only. 555-3264 after 7 PM.</articleText>

</env:Body>

</env:Envelope>
复制代码在这种情况下,消息本身并不包含有关数据所提交到的进程的信息,此工作由路由软件进行。例如,所有对特定 URL 或端点的调用都可能指向特定的操作。另外,从技术上讲,可以使用 document/encoded 样式,但目前还没有人这样做,因此可以将其忽略。
5:消息交换模式
发送消息,就实质而言,只有两个选择:
请求/响应:在请求/响应模式种,以 SOAP 消息的形式发送请求,然后直接等待发送回响应。请求可以为同步的,也可以是异步的。
单向消息传递:这种情况也称为“Fire and Forget”方法,发送请求但并不等待响应。可以在仅传递信息时或并不关心接收者对此如何响应时使用此方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值