WebServices和Xfire学习总结

来源:互联网 发布:云安全软件怎么样 编辑:程序博客网 时间:2024/06/05 08:33
用了3天多的时间学习了WebServices,第一次接触它,便对它一见钟情。但苦于网上的资料都是东拼西凑,XFire官方文档写的又不好,例子居然都少了services.xml文件。而且我看到有很多像我一样的初学者在开始学习WS时不知道如何下手,所以写了这篇文章,希望能对向学习WS的朋友有所帮助。在看不懂程序时一定要去查API。
WebServices概述
WebService是一种分布式环境.可以通过接口和代理远程访问对象,并可在这些对象上进行操作,并且它的设计理念相当好--“双跨”--跨平台、跨语言。
学习WebService,就不能不知道两个概念SOAP、WSDL。
SOAP概述:
SOAP意思是简单对象访问协议(Simple Object Access Protocol)。的确如它的名字一样,SOAP是很简单的。它是一个基于XML的协议,允许程序组件和应用程序彼此使用一种标准的Internet协议--HTTP来通讯。SOAP是一种独立的平台,它不依赖程序语言,它是简单的,弹性的,很容易扩展的。目前,应用程序能够彼此使用一种基于DCOM和CORBA技术的远程过程调用(RPC)来进行相互通讯,但HTTP不被设计为这个目的。RPC在Internet上应用是非常困难的,它们会出现许多兼容性和安全性的问题,因为防火墙和代理服务器通常都会阻断(block)这些类型的流量。应用程序之间最好的通讯方式是通过HTTP协议,因为HTTP是支持所有Internet浏览器和服务器的。基于这个目的,SOAP协议被创建出来。

  那么,它们是如何运作的呢?比如,一个应用程序(A)需要和另一个应用程序(B)在SOAP的帮助下进行彼此通讯。它们将使用下面的框架图来完成这个过程:

这个SOAP信封(SOAP envelope)是一个包含以下内容的XML文档:
具体的,一个SOAP信封在一个HTTP数据包之上,我理解它最终会被HTTP协议打包,它是这个样子:
被发送到SOAP Service的SOAP Envelope

<?xml version=1.0 encoding=UTF-8?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/
soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/
XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:sayHi xmlns:ns1="urn:HelloWorld_SOAPService"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/
soap/encoding/">
<ourName xsi:type="xsd:string">Superman</ourName>
</ns1:sayHi>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>:

  从SOAP Service接收的SOAP Envelope

<?xml version=1.0 encoding=UTF-8?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/
soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/
XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:sayHiResponse xmlns:ns1="urn:HelloWorld_SOAPService"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.
org/soap/encoding/">
<return xsi:type="xsd:string">Hello my friend, Superman!
Glad to see you!</return>
</ns1:sayHiResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
要理解SOAP Envelope中的所有标签的含义,需要花一点时间阅读 http://www.w3.org/2001/06/soap-envelope 命名空间规范。
其实在后面的学习中,因为Java提供了各种XML的API(JDOM),再搭配上XFire这个SOAP架构,所以一般只需要知道SOAP的工作原理就够了,基本上不会涉及到自己去写一个SOAP信封。
WSDL概述

你会怎样向别人介绍你的Web service有什么功能,以及每个函数调用时的参数呢?你可能会自己写一套文档,你甚至可能会口头上告诉需要使用你的Web service的人。这些非正式的方法至少都有一个严重的问题:当程序员坐到电脑前,想要使用你的Web service的时候,他们的工具(如Visual Studio)无法给他们提供任何帮助,因为这些工具根本就不了解你的Web service。解决方法是:用机器能阅读的方式提供一个正式的描述文档。Web service描述语言(WSDL)就是这样一个基于XML的语言,用于描述Web service及其函数、参数和返回值。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的,这将是一个很大的好处。一些最新的开发工具既能根据你的Web service生成WSDL文档,又能导入WSDL文档,生成调用相应Web service的代码。

XFire

XFire是一个开源的SOAP框架,用它可以快速开发WS。

它在Eclipse下的配置也是相当简单,甚至你可能都不需要做什么配置。

在Eclipse下新建一个Web Service Project工程,这个工程与传统的Web Project工程不同的不过是通过向导添加XFire以及services.xml。

