Web Service简介及开发实例

Web Service简介及开发实例 
作者:岳乡成 
本文档实例Dome基于的技术是:JSF  +  Jboss-seam-2.1.1.GA.  +  Jboss 4.2.3 GA  +  EJB 3.0  +  Jboss ESB  +  My-SQL-5.0.8  +  JDK 1.6。由于实例工程太大,不能放博客上,如有需要联系我,MSN:xiangcheng.yue@163.com。 
QQ号码:827650367。 
一、 Web Service的简介 
1、 什么是Web Service 
Web services是建立可互操作的分布式应用程序的新平台。 
Web service平台是一套标准,它定义了应用程序如何在Web上实现互操作性。你可以用任何你喜欢的语言,在任何你喜欢的平台上写Web service ,只要我们可以通过Web service标准对这些服务进行查询和访问。 
Web service平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,Web service平台必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。 
基础的 Web Services 平台是 XML + HTTP。 
HTTP 协议是最常用的因特网协议。 
XML 提供了一种可用于不同的平台和编程语言之间的语言。 
Web services 平台是简单的可共同操作的消息收发框架。它仍然缺少许多诸如安全和路由等重要的特性。但是,一旦 SOAP 变得更加高级,这些事项就会得到解决。 
Web services 有望使应用程序更加容易通信。 
★ Web services 把 Web 应用程序提升到了另外一个层面 
通过使用 Web services,您的应用程序可向全世界发布功能或消息。 
Web services 使用 XML 来编解码数据,并使用 SOAP 借由开放的协议来传输数据。 
通过 Web services,您的会计部门的 Win 2k 服务器可与 IT 供应商的 UNIX 服务器进行连接。 
★ Web services 有两种类型的应用 
可重复使用的应用程序组件 
有一些功能是不同的应用程序常常会用到的。那么为什么要周而复始地开发它们呢? 
Web services 可以把应用程序组件作为服务来提供,比如汇率转换、天气预报或者甚至是语言翻译等等。 
比较理想的情况是,每种应用程序组件只有一个最优秀的版本,这样任何人都可以在其应用程序中使用它。 
连接现有的软件 
通过为不同的应用程序提供一种链接其数据的途径,Web services有助于解决协同工作的问题。 
通过使用 Web services,您可以在不同的应用程序与平台之间来交换数据。 
★ Web Services 拥有两种基本的元素。 
它们是:SOAP及WSDL 
                       (1)什么是 SOAP? 
 SOAP 指简易对象访问协议 
 SOAP 是一种通信协议 
 SOAP 用于应用程序之间的通信 
 SOAP 是一种用于发送消息的格式 
 SOAP 被设计用来通过因特网进行通信 
 SOAP 独立于平台 
 SOAP 独立于语言 
 SOAP 基于 XML 
 SOAP 很简单并可扩展 
 SOAP 允许您绕过防火墙 
 SOAP 将作为 W3C 标准来发展 
                   (2)什么是 WSDL? 
WSDL 是基于 XML 的用于描述 Web Services 以及如何访问 Web Services 的语言。 
 WSDL 指网络服务描述语言 
 WSDL 使用 XML 编写 
 WSDL 是一种 XML 文档 
 WSDL 用于描述网络服务 
 WSDL 也可用于定位网络服务 
 WSDL 还不是 W3C 标准 
2、 什么是JWS 
         JWS(Java Web Service)是Java应用平台上专门用于开发Web服务系统及面向服务系统的产品,它的最新版本是2.0,Java EE 5和Java SE 6都对JWS 2.0提供支持。 
         在JWS 2.0,Java定义了一系列新的标准,JMS本身也包含了一些工具,如JAX-WS 2.0,JAXB 2.0,JAXP 1.4,SAAJ 1.3以及WS-Metadata等。 
         面向服务的系统往往由多个具有不同的子功能的独立组件构成,通过他们之间良好的相互协作,可以实现复杂的需求。面向服务系统的这个特点,要求独立组建之间有公共的接口,这些用于交换数据的公共接口有良好的定义。由于要实现组件之间的数据通信,这些具有良好定义的接口就必须要被别的组件识别并正确理解,才能实现协作。 
         在定义了公共的接口后,还存在具体的数据交换问题,即双方需要遵循一个共同的数据交换标准,这个数据交换标准称为协议。要在独立的组件之间进行通信,需要一系列标准来严格规定数据通信的格式和规则。 
         Web Service的出现解决了上述问题,利用WSDL定义统一的接口格式,用SOAP消息统一输入/输出参数的统一格式。SOAP消息可以由多种途径传递,比如,通过HTTP,SMTP及JMS协议传递。以HTTP为例,在服务使用端,WSDL的接口定义可以通过HTTP-GET请求获取,而SOAP应答消息及回复消息的传输可以通过HTTP-POST请求来实现。这样,基于WSDL和SOAP消息机制就可以满足面向服务应用系统开发的需求。 
         Web Service平台架构主要由三部分构成:序列化及反序列化子系统、调用子系统及发布子系统。这三个子系统并不受具体语言的限制,也不受平台和框架的限制,无论使用Java语言还是.NET语言,无论使用Axis平台还是JWS来开发web服务,都会涉及这三个最基本的功能模块。 
(1)序列化及反序列化 
             在JMS中将一个Java对象转化为一个XML元素的过程,称为序列化。反之将一个XML元素转化为相应的Java 
对象的过程,称为反序列化。序列化和反序列化的过程要依赖于Java类和XML-Scheme之间的映射关系,JWS有独立 
的序列化和反序列化子系统用来负责完成这些映射及转化。 
在web服务的客户端,序列化过程将参数转化为xml结点,进而封装成Soap请求消息,发送至服务器端的web服务端点。获得返回值时,反序列化过程启动,它将返回值从SOAP消息中指定的xml结点中取出,然后将它转化为客户端相对应的Java对象。 
转化规则的定义在JWS中是通过JAXB的注释来完成的。新版的JAXB简化了绑定规则的描述,它允许直接将规则以注解的形式写入Java类。 
例如: 
@Entity 
@Name("hotel") 
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER) 
@XmlType(name="", propOrder = { 
    "id", 
    "img", 
    "name", 
    "address", 
    "city", 
    "state", 
    "zip", 
    "country", 
    "price", 
    "ipAddress" 
}) 
@XmlRootElement(name="Hotel") 
public class Hotel implements Serializable 

   private Long id; 
   private String img; 
   private String name; 
   private String address; 
   private String city; 
   private String state; 
   private String zip; 
   private String country; 
   private BigDecimal price; 
   private String ipAddress = "192.168.1.112"; 