然后我们next,这时的界面是传统Web Project工程的界面,需要输入工程名什么的,填好,next
这个界面设置你的XFire参数,这些参数会被写进Web.xml中,next
我的MyEclipse集成了XFire1.1,当然还可以通过User Libraries加载你的XFire包。
这时就配置好了,这里要说明的是,services.xml文件会自动加载到类路径下,具体路径是/Apache Software Foundation/Tomcat 5.5/webapps/SimpleWSExam/WEB-INF/classes/META-INF/xfire
web.xml:
开发一个简单的WS
首先先来写WS服务器端,通过XFire你无须任何其它的操作,只需要为已经实现的POJOs设计相应的窄接口就OK了,当然我们也可以反过来,先设计接口,再实现它,为了让例子最简单,例子采用后者
服务器端某接口:
HelloWorldService.java
/**
 * HelloWorldService 中声明需要发布成 Web 服务的所有 Java 方法 HelloWorldService 作为Web服务接口
 */
public interface HelloWorldService {
 /**
  * sayHello 方法声明了 Web 服务对外暴露的接口
  *
  * @return 返回给客户端的字符串
  */
 public String sayHello(String name);
 }
接口实现:
HelloWorldServiceImpl.java
/**
 * HelloWorldServiceImpl 中为 Web 服务接口中声明的所有 Java 方法提供具体实现 HelloWorldServiceImpl
 * 作为 Web 服务的实现类
 */
public class HelloWorldServiceImpl implements HelloWorldService {
 /*
  * sayHello 方法为 HelloWorldService 服务接口定义的 sayHello 方法提供具体实现
  *
  * @see org.vivianj.XFire.pojo.HelloWorldService#sayHelloToXFire()
  */
 public String sayHello(String name) {
  return "Hello World!"+name;
 }
}
然后我们配置它,XFire让我们只需要在services.xml中配置即可
services.xml

关于services.xml各元素信息,请参阅官方文档http://xfire.codehaus.org/services.xml+Reference。

现在我们的Web服务已经配置好了,我们可以在地址栏中输入http://localhost:8080/SimpleWSExam/services,可以看到

单击wsdl连接,可以看到关于这个WS的WSDL。在XFire下配置WS就是这么简单!

现在我们来写客户端,

HelloWorldClient.java

public class HelloWorldClient {
 public static void main(String args[]){
  String serviceURL = "http://localhost:8080/SimpleWSExam/services/HelloWorldService";
        Service serviceModel = new ObjectServiceFactory().create(HelloWorldService.class,null,"http://localhost:8080/SimpleWSExam/services/HelloWorldService",null);
       
        XFireProxyFactory serviceFactory = new XFireProxyFactory();
        try {
   HelloWorldService hello=(HelloWorldService)serviceFactory.create(serviceModel, serviceURL);
  
      System.out.println(hello.sayHello("Jackal Wood"));
   } catch (MalformedURLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }
}

一个众人皆知的道理是,WS是只读的,也就是说,WS不能有setxxx()。

如果取的值是List或者是Map,则需要在与源文件相同的目录下创建名为xxxx.aegis.xml的文件,其中xxxx是服务的类名,比如我们在接口中添加public List getList();并实现之,如果客户端想调用这个函数,需要写HelloWorldService.aegis.xml:

 Aegis 是 XFire 的缺省的绑定方式,可以将 XML 映射成 POJO。,在官方例子中,aegis文件中对类的描述基本是全部的,不像我这里只是写上必须的描述。关于aegis,请见其它更详细的资料。当然XFire不仅仅可以绑定aegis,它还支持JAXB2、XMLBeans、Castor、Jibx。

身份验证

XFire提供了四种身份验证的方式,分别是http验证,JSR181,Handler验证,WS-Security。

这里我们只谈Handler验证。

SOAP的原理告诉我们一个WS交互的流程是客户端发送请求->服务器接收请求->服务器发送数据->客户端接收数据,handler可以让我们在这四个操作中之前进行编码,所以,我们的验证进行在客户端发送请求和服务器接收请求的时候,因此,我们要写两个handler,然后把它们装配上就可以了。

服务器接收请求:

AuthentificationHandler.java:

package org.vivianj.xfire.handlers;

import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
import org.jdom.Element;
import org.jdom.Namespace;

public class AuthentificationHandler extends AbstractHandler {