@Id @GeneratedValue 
   public Long getId() 
   { 
      return id; 
   } 
   public void setId(Long id) 
   { 
      this.id = id; 
   } 
   
@Length(max = 50) 
@NotNull 
public String getImg() { 
return img; 


public void setImg(String img) { 
this.img = img; 


@Length(max=50) @NotNull 
   public String getName() 
   { 
      return name; 
   } 
   public void setName(String name) 
   { 
      this.name = name; 
   } 
   
   @Length(max=100) @NotNull 
   public String getAddress() 
   { 
      return address; 
   } 
   public void setAddress(String address) 
   { 
      this.address = address; 
   } 
   
   @Length(max=40) @NotNull 
   public String getCity() 
   { 
      return city; 
   } 
   public void setCity(String city) 
   { 
      this.city = city; 
   } 
   
   @Length(min=4, max=6) @NotNull 
   public String getZip() 
   { 
      return zip; 
   } 
   public void setZip(String zip) 
   { 
      this.zip = zip; 
   } 
   
   @Length(min=2, max=10) @NotNull 
   public String getState() 
   { 
      return state; 
   } 
   public void setState(String state) 
   { 
      this.state = state; 
   } 
   
   @Length(min=2, max=40) @NotNull 
   public String getCountry() 
   { 
      return country; 
   } 
   public void setCountry(String country) 
   { 
      this.country = country; 
   } 

   @Column(precision=6, scale=2) 
   public BigDecimal getPrice() 
   { 
      return price; 
   } 
   public void setPrice(BigDecimal price) 
   { 
      this.price = price; 
   } 
   @Transient 
   public String getIpAddress() { 
   return ipAddress; 
   } 
   public void setIpAddress(String ipAddress) { 
   this.ipAddress = ipAddress; 
   } 
   @Override 
   public String toString() 
   { 
      return "Hotel(" + name + "," + address + "," + city + "," + zip + ")"; 
   } 

     (2)服务调用的过程 
          在面向服务的分布式系统中,一般将传统的客户服务器框架中的客户端Client称为服务的消费者,而将服务器端称为服务的提供者。 
          按照现在的web服务标准,一个服务被调用时,在服务的提供端大致的处理过程如下: 
          ①接受并预处理SOAP请求消息,例如效验,处理SOAP消息报文头。 
②从消息中获取该消息希望调用的接口和具体操作。 
③利用web服务提供的支持,找到具体的实现对象,并调用该对象的接口。这个对象可以由不同的语言实现。JWS支持从WSDL到Java的映射,可以通过WSDL找到与它相对应的Java服务端点实现类。 
④使用序列化工具的反序列化过程,将SOAP请求消息中的服务请求参数取出,传递给步骤3中的目标对象的相应函数。 
⑤目标Java对象执行相应的操作,将计算的结果以对象形式返回。 
⑥使用序列化工具的序列化过程,根据wsdl中的定义,将步骤5中的结果对象序列化成XML元素,并封装到SOAP回复消 
息中。 
⑦将步骤6中的SOAP回复消息发送回服务调用端。 
与之对应的,在服务使用端的调用过程如下: 
①首先创建服务端点接口对象SEI(Service Endpoint Interface),在Web服务客户端一般都会有相应的工厂类完成SEI对象的实例化。在JWS中,SEI对象一般是由Java代理来实现的。 
②客户端通过SEI调用其中封装的web服务接口。 
③利用序列化工具的序列化过程,根据WSDL的定义,将客户端的调用接口的参数转化成XML元素,再将该元素封装在SOAP请求消息里。 
④在同步模式下,在SOAP请求消息发出后,客户端会等待SOAP应答消息;异步模式下,客户端顺序执行后续代码,直到通过监听器接收到SOAP请求消息里。 
⑤解析从服务器端获得的SOAP应答消息,使用序列化工具中的反序列化过程,将SOAP应答消息中的数据转化成客户端对象,该对象的值就是被调用服务的返回值。 
   (3)web服务的发布 
          以JMS为例,web服务发布系统的主要功能 
          ①以URL的形式公开被发布的WEB服务的WSDL文件,并绑定SOAP请求消息和Java目标类。 
          ②发布JAVA目标文件(例如Java Object文件,WAR文件,JAR文件及相关配置文件等)。 
          ③配置序列化和反序列化子系统。 
④配置web服务端点监听器和SOAP消息预处理进程。 
二、 web service的几种开发及调用方式 
(1)用EJB容器模型开发Web Service,用wsimport工具生成辅助类进行调用。 
实例如下(实例中应用的EJB容器为Jboss,所有代码是基于JBoss Seam架构来编写的): 
①服务的提供者端<提供服务端的代码>: 
服务的提供者端的任务是暴露一个web service以供外部使用,本例的web service是通过JavaEE平台提供的@java.jws.WebService注解来实现的。 
以下代码的路径是VSS上   前期调查文件夹下SOA\web service\用EJB容器模型开发Web Service,用wsimport工具生成辅助类进行调用\tele\book\src\org\jboss\seam\example\booking\ws\impl,用EJB容器模型开发Web Service,用wsimport工具生成辅助类进行调用文件夹下包括两个文件夹:local,tele。tele文件夹下放服务的提供者端的工程和打好的EAR包。local文件夹下放服务的消费者端的工程和打好的EAR包。 
package org.jboss.seam.example.booking.ws.impl; 
import static org.jboss.seam.ScopeType.SESSION; 

import java.util.ArrayList; 
import java.util.List; 

import javax.ejb.Remote; 
import javax.ejb.Stateless; 
import javax.jws.WebService; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 


import org.jboss.seam.annotations.Out; 
import org.jboss.seam.example.booking.Hotel; 
import org.jboss.seam.example.booking.User; 
import org.jboss.seam.example.booking.ws.HotelObject; 



@Stateless 
/**实例是使用了两个JBoss应用服务器,该代码是服务提供者端的代码,所以使用了@Remote注解,实现远程接口。。*/ 
@Remote (HotelObject.class) 
@WebService 
public class HotelObjectImpl implements HotelObject { 
@PersistenceContext 
private EntityManager em; 
   
    public List<Hotel> getHotelObject(String hotelName) { 
List<Hotel> results = em.createQuery("select h from Hotel h where h.name like"+ " '%"+hotelName+"%'") 
         .getResultList(); 
if(results.size()>0){ 
return results; 

        return null; 
    } 
    
    public String updateHotelObject(Hotel h){ 
    try{ 
    System.out.println(h); 
    System.out.println(h.getName()); 
    em.merge(h); 
    }catch(Exception e ){ 
    e.printStackTrace(); 
    return "error"; 
    } 
return "success";    
    } 

上面的类继承了一个接口,接口类的路径是:VSS上前期调查文件夹下SOA\web service\用EJB容器模型开发Web Service,用wsimport工具生成辅助类进行调用\tele\book\src\org\jboss\seam\example\booking\ws。 
package org.jboss.seam.example.booking.ws; 

import java.util.List; 
import org.jboss.seam.example.booking.Hotel; 

public interface HotelObject { 
    public List<Hotel> getHotelObject(String hotelName); 
    public String updateHotelObject(Hotel h); 

上面的@WebService注解指名这是一个Web Service。打开Ant文件,执行desploy业务。部署完成后,通过JBoss管理平台查看刚才部署的Web Service,输入http://localhost:8080/jbossws/进入JBoss Web Service的查看界面,可以看到View a list of deployed services链接,单击View a list of deployed services链接后,可以查看已经发布的Web Service的URL地址,单击该地址就可以查看生成的WSDL文。 
          这就是服务提供端的主要代码,先用注解定义了一个web服务,这个web服务提供了两个方法,这两个方法可以接收从远程传过来的参数。服务的消费着可以通过上面的URL地址访问该服务。 
(2)服务的消费者端<使用服务端的代码> 
要调用web服务,我们可以通过好多方式,其中的一种方式是通过wsimport工具(在JDK1.6中,Sun已经为用户提供了wsimport工具)生成辅助类来调用web服务。 
     在VSS上web Service文件夹中有一个叫做jaxws-ri的文件包里面包含了wsimport工具,本例的辅助类就是用它直接生成的。要用wsimport工具生成辅助类,首先要在dos环境下打开wsimport工具所在的目录,比如wsimport工具的本地路径: 
     
Dos下打开wsimport工具所在的路径。 
         
     在wsimport路径下输入如下内容: 
     wsimport.bat -keep -d  ../build/classes/client  http://192.168.1.106:8080/WsHelloWorld/HelloWorldBean?wsdl
     其中../build/classes/client表示生成的辅助类存放的路径,http://192.168.1.106:8080/WsHelloWorld/HelloWorldBean?wsdl表示要调用的web服务的WSDL文地址,其中192.168.1.106表示服务提供端的IP地址。 
     生成的辅助类如下图所示: 
        
把生成的辅助java类拷贝到我们的工程中 
然后实例化一个生成的辅助类的对象,通过该对象调用服务提供者提供的方法<即getHotelObjec()>,去检索符合条件的记录。 
该类位于VSS上如下路径下; 
SOA\Web Servic\用EJB容器模型开发Web Service,用wsimport工具生成辅助类进行调用\local\book\src\org\jboss\seam\example\booking 
package org.jboss.seam.example.booking; 

import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.List; 

import javax.ejb.Remove; 
import javax.ejb.Stateful; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 

import org.jboss.seam.ScopeType; 
import org.jboss.seam.annotations.Factory; 
import org.jboss.seam.annotations.Name; 
import org.jboss.seam.annotations.Scope; 
import org.jboss.seam.annotations.datamodel.DataModel; 
import org.jboss.seam.annotations.security.Restrict; 
import org.jboss.seam.example.booking.ws.impl.Hotel; 
import org.jboss.seam.example.booking.ws.impl.HotelObjectImpl; 
import org.jboss.seam.example.booking.ws.impl.HotelObjectImplService; 

@Stateful 
@Name("teledataHotelSearch") 
@Scope(ScopeType.SESSION) 
@Restrict("#{identity.loggedIn}") 
public class TeledateHotelSearchingAction implements TeledateHotelSearching 

    @PersistenceContext 
    private EntityManager em; 
    
    private String searchString; 
   
    @DataModel 
    private List<Hotel> telehotels; 
   
    public void find() 
    { 
        queryHotels(); 
    } 

    
    private void queryHotels() { 
          /**实例化生成的辅助类的对象,通过该对象调用远程的web service。*/ 
HotelObjectImplService service; 
        try 
        { 
          service = new HotelObjectImplService(); 
          HotelObjectImpl hotelObjectImpl = service.getHotelObjectImplPort(); 
          List<Hotel> result = hotelObjectImpl.getHotelObject(this.getSearchString()); 
          System.out.println(result.size()); 
          if(result.size()>0){ 
          telehotels = result; 
          }else{ 
          telehotels =null; 
          } 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
        }       
    public String getSearchString() 
    { 
       return searchString; 
    } 
    
    public void setSearchString(String searchString) 
    { 
       this.searchString = searchString; 
    } 
   
   @Remove 
   public void destroy() {} 


@Override 
public void cancel() { 
// TODO 自动生成的方法存根 



还有一个类是为了保存修改了的服务提供者(即远程的Web Service)返回的Hotel对象,必须调用服务提供者提供的保存Hotel对象的方法<即updateHotelObject ()>。 
package org.jboss.seam.example.booking; 

import static javax.persistence.PersistenceContextType.EXTENDED; 

import java.util.Calendar; 

import javax.ejb.Remove; 
import javax.ejb.Stateful; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 

import org.jboss.seam.annotations.Begin; 
import org.jboss.seam.annotations.End; 
import org.jboss.seam.annotations.In; 
import org.jboss.seam.annotations.Logger; 
import org.jboss.seam.annotations.Name; 
import org.jboss.seam.annotations.Out; 
import org.jboss.seam.annotations.security.Restrict; 
import org.jboss.seam.core.Events; 
import org.jboss.seam.example.booking.ws.impl.HotelObjectImpl; 
import org.jboss.seam.example.booking.ws.impl.HotelObjectImplService; 
import org.jboss.seam.faces.FacesMessages; 
import org.jboss.seam.log.Log; 

@Stateful 
@Name("saveTeteDataHotel") 
@Restrict("#{identity.loggedIn}") 
public class SaveTeteDataHotelAction implements SaveTeteDataHotel 

   
   @Begin 
   public void seveHotel(Hotel hotel) 
   { 
          HotelObjectImplService service; 
        try 
        { 
          service = new HotelObjectImplService(); 
          HotelObjectImpl hotelObjectImpl = service.getHotelObjectImplPort(); 
          String result = hotelObjectImpl.updateHotelObject(this.conversionHotel(hotel)); 
              System.out.println(result); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
   } 
   
   public org.jboss.seam.example.booking.ws.impl.Hotel conversionHotel(Hotel hotel){ 
   org.jboss.seam.example.booking.ws.impl.Hotel h = new org.jboss.seam.example.booking.ws.impl.Hotel(); 
   h.setAddress(hotel.getAddress()); 
   h.setCity(hotel.getCity()); 
   h.setCountry(hotel.getCountry()); 
   h.setId(hotel.getId()); 
   h.setImg(hotel.getImg()); 
   h.setName(hotel.getName()); 
   h.setPrice(hotel.getPrice()); 
   h.setState(hotel.getState()); 
   h.setZip(hotel.getZip()); 
   return h; 
   } 
   
   @End 
   public void cancel() {} 
   
   @Remove 
   public void destroy() {} 

以上类中有一点值得注意,就是服务消费者中的Hotel对象不能直接用于保存,必须经过转换,转换成生成的辅助类中的org.jboss.seam.example.booking.ws.impl.Hotel类,才能进行保存,上面的conversionHotel(Hotel hotel)方法就是为了进行该转换。 
以上就是该种使用web service的几个重要的点,通过以上各步就可以访问远程发布的web服务,完整的过程请参考Vss上Dome工程。 
(2)利用SAAJ协议动态生成SOAP消息访问服务提供者端的web service。 
第一种方式是利用wsimpot工具来生成辅助类来实现对服务提供者端的web service的访问,由于生成的辅助类特别多,而且灵活性差。下面我们介绍用SAAJ协议动态生成SOAP消息来访问服务提供者端的web service。 
①服务提供者端的代码 
该类的在VSS上: 
EHR\SOA\SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\ws\impl下 
package org.jboss.seam.example.booking.ws.impl; 
import static org.jboss.seam.ScopeType.SESSION; 

import java.util.ArrayList; 
import java.util.List; 

import javax.ejb.Remote; 
import javax.ejb.Stateless; 
import javax.jws.WebResult; 
import javax.jws.WebService; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 


import org.jboss.seam.annotations.Out; 
import org.jboss.seam.example.booking.Hotel; 
import org.jboss.seam.example.booking.User; 
import org.jboss.seam.example.booking.ws.HotelObject; 



@Stateless 
@Remote (HotelObject.class) 
@WebService(name = "HotelObject", targetNamespace = "http://tower/ehr_DEV") 
public class HotelObjectImpl implements HotelObject { 
@PersistenceContext 
private EntityManager em; 
@WebResult(name="Hotel")   
    public List<Hotel> getHotelObject(String hotelName) { 
List<Hotel> results = em.createQuery("select h from Hotel h where h.name like"+ " '%"+hotelName+"%'") 
         .getResultList(); 
if(results.size()>0){ 
return results; 

        return null; 
    } 
    
    public String updateHotelObject(Hotel h){ 
    try{ 
    System.out.println(h); 
    System.out.println(h.getName()); 
    em.merge(h); 
    }catch(Exception e ){ 
    e.printStackTrace(); 
    return "error"; 
    } 
return "success";    
    } 

该web服务提供了两个方法,getHotelObject(String hotelName)方法接收远程的的参数,通过该参数查找符合条件的Hotel对象;updateHotelObject(Hotel h)方法用来保存修改后的对象。 
②服务调用者端的代码 
该类的在VSS上: 
EHR\SOA\ SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\soa\esb\impl下 
本例使用了Jboss ESB,其实服务调用端首先发送请求到Jboss ESB服务器上,Jboss ESB服务器上也定义了一些web服务,来接受这些请求,接受到请求消息后,对消息进行进一步的加工或者直接把消息转发给真正的消息提供者端。 
下面的代码展示了服务消费端如何通过ESB服务器,去调用服务提供者端提供的服务。 
package org.jboss.seam.example.booking.soa.esb.impl; 

import java.io.ByteArrayInputStream; 
import java.io.InputStream; 
import java.util.List; 

import javax.ejb.Remove; 
import javax.ejb.Stateful; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 
import javax.xml.bind.JAXBContext; 
import javax.xml.bind.JAXBException; 
import javax.xml.bind.Unmarshaller; 
import javax.xml.soap.SOAPMessage; 

import org.jboss.seam.ScopeType; 
import org.jboss.seam.annotations.Name; 
import org.jboss.seam.annotations.Scope; 
import org.jboss.seam.annotations.datamodel.DataModel; 
import org.jboss.seam.annotations.security.Restrict; 
import org.jboss.seam.example.booking.Hotel; 
import org.jboss.seam.example.booking.commom.collectionbinder.ListHotel; 
import org.jboss.seam.example.booking.commom.esb.BuildSoapMessage; 
import org.jboss.seam.example.booking.commom.esb.SendSoapMessageToEsb; 
import org.jboss.seam.example.booking.commom.esb.impl.BuildSoapMessageImpl; 
import org.jboss.seam.example.booking.commom.esb.impl.SendSoapMessageToEsbImpl; 
import org.jboss.seam.example.booking.commom.formattransform.TransformMessageFormat; 
import org.jboss.seam.example.booking.commom.formattransform.TransformString; 
import org.jboss.seam.example.booking.commom.formattransform.impl.TransformMessageFormatImpl; 
import org.jboss.seam.example.booking.commom.formattransform.impl.TransformStringImpl; 
import org.jboss.seam.example.booking.soa.esb.TeledateHotelSearching; 
import org.w3c.dom.Document; 
import org.w3c.dom.Element; 
import org.w3c.dom.NodeList; 



@Stateful 
@Name("teledataHotelSearch") 
@Scope(ScopeType.SESSION) 
@Restrict("#{identity.loggedIn}") 
public class TeledateHotelSearchingImpl implements TeledateHotelSearching 

    @PersistenceContext 
    private EntityManager em; 
    
    private String searchString; 
   
    @DataModel 
    private List<Hotel> telehotels; 

   
    public void find() 
    { 
    getHotelObject(this.getSearchString()); 
    } 
    

public void getHotelObject(String searchString) { 
    try{     
    BuildSoapMessage bsm = new BuildSoapMessageImpl(); 
    SOAPMessage sm = bsm.getMessage("sayGoodbye", "http://webservice_producer/goodbyeworld", searchString); 
    TransformMessageFormat tmf = new TransformMessageFormatImpl(); 
    String stringSOAPMessage = tmf.transformToString(sm); 
    SendSoapMessageToEsb smtEsb = new SendSoapMessageToEsbImpl(); 
    Object o = smtEsb.sendMessageToJBRListener("http", "8765", stringSOAPMessage); 
    
    System.out.println("wo de ma ya!!!"+o.toString()); 
    
    TransformString tfs = new TransformStringImpl(); 
    Document   doc   =  tfs.transformStringToDocument(o.toString()); 
NodeList   nodeList  =  doc.getElementsByTagName("ListHotel"); 
    Element   element   =  (Element)nodeList.item(0); 
    String returnContent = element.getTextContent(); 
    if(returnContent.length()>0){ 
    String returnContentAnd = "<ListHotel>"+returnContent+"</ListHotel>"; 
    InputStream inputStream = new ByteArrayInputStream(returnContentAnd.getBytes()); 
            JAXBContext context = JAXBContext.newInstance(ListHotel.class); 
            try { 
                 Unmarshaller um = context.createUnmarshaller(); 
                 ListHotel hl = (ListHotel)um.unmarshal(inputStream); 
                 System.out.println("I'm King!"+hl.getElements());                  
                 telehotels = hl.getElements(); 
            } catch (JAXBException e) { 
                 e.printStackTrace(); 
            } 
    }else{ 
    telehotels = null; 
    } 
    }catch(Exception e){ 
    e.printStackTrace(); 
    telehotels = null; 
    } catch (Throwable e) { 
e.printStackTrace(); 


  } 
        
    

    public String getSearchString() 
    { 
       return searchString; 
    } 
    
    public void setSearchString(String searchString) 
    { 
       this.searchString = searchString; 
    } 
   
   @Remove 
   public void destroy() {} 


@Override 
public void cancel() { 
searchString = ""; 
telehotels = null; 


在getHotelObject(String searchString)方法中,首先通过BuildSoapMessage类提供的getMessage()方法生成要发送的SOAP消息。(调用的代码如下:) 
BuildSoapMessage bsm = new BuildSoapMessageImpl(); 
SOAPMessage sm = bsm.getMessage("sayGoodbye", "http://webservice_producer/goodbyeworld", searchString); 
BuildSoapMessage类及代码如下: 
该类的在vss上的路径如下: 
SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\commom\esb\impl 
package org.jboss.seam.example.booking.commom.esb.impl; 

import java.util.HashMap; 
import java.util.Map; 

import javax.xml.soap.MessageFactory; 
import javax.xml.soap.Name; 
import javax.xml.soap.SOAPBody; 
import javax.xml.soap.SOAPConnection; 
import javax.xml.soap.SOAPConnectionFactory; 
import javax.xml.soap.SOAPConstants; 
import javax.xml.soap.SOAPElement; 
import javax.xml.soap.SOAPEnvelope; 
import javax.xml.soap.SOAPException; 
import javax.xml.soap.SOAPFactory; 
import javax.xml.soap.SOAPMessage; 
import javax.xml.soap.SOAPPart; 

import org.jboss.seam.example.booking.commom.esb.BuildSoapMessage; 



public class BuildSoapMessageImpl implements BuildSoapMessage{ 

public static final SOAPConnection getSOAPConnection(){ 
SOAPConnectionFactory soapConnFactory; 
SOAPConnection connection; 
try { 
soapConnFactory = SOAPConnectionFactory.newInstance(); 
connection = soapConnFactory.createConnection(); 
return connection; 
} catch (UnsupportedOperationException e) { 
e.printStackTrace(); 
return null; 
} catch (SOAPException e) { 
e.printStackTrace(); 
return null; 




public static final SOAPMessage getSOAPMessage(){ 
MessageFactory messageFactory; 
SOAPMessage message; 
try { 
messageFactory = MessageFactory.newInstance(); 
message = messageFactory.createMessage(); 
return message; 
} catch (SOAPException e) { 
e.printStackTrace(); 
return null; 




public Name  getName(String param){ 
SOAPFactory soapFactory; 
try { 
soapFactory = SOAPFactory.newInstance(); 
Name name = soapFactory.createName(param); //"arg0" 
return name; 
} catch (SOAPException e) { 
e.printStackTrace(); 
return null; 




public SOAPElement getBodyElement(SOAPMessage message,String param1,String param2,String param3){ 
SOAPPart soapPart =     message.getSOAPPart(); 
        SOAPEnvelope envelope; 
        SOAPBody body; 
        SOAPElement bodyElement; 
try { 
envelope = soapPart.getEnvelope(); 
body = envelope.getBody(); 
bodyElement = body.addChildElement(envelope.createName(param1,//"sayHello" , 
param1,//"ns1", 
param1));//"http://webservice_consumer1/helloworld")); 
bodyElement.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING); 
return bodyElement; 
} catch (SOAPException e) { 
e.printStackTrace(); 
return null; 




public SOAPElement addTextNode(SOAPMessage message,String param1,String param2,String param3){ 
SOAPPart soapPart =     message.getSOAPPart(); 
        SOAPEnvelope envelope; 
        SOAPBody body; 
        SOAPElement bodyElement; 
try { 
envelope = soapPart.getEnvelope(); 
body = envelope.getBody(); 
bodyElement = body.addChildElement(envelope.createName(param1,//"sayHello" , 
param1,//"ns1", 
param1));//"http://webservice_consumer1/helloworld")); 
bodyElement.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING); 
return bodyElement; 
} catch (SOAPException e) { 
e.printStackTrace(); 
return null; 





public SOAPMessage getMessage(String methodName,String targetNamespace,String messageContext) throws SOAPException,Exception{ 

        //传送参数需要创建Name 
        SOAPFactory soapFactory = SOAPFactory.newInstance(); 
        //Next, create the actual message 
        MessageFactory messageFactory = MessageFactory.newInstance(); 
        SOAPMessage message = messageFactory.createMessage();       
        //获得一个SOAPPart对象            
        SOAPPart soapPart =     message.getSOAPPart(); 
        SOAPEnvelope envelope = soapPart.getEnvelope(); 
        SOAPBody body =         envelope.getBody(); 
        //Create the main element and namespace 
        SOAPElement bodyElement = 
                  body.addChildElement(envelope.createName(methodName , 
                                                                "tower", 
                                                                targetNamespace)); 
        bodyElement.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING); 
        
        //传送参数新建一个Name对象 
        Name name = soapFactory.createName("message"); 
        SOAPElement symbol = bodyElement.addChildElement(name); 
        symbol.addTextNode(messageContext); 
        message.saveChanges();   
return message; 
    } 

public SOAPMessage getMessage(String methodName,String targetNamespace,HashMap<String,String> hm) throws SOAPException,Exception{ 
     //传送参数需要创建Name 
        SOAPFactory soapFactory = SOAPFactory.newInstance(); 
        //Next, create the actual message 
        MessageFactory messageFactory = MessageFactory.newInstance(); 
        SOAPMessage message = messageFactory.createMessage();       
        //获得一个SOAPPart对象            
        SOAPPart soapPart =     message.getSOAPPart(); 
        SOAPEnvelope envelope = soapPart.getEnvelope(); 
        SOAPBody body =         envelope.getBody(); 
        //Create the main element and namespace 
        SOAPElement bodyElement = 
                  body.addChildElement(envelope.createName(methodName , 
                                                                "ns1", 
                                                                targetNamespace)); 
        bodyElement.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING); 
        
        //传送参数新建一个Name对象 
        Name name = soapFactory.createName("arg0"); 
        SOAPElement symbol = bodyElement.addChildElement(name); 
        for (Map.Entry<String, String> m : hm.entrySet()) {   
            System.out.println("HashMap" + m.getKey() + ":" + m.getValue()); 
            Name symbolName = soapFactory.createName(m.getKey()); 
           SOAPElement symbolNameSOAPElemen = symbol.addChildElement(symbolName);            
           symbolNameSOAPElemen.addTextNode(m.getValue()); 
        } 
        message.saveChanges();   
return message; 





在本例中Jboss esb服务器提供的web服务只能接受String类型的参数,所以要把soap消息进行格式转换。在服务的消费端通过以下的代码调用格式转换方法: 
TransformMessageFormat tmf = new TransformMessageFormatImpl(); 
String stringSOAPMessage = tmf.transformToString(sm); 
格式转换的类和方法如下: 
该类的路径在: 
SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\commom\formattransform\impl 

package org.jboss.seam.example.booking.commom.formattransform.impl; 

import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 

import javax.xml.soap.SOAPException; 
import javax.xml.soap.SOAPMessage; 

import org.jboss.internal.soa.esb.util.StreamUtils; 
import org.jboss.seam.example.booking.commom.formattransform.TransformMessageFormat; 

public class TransformMessageFormatImpl implements TransformMessageFormat{ 

public String transformToString(SOAPMessage sm) throws SOAPException, IOException{ 
        ByteArrayOutputStream s = new ByteArrayOutputStream(); 
        sm.writeTo(s);       
        byte[] buf=s.toByteArray();       
        ByteArrayInputStream bin=new ByteArrayInputStream(buf); 
        String msg = new String(StreamUtils.readStream(bin)); 
return msg; 



把转换后的Soap消息发送给Jboss ESB服务器,在服务消费者端的调用过程代码如下: 
SendSoapMessageToEsb smtEsb = new SendSoapMessageToEsbImpl(); 
Object o = smtEsb.sendMessageToJBRListener("http", "8765", stringSOAPMessage); 
发送消息到Jboss ESB服务器上的类和方法如下: 
该类在VSS上的路径如下: 
SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\commom\esb\impl 

package org.jboss.seam.example.booking.commom.esb.impl; 

import org.jboss.remoting.Client; 
import org.jboss.remoting.InvokerLocator; 
import org.jboss.seam.example.booking.commom.esb.SendSoapMessageToEsb; 

public class SendSoapMessageToEsbImpl implements SendSoapMessageToEsb { 

public static String ESBSERVICEIP="192.168.1.101"; 
public  Object sendMessageToJBRListener(String protocol, String port, String message) throws Throwable { 
        String locatorURI = protocol + "://"+ESBSERVICEIP+":" + port; 
        InvokerLocator locator = new InvokerLocator(locatorURI); 
        Client remotingClient = null; 
        try { 
            remotingClient = new Client(locator); 
            remotingClient.connect(); 
            Object response = remotingClient.invoke(message); 
            return response; 
        } finally { 
            if(remotingClient != null) { 
                remotingClient.disconnect(); 
            } 
        } 
    } 


public static String ESBSERVICEIP="192.168.1.101";这个IP地址表示Jboss ESB服务器的IP地址。 
String locatorURI = protocol + "://"+ESBSERVICEIP+":" + port;这行代码可以拼成访问Jboss ESB上发布的Web服务的URL地址。 
我们接着看调用端的代码,从Jboss ESB的返回值(其实是Jboss ESB服务器上发布的Web服务再通过转发调用服务提供者端的Web服务,从而得到返回值,服务提供者端的返回值首先把值传回Jboss ESB服务器,Jboss ESB服务器再把值返回到服务调用端。)也是一个Object对象,我们要把这个Object对象转换为我们应用中的真实的对象(比如例子中的ListHotel对象),这个转换我使用了JAXB标准,这个标准主要用于.xml和Java对象之间的互转,因为返回值是SOAP消息,是.xml形式的,同过JAXB标准可以很方便的转换成我们想要的java类(比如ListHotel),在调用端的代码如下: 
TransformString tfs = new TransformStringImpl(); 
    Document   doc   =  tfs.transformStringToDocument(o.toString()); 
NodeList   nodeList  =  doc.getElementsByTagName("ListHotel"); 
    Element   element   =  (Element)nodeList.item(0); 
    String returnContent = element.getTextContent(); 
    if(returnContent.length()>0){ 
    String returnContentAnd = "<ListHotel>"+returnContent+"</ListHotel>"; 
    InputStream inputStream = new ByteArrayInputStream(returnContentAnd.getBytes()); 
            JAXBContext context = JAXBContext.newInstance(ListHotel.class); 
            try { 
                 Unmarshaller um = context.createUnmarshaller(); 
                 ListHotel hl = (ListHotel)um.unmarshal(inputStream); 
                 System.out.println("I'm King!"+hl.getElements());                  
                 telehotels = hl.getElements(); 
            } catch (JAXBException e) { 
                 e.printStackTrace(); 
            } 
    }else{ 
    telehotels = null; 
    } 
    }catch(Exception e){ 
    e.printStackTrace(); 
    telehotels = null; 
    } catch (Throwable e) { 
e.printStackTrace(); 


从Jboss ESB服务器的返回值先要经过转换,把它转换为Document类型,代码如下: 
TransformString tfs = new TransformStringImpl(); 
Document   doc   =  tfs.transformStringToDocument(o.toString()); 
转换的类和方法如下: 
该类在VSS上的路径如下: 
SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\commom\formattransform\impl 
package org.jboss.seam.example.booking.commom.formattransform.impl; 

import java.io.IOException; 
import java.io.StringReader; 
import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.parsers.ParserConfigurationException; 

import org.jboss.seam.example.booking.commom.formattransform.TransformString; 
import org.w3c.dom.Document; 
import org.w3c.dom.Element; 
import org.w3c.dom.NodeList; 
import org.xml.sax.InputSource; 
import org.xml.sax.SAXException; 

public class TransformStringImpl implements TransformString{ 

public Document transformStringToDocument(String s){ 
      DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); 
     DocumentBuilder builder; 
     try { 
builder = factory.newDocumentBuilder(); 
Document   doc   =   builder.parse(new   InputSource(new   StringReader(s))); 
return doc; 
} catch (ParserConfigurationException e) { 
e.printStackTrace(); 
} catch (SAXException e) { 
e.printStackTrace(); 
} catch (IOException e) { 
e.printStackTrace(); 

return null; 
  } 


public String[] getObjectArrayByESBReturnContent(String s){ 
      DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); 
     DocumentBuilder builder; 
     try { 
builder = factory.newDocumentBuilder(); 
Document   doc   =   builder.parse(new   InputSource(new   StringReader(s))); 
NodeList   nodeList  =  doc.getElementsByTagName("return"); 
    Element    element   =   (Element)nodeList.item(0); 
    String returnContent = element.getTextContent(); 
    if(returnContent.length()>0){ 
String[] getObjectContest = returnContent.split("</O>"); 
return getObjectContest; 
    }else{ 
    return null; 
    } 
} catch (ParserConfigurationException e) { 
e.printStackTrace(); 
} catch (SAXException e) { 
e.printStackTrace(); 
} catch (IOException e) { 
e.printStackTrace(); 

return null; 
  } 

public String[] getPropertyArrayByStringObject(String returnContent){ 
    if(returnContent.length()>0){ 
String[] getObjectContest = returnContent.split("</A&V>"); 
return getObjectContest;
    }else{ 
    return null; 
    } 
  } 

转换后的Document对象要进行一下截取,重新组装变成适合JAXB转换的XML形式,这个过程如下: 
NodeList   nodeList  =  doc.getElementsByTagName("ListHotel"); 
Element   element   =  (Element)nodeList.item(0); 
String returnContent = element.getTextContent(); 
     String returnContentAnd = "<ListHotel>"+returnContent+"</ListHotel>"; 
InputStream inputStream = new ByteArrayInputStream(returnContentAnd.getBytes()); 
把返回值转换为先转换为InputStream对象,这个InputStream对象就符合转换的形式了,可以进行转换了。 
要利用JAXB规范进行这样的转换,要进行两步: 
①用@XmlRootElement注解标注要转换成的Java类。 
Java类如下: 
这个Java类在VSS上的路径是: 
SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\commom\collectionbinder 
package org.jboss.seam.example.booking.commom.collectionbinder; 

import java.util.List; 

import javax.xml.bind.annotation.XmlElementRef; 
import javax.xml.bind.annotation.XmlRootElement; 

import org.jboss.seam.example.booking.Hotel; 
@XmlRootElement(name="ListHotel") 
public class ListHotel { 
private List<Hotel> hotel; 

public ListHotel() { 


public ListHotel(List<Hotel> hotel) { 
this.hotel = hotel; 


@XmlElementRef 
public List<Hotel> getElements() { 
return hotel; 


public void setElements(List<Hotel> hotel) { 
this.hotel = hotel; 



②通过JAXB提供的编组机制,把xml字符转换为Java对象。代码如下: 
JAXBContext context = JAXBContext.newInstance(ListHotel.class); 
            try { 
                 Unmarshaller um = context.createUnmarshaller(); 
                 ListHotel hl = (ListHotel)um.unmarshal(inputStream); 
                 System.out.println("I'm King!"+hl.getElements());                  
                 telehotels = hl.getElements(); 
            } catch (JAXBException e) { 
                 e.printStackTrace(); 
            } 
以上就是我用SAAJ实现Web Service开发的详细过程,文档只是一个参考的作用,要灵活掌握这种方式要翻越其他的资料,认真思考。 
这个实例中牵涉到了JBoss ESB服务器,我没有进行详细的介绍,我会在下一个文档中仔细讨论该部分内容。要很好的理解该实例请参考我的Jboss ESB的文档及Vss上的相关代码。 
以上是通过Jboss ESB服务器作为中间件转发从消息消费端到消息使用端的的消息的过程,SOA(V1.2)这个文件夹下的工程中还包括了一个服务消费者端直接访问服务提供者端的实例。即: 
该类在VSS上的路径为: 
SOA(V1.2)\Request endpoint(192.168.1.112)\booking\src\org\jboss\seam\example\booking\soa\esb\impl 

package org.jboss.seam.example.booking.soa.esb.impl; 

import java.lang.reflect.Field; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.util.HashMap; 

import javax.ejb.Remove; 
import javax.ejb.Stateful; 
import javax.xml.soap.SOAPException; 
import javax.xml.soap.SOAPMessage; 
import javax.xml.transform.Source; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.stream.StreamResult; 

import org.jboss.seam.annotations.Begin; 
import org.jboss.seam.annotations.End; 
import org.jboss.seam.annotations.In; 
import org.jboss.seam.annotations.Name; 
import org.jboss.seam.annotations.Out; 
import org.jboss.seam.annotations.security.Restrict; 
import org.jboss.seam.example.booking.Hotel; 
import org.jboss.seam.example.booking.commom.esb.BuildSoapMessage; 
import org.jboss.seam.example.booking.commom.esb.SendSoapMessage; 
import org.jboss.seam.example.booking.commom.esb.impl.BuildSoapMessageImpl; 
import org.jboss.seam.example.booking.commom.esb.impl.SendSoapMessageImpl; 
import org.jboss.seam.example.booking.commom.formattransform.TransformMethodName; 
import org.jboss.seam.example.booking.commom.formattransform.impl.TransformMethodNameImpl; 
import org.jboss.seam.example.booking.soa.esb.TeteDataHotelSave; 


@Stateful 
@Name("saveTeteDataHotel") 
@Restrict("#{identity.loggedIn}") 
public class TeteDataHotelSaveImpl implements TeteDataHotelSave 

   @In(required=false) 
   @Out(required=false) 
   private Hotel hotel; 
   
   
   @Begin 
   public void selectHotel(Hotel selectedHotel) 
   { 
  //Hotel h = new Hotel(); 
      hotel = selectedHotel; 
   } 
   
   public void seveHotel(Hotel hotel) 
   { 
   String ipAddress = hotel.getIpAddress(); 
   System.out.println("yuexiangcheng,test!!!!!========"+ipAddress); 
   TransformMethodName tmn = new TransformMethodNameImpl();    
   Field[] f = hotel.getClass().getDeclaredFields(); 
   HashMap<String,String> hm = new HashMap(); 
       for(int i = 0;i< f.length;i++){ 
try { 
    String attributeName = f[i].getName(); 
    System.out.println("attributeNameattributeNameattributeNameattributeNameattributeNameattributeName"+attributeName);
      String motherName = tmn.getGetMethodByAttribute(attributeName); 
Method method = hotel.getClass().getDeclaredMethod(motherName,null); 
String attributeValue = method.invoke(hotel, null).toString(); 
System.out.println("attributeValueattributeValueattributeValueattributeValueattributeValueattributeValue"+attributeValue);
hm.put(attributeName, attributeValue); 
} catch (SecurityException e) { 
e.printStackTrace(); 
} catch (NoSuchMethodException e) { 
e.printStackTrace(); 
} catch (IllegalArgumentException e) { 
e.printStackTrace(); 
} catch (IllegalAccessException e) { 
e.printStackTrace(); 
} catch (InvocationTargetException e) { 
e.printStackTrace(); 

       } 
       BuildSoapMessage bsm = new BuildSoapMessageImpl(); 
       SendSoapMessage ssm = new SendSoapMessageImpl(); 
       try { 
SOAPMessage sm = bsm.getMessage("updateHotelObject", "http://tower/ehr_DEV", hm); 
SOAPMessage result = ssm.send(sm, "http://"+ipAddress+":8080/jboss-seam-booking-jboss-seam-booking/HotelObjectImpl"); 
    TransformerFactory transformerFactory = 
            TransformerFactory.newInstance(); 
            Transformer transformer = 
            transformerFactory.newTransformer(); 
            Source sourceContent = result.getSOAPPart().getContent(); 
            StreamResult resultPrint = new StreamResult(System.out); 
            transformer.transform(sourceContent, resultPrint); 
            System.out.println();
} catch (SOAPException e) { 
// TODO 自动生成 catch 块 
e.printStackTrace(); 
} catch (Exception e) { 
// TODO 自动生成 catch 块 
e.printStackTrace(); 

  
   } 
   
   
   @End 
   public void cancel() {} 
   
   @Remove 
   public void destroy() {} 

该过程是服务消费者端修改了从服务提供者端检索到的数据,并对其进行了修改再保持到服务提供者端。具体的过程和上例查不多,请详细研究在VSS上的代码。值得注意的是该例中用到了JAXB的解组功能。 

以上就是我实现Web Service的两种方法,因为能力有限,所以写的不好,仅供参考。希望对大家有帮助,谢谢。 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值