 public void invoke(MessageContext cfx) throws Exception {
  // TODO Auto-generated method stub
  
      if(cfx.getInMessage().getHeader() == null)
      {
       throw new org.codehaus.xfire.fault.XFireFault("请求必须包含验证信息",org.codehaus.xfire.fault.XFireFault.SENDER);
      }
      Element token=cfx.getInMessage().getHeader().getChild("AuthenticationToken");

      if (token == null)

      {

          throw new org.codehaus.xfire.fault.XFireFault("请求必须包含身份验证信息",

            org.codehaus.xfire.fault.XFireFault.SENDER);

      }

      String username = token.getChild("Username").getValue();

      String password = token.getChild("Password").getValue();

      try

      {

          //进行身份验证

         if(username.equals("jackal") && password.equals("talent"))
          //这语句不显示
          System.out.println("身份验证通过");
         else throw new Exception();
        
          // 身份验证通过

         // cfx.setProperty(User_KEY, user);

      }

      catch (Exception e)

      {

          throw new   org.codehaus.xfire.fault.XFireFault("非法的用户名和密码",   org.codehaus.xfire.fault.XFireFault.SENDER);

      }

  }

}

客户端发送请求:

ClientAuthenticationHandler.java

package org.vivianj.xfire.handlers;

import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
import org.jdom.Element;
import org.jdom.Namespace;


public class ClientAuthenticationHandler extends AbstractHandler {

        private String username = null;

        private String password = null;

        public ClientAuthenticationHandler() {

        }

        public ClientAuthenticationHandler(String username,String password) {

            this.username = username;

            this.password = password;
        }

        public void setUsername(String username) {

            this.username = username;

        }

        public void setPassword(String password) {

            this.password = password;

        }

        public void invoke(MessageContext context) throws Exception {

             Element el = new Element("header");
            context.getOutMessage().setHeader(el);
           

            Element auth = new Element("AuthenticationToken");

            Element username_el = new Element("Username");

            username_el.addContent(username);

            Element password_el = new Element("Password");

            password_el.addContent(password);

            auth.addContent(username_el);

            auth.addContent(password_el);

            el.addContent(auth); 
        }

    }

可以看到,客户端在发送数据前做的事情是:往SOAP信封里加入了一个头信息,并在这个头信息中包含了AuthenticationToken属性,它又包含了Username以及Password两个属性,而用户名和密码的值通过形参穿过来。这里面用到了JDOM,JDOM是一个针对JAVA的轻量级的文档对象模型。

服务器接受到SOAP信封后先找到这个验证信息,然后验证它。

装配:

客户端装配:

只需要在客户端程序HelloWorldClient.java中加入

 Client client = Client.getInstance(hello);

 client.addOutHandler(new ClientAuthenticationHandler("jackal","talent"));

服务器端装配:

需要在services.xml中开始写入,重写后的文件

services.xml:

 

如果你有Spring+Struts的经验,你会发现XFire+Spring和它很像。

web.xml:

这个web.xml有这几点要注意:

1.XFire的servlet-class已经不是 org.codehaus.xfire.transport.http.XFireConfigurableServlet,而是org.springframework.web.servlet.DispatcherServlet。

2.在contextConfigLocation中我们又加载了XFire自身的xfire.xml。

xfire-servlet.xml:

如果你有struts+spring的经验,你一定对action-servlet.xml不陌生,没错,这里我们需要xfire-servlet.xml,"xfire"映射web.xml文件中<servlet-name>,而且他的路径应该是根目录下

META-INF/xfire/service.xml文件可以省略了,因为web服务的定义在xfire-servlet.xml中可以找到。
下面要做的工具就是配置了。

这个文件的上半部分将HelloWorldService这个URL和hello这个bean联系在一起。下半部分定义了Web服务的bean和服务接口。其中HelloWorldBean是我们在applicationContext.xml中配置的那个Bean。

最后来看看applicationContext.xml

如果有兴趣的朋友,还可以看看SpringSide2.0中关于这部分的源码。

最后说一句,与spring集成后,就不能通过http://localhost:8080/services或者http://localhost:8080/services/HelloService查看是否通,而只能通过http://localhost:8080/services/HelloService?wsdl.

 


 

 

 
 
 

 

XFire与Spring的集成: