XML-RPC 的 Apache 实现

来源:互联网 发布:淘宝泄露买家地址信息 编辑:程序博客网 时间:2024/04/28 16:32

-RPC 规范及 Java 实现

转自:http://www.ibm.com/developerworks/cn/webservices/1211_zhusy_rpc/

简介: 在新技术、新概念甚至新思维层见叠出的 IT 行业 , XML-RPC 绝对不是最新的热门技术,但它自从诞生时起,一直在 IT 行业占有一席之地。XML-RPC 具有简单、高效且易于实现等优点,它一直是中小型应用实现分布式计算的最佳选择之一。本文将会首先介绍一下 XML-RPC 协议规范,然后介绍如何实现 Apache XML-RPC 开发客户端及服务端 Java 代码。最后对 Apache XML-RPC 的高级特性进行了介绍。

在新技术、新概念甚至新思维层见叠出的 IT 行业,XML-RPC 绝对不是最新的热门技术,但它自诞生之日起,一直在 IT 行业占有一席之地。XML-RPC 具有简单且易于实现,可以高效跨越不同的软硬件平台上应用系统等优点。相对于庞大、复杂的分布式计算机制,它一直是中小型应用的最佳选择之一。比如,流行于软件研发团队的任务计划管理系统 JIRA、开源的测试用例管理工具 TestLink 等都提供了开放的 XML-RPC 接口,供用户定制开发时进行调用。

XML-RPC(XML-based Remote Procedure Call,基于 XML 的远程过程调用)是一种工作在互联网上的远程过程调用协议。一个 XML-RPC 请求消息就是一个 HTTP-POST 请求消息,其请求消息主体基于 XML 格式。客户端发送 XML-RPC 请求消息到服务端,调用服务端的远程方法并在服务端上运行远程方法。远程方法执行完毕后,返回响应消息给客户端,其响应消息主体同样基于 XML 格式。远程方法的参数支持数字,字符串、日期等;也支持列表数组和其他复杂结构类型。

主流的开发语言都提供了 XML-RPC 协议的客户端程序和服务端程序的实现,这些语言包括 Java、Ruby、Python、C/C++ 、Perl 和 PHP 等。本文选择以 XML-RPC 的 Java 实现 Apache XML-RPC 为例,来研究 XML-RPC 协议规范。Apache XML-RPC 完全兼容 XML-RPC 协议规范,同时也提供了一些扩展特性,使其还具有下述能力:

  • 支持 Java 的全部基本数据类型,如 long、byte、short,以及 double
  • 支持 Calendar 对象
  • 支持支持 DOM 节点、JAXB 对象实例能通过 XML-RPC 进行传输。事实上,任何实现 java.io.Serializable 接口的对象都可以被传输
  • 客户端和服务端都可以通过配置在流模式(stream mode)下进行操作,这样比基于大字节数组的默认模式节省资源

下面就以实例来学习 XML-RPC 的请求和响应消息的格式。本文以当前工作使用的 Window 机器为客户端,IP 为 192.168.1.105。服务端为 Ubuntu 机器,IP 为 192.168.1.126。

步骤 1. 下载本文附带的源代码文件,内含三个 Eclipse Java 项目,请把他们导入到 Eclipse 工作区间:

    • XML-RPC-Client XML-RPC 客户端实现的例子
    • XML-RPC-EmbeddedWebServer 嵌入式服务端端实现的例子,可以直接运行
    • XML-RPC-Server Servlet 服务端端实现的例子 , 需要部署在 Servlet 容器,比如 Tomcat、Jetty 等

步骤 2. 右键选择 XML-RPC-Server 项目,导出为 WAR 文件,命名为 xmlrpc.war。把它部署在服务端的 Servlet 容器上,本文以部署在 Ubuntu 12.04 上的 Tomcat 7.0 为例,复制 xmlrpc.war 到 Tomcat 安装根目录下的 webapps 文件夹下。启动 Tomcat 服务器,使用浏览器访问 http://192.168.1.126:8080/xmlrpc/xmlrpc, 如能正常显示,则表明在服务端上部署成功。

步骤 3,下载安装 SmartSniff(下载地址见参考资源部分)。SmartSniff 用于捕获客户端与服务端的 XML-RPC 消息交互数据包。运行该软件,在菜单 Options-Capture Filter 与 Display Filter 里设置捕获过滤、展示过滤:include:remote:tcpudp: 192.168.1.126:8080,这样只显示我们感兴趣的协议数据。点击 Start Capture 按钮,启动捕获监听。注意:SmartSniff 只能运行在 Window 机器上,并且只能捕获不同的机器之间的 TCP/IP 网络数据交互。如果 XML-RPC 客户端与服务端运行在相同的机器上,无法使用该工具捕获协议交互数据。

步骤 4,运行客户端的单元测试类,研究 XML-RPC 消息格式。在 Eclipse 项目 XML-RPC-Client 中,打开单元测试类 junit.TestClientServletWebServer.java。选择 testXMLRPC()方法,以 JUnit Test 方式运行。可以发现 JUnit 单元测试可以运行成功,SmartSniff 捕获的协议见图 1。使用相同的方法,可以运行更多的测试类,研究更多的 XML-RPC 消息的格式。


图 1. 捕获 XML-RPC 协议消息
图 1:捕获 XML-RPC 协议消息 

XML-RPC 请求消息格式

清单 1 是捕获的请求消息,可以看出和普通的 HTTP 请求一样,包含如下消息头:POST、Content-Type、User-Agent、Authorization、Host 与 Content-Length 等。XML 格式的消息体包含一个 methodCall 元素来指定远程方法的信息。MethodName 指定远程方法名称,params/param/value 等标签元素指定远程方法需要的参数列表。xmlns:ex 名称空间是 Apache XML-RPC 对 XML-RPC 协议的扩展,后续会详细介绍。


清单 1. 请求消息例子
   POST /xmlrpc/xmlrpc HTTP/1.1  Content-Type: text/xml  User-Agent: Apache XML RPC 3.1.3 (Jakarta Commons httpclient Transport)  Authorization: Basic Zm9vOmJhcg==  Host: 192.168.1.126:8080  Content-Length: 260  <?xml version="1.0" encoding="UTF-8"?>  <methodCall xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">  <methodName>Calculator.add</methodName>  <params>  <param>  <value>  <i4>2</i4>  </value>  </param>  <param>  <value>  <i4>3</i4>  </value>  </param>  </params>  </methodCall> 

XML-RPC 响应消息格式

清单 2 是捕获的响应消息。消息体同样是 XML 格式的,它包含一个 methodResponse 元素来指定远程方法的响应内容。methodResponse 元素下面可以包含一个 params 元素,用于包装返回的数据内容。也可以包含 <fault> 元素,用于返回遇到的异常消息。XML-RPC 响应消息必须有返回值,因此 <fault> 和 <params> 必须出现一个且只能出现一个。


清单 2. 响应消息例子
   HTTP/1.1 200 OK  Server: Apache-Coyote/1.1  Content-Type: text/xml  Content-Length: 189  Date: Thu, 18 Oct 2012 02:23:04 GMT  <?xml version="1.0" encoding="UTF-8"?>  <methodResponse  xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">  <params>  <param>  <value>  <i4>5</i4>  </value>  </param>  </params>  </methodResponse> 

在本节,我们研究了 XML-RPC 协议规范及其消息内容格式。下面继续介绍的是,如何使用 Apache XML-RPC 开发客户端和服务端 Java 代码。

如何开发客户端代码

在 Eclipse 项目 XML-RPC-Client 中,打开单元测试类 junit.TestClientServletWebServer.java。我们研究一下客户端的代码实现,见清单 3:


清单 3. 客户端代码实现
   import org.apache.xmlrpc.client.XmlRpcClient;   import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;   ……   // 创建客户端实例 XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();   config.setServerURL(new URL("http://192.168.1.126:8080/xmlrpc/xmlrpc"));   XmlRpcClient client = new XmlRpcClient();  client.setConfig(config);  // 设置传输工厂类 client.setTransportFactory(new XmlRpcSunHttpTransportFactory(client));  // 创建远程方法的参数数组,通过指定远程方法名称进行调用  Object[] params = new Object[]{new Integer(33), new Integer(9)};  Integer result = (Integer) client.execute("Calculator.add", params); 

上面的代码首先实例化一个客户端配置对象 XmlRpcClientConfigImpl config,为该实例指定服务端 URL 属性。接着创建 XmlRpcClient 对象,为其指定配置对象,然后为其设置一个 Sun HTTP 传输工厂对象,这个属于默认的传输工厂对象,可以忽略该行语句。最后创建参数数组,并调用服务端的方法 Calculator.add(后文会介绍如何查询服务端可以提供的方法列表)。

我们可以选择不同的传输工厂对象,比如可以不使用默认的 Sun HTTP 传输工厂对象,而是使用基于 Jakarta Commons HttpClient 的传输工厂。使用不同的传输工厂, XML-RPC 请求消息头 User-Agent 部分可以看出差异。使用 SmartSniff 捕获到的请求消息头,如下图所示。


图 2. 使用不同传输工厂的 XML-RPC 请求消息
图 2:使用不同传输工厂的 XML-RPC 请求消息 

XmlRpcClient 是一个无状态的线程安全的对象,实例化 client 对象时,需要设置下面的对象:


表 1. 配置 XmlRpcClient
名称描述ClientConfig该对象是 XmlRpcClientConfig 的实例,包括如下原子属性,比如服务端 URL,认证信息,编码字符集等TransportFactory该工厂类用于创建负责使用客户端配置与服务端通讯的对象。下文会介绍常用的传输工厂类。XmlWriterFactoryXmlWriter 用于创建 XML

传输工厂类 TransportFactory 决定客户端与服务端通讯的方式,Apache XML-RPC 支持如下几种工程类:


表 2. 传输工厂类
工厂类名描述XmlRpcSunHttpTransportFactory默认的工程类 , 使用 java.net.HttpURLConnection 与服务端连接XmlRpcCommonsTransportFactory该工厂类使用 Jakarta Commons HttpClient 与 HTTP 服务端通讯 . 该工厂允许直接访问结果文档,可以降低内存资源的使用。XmlRpcLiteHttpTransportFactory该工厂类基于内置的 HTTP 客户端,速度很快,但不支持 
HTTP/1.1, 不能使用 keepalive 连接。XmlRpcLocalTransportFactory该工厂类拥有内置的 XML-RPC 服务器,可以直接使用 Java 调用,主要用于调试与开发环境。

根据传输工厂的不同,客户端配置分为如下两种类型:

  • org.apache.xmlrpc.client.XmlRpcHttpClientConfig

    用于 HTTP 传输工厂,即上文提到的 XmlRpcSunHttpTransportFactory、XmlRpcCommonsTransportFactory 和 XmlRpcLiteHttpTransportFactory。HTTP 传输工厂对应的客户端配置实例支持以下属性设置 basicUserName、basicPassword、basicEncoding、contentLengthOptional、enabledForExceptions、enabledForExtensions、Encoding、gzipCompressing 和 gzipRequesting。关于这些属性的详细描述,请参考 Apache XMl-RPC 官方站点。

  • org.apache.xmlrpc.client.XmlRpcLocalClientConfig

    用于本地传输工厂,即上文提到的 XmlRpcLocalTransportFactory。本地传输工厂对应的客户端配置实例支持配置属性 xmlRpcServer。作为内置的 XML-RPC 客户端,模拟来自客户端的请求,用于调试与开发。

    org.apache.xmlrpc.client.XmlRpcClientConfigImpl 实现了上述两个接口,为方便计,不管使用什么传输工厂类,客户端配置都可以使用实现类 XmlRpcClientConfigImpl。

如何开发服务端代码

相对于 XmlRpcClient,服务端的 XmlRpcServer 对象用于接收并执行来自客户端的 XML-RPC 调用,该对象可以嵌入 Servlet 容器(Tomcat,Jetty 等),或其他的 HTTP 服务器。在使用本地传输工厂类的情况下,XML-RPC 被集成到客户端应用内。和 XmlRpcClient 类似,XmlRpcServer 也需要制定一个服务端配置对象 XmlRpcServerConfigImpl。该配置对象支持的属性如下:


表 3. 服务端配置属性
属性名称描述enabledForExceptions在启用该属性时,如果服务端捕获异常,服务端会把异常转换成字节数组,
并返回客户端。enabledForExtensions指定是否启用 Apache 对 XML-RPC 的扩展。

下面,我们就来研究一下如果使用 XML-RPC Servlet 来开发服务端的代码。

首先,创建一个业务逻辑类,供客户端调用。见清单 4。需要注意:能被客户端调用的方法必须是 public 的,返回值不是 void 的、实例方法。像构造方法、存取方法的 setter 方法和静态方法都不能被远程调用。任何抽象类、接口类,不能被实例化,它们的方法也不能被远程调用。


清单 4. Calculator 业务逻辑类
    package org.apache.xmlrpc.demo;     public class Calculator {                 public int add(int i1, int i2) {                         return i1 + i2;                 }                 public int subtract(int i1, int i2) {                         return i1 - i2;                 }  } 

接着,在源文件目录 org/apache/xmlrpc/webserver/ 下创建属性文件 XmlRpcServlet.properties。XmlRpcServlet 默认在上述包路径下寻找属性文件。在文件中添加如下条目(清单 5)。左侧为一标识符,右侧为业务逻辑类的全限定类名。


清单 5. 句柄属性文件
   Calculator=org.apache.xmlrpc.demo.Calculator 

然后,在 Web 应用的部署描述文件 web.xml 添加如下内容(清单 6):


清单 6. 部署描述符配置 XmlRpcServlet
   <servlet>         <servlet-name>XmlRpcServlet</servlet-name>         <servlet-class>org.apache.xmlrpc.webserver.XmlRpcServlet</servlet-class>         <init-param>           <param-name>enabledForExtensions</param-name>           <param-value>true</param-value>         </init-param>     </servlet>     <servlet-mapping>         <servlet-name>XmlRpcServlet</servlet-name>         <url-pattern>/xmlrpc</url-pattern>     </servlet-mapping> 

现在已经完成了服务端的代码开发,可以把它打成 war 包,部署在 servlet 容器内。启动服务端后,就可以运行客户端代码对服务端上提供的远程方法进行调用。关于如何运行,请详细参考本文附带的源代码项目。

另外,为了给服务端上提供的 XML-RPC 接口提供安全保障,可以为服务端启用认证机制。Apache XML-RPC 使用 XmlRpcHandler 处理认证,它以 XmlRpcRequest 作为参数。XmlRpcRequest 的 getConfig() 方法可以返回 XmlRpcRequestConfig 对象。使用 XmlRpcRequestConfig 的方法 getBasicUserName() 和 getBasicPassword() 可以获得客户端的认证信息。获取客户端的认证信息后,就可以采取适当的逻辑去验证。

首先,实现 AuthenticationHandler 接口,编写处理认证信息的逻辑代码,见清单 7。


清单 7. 认证处理器程序
   public class myAuthenticationHandler implements AuthenticationHandler {  @Override  public boolean isAuthorized(XmlRpcRequest req) throws XmlRpcException {  XmlRpcHttpRequestConfig config = (XmlRpcHttpRequestConfig) req.getConfig();  return isAuthenticated(config.getBasicUserName(),  config.getBasicPassword());  }  private boolean isAuthenticated(String pUserName, String pPassword) {  return "foo".equals(pUserName) && "bar".equals(pPassword);  }  } 

在自己定义的 XmlRpcServlet 类中使用 AuthenticationHandler,见清单 8。并在应用的部署描述文件中,修改 servlet-class,使用定制的 Servlet 类 MyServlet。


清单 8. 自定义的 XmlRpcServlet
   public class MyServlet extends XmlRpcServlet {  private static final long serialVersionUID = 4499604583805027974L;  protected XmlRpcHandlerMapping newXmlRpcHandlerMapping()  throws XmlRpcException {  PropertyHandlerMapping mapping = (PropertyHandlerMapping) super  .newXmlRpcHandlerMapping();  AuthenticationHandler handler = new myAuthenticationHandler();  mapping.setAuthenticationHandler(handler);            //XmlRpcSystemImpl.addSystemHandler(mapping);  return mapping;  } 

重启 Servlet 容器,刚才的单元测试类必须添加如下用户认证信息(清单 9)才能执行成功。


清单 9. XML-RPC 客户端提供认证信息
   config.setBasicUserName("foo");  config.setBasicPassword("bar"); 

至此,我们完成了使用 Apache XML-RPC 开发客户端和服务端代码,部署到了 Servlet 容器 Tomcat 上,并掌握了如何运行客户端代码调用服务端的远程方法。下面对 Apache XML-RPC 的高级特性进行介绍。除了部署在 Servlet 容器里,Apache XML-RPC 还提供了嵌入式 WebServer。XML-RPC 动态代理技术带来了良好的编程体验。通过启用扩展,Apache XML-RPC 支持了更多的 Java 数据类型。还对内省机制进行了简单介绍。

嵌入式 WebServer 实现

嵌入式 WebServer,更加便携,适用于为非 Web 应用提供远程方法。WebServer 实现代码见如下清单 10。


清单 10. 嵌入式 WebServer 代码
            WebServer webServer = new WebServer(port);                   XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();            PropertyHandlerMapping phm = new PropertyHandlerMapping();           /* 从属性文件加载           */           phm.load(Thread.currentThread().getContextClassLoader(),                    "MyHandlers.properties");           /* 直接使用 PropertyHandlerMapping 的 addHandler() 方法进行加载。           * 推荐使用属性文件,避免硬编码           */           phm.addHandler("Calculator",dw.xmlrpc.Calculator.class);           XmlRpcServerConfigImpl serverConfig =               (XmlRpcServerConfigImpl) xmlRpcServer.getConfig();           serverConfig.setEnabledForExtensions(true);           serverConfig.setEnabledForExceptions(true);           serverConfig.setContentLengthOptional(false);           webServer.start(); 

Apache XML-RPC 推荐使用 WebServer 的子类 ServletWebServer。ServletWebServer 支持 Servlet API,方便日后迁移到 Servlet 容器上。使用该子类需要把 servlet-api.jar 加入到构建路径中,该 jar 文件可以在 Servlet 容器中得到。本文演示的 ServletServer.java 代码片段如清单 11 所示。


清单 11. 嵌入式 ServletServer 实现
        XmlRpcServlet servlet = new XmlRpcServlet();         ServletWebServer webServer = new ServletWebServer(servlet, port);         XmlRpcServer xmlRpcServer=  servlet.getXmlRpcServletServer();           webServer.start();                XmlRpcServerConfigImpl serverConfig =             (XmlRpcServerConfigImpl) xmlRpcServer.getConfig();             serverConfig.setEnabledForExtensions(true);             serverConfig.setEnabledForExceptions(true);             serverConfig.setContentLengthOptional(false);         PropertyHandlerMapping phm=(PropertyHandlerMapping)         xmlRpcServer.getHandlerMapping();         String[] ss=phm.getListMethods();         System.out.println("打印服务端支持的远程调用方法:");         for(String s:ss){         System.err.println(s);         } 

可以在 Eclipse 选中包 dw.webserver 中 Server.java 或 ServletServer.java,右键弹出上下文菜单,选择 Export 导出菜单,在接下来的向导中选择 Runnable Jar file,并将依赖的 Jar 文件导出到单独的文件夹中,导出的可运行的 Jar 命名为 XMLRPC.jar。通过下面的命令,启动嵌入式 WebServer:java -cp XMLRPC_lib -jar XMLRPC.jar。

在客户端调用嵌入式服务端的远程方法和调用部署在 Servlet 容器上的服务端的远程方法一样,只是前者为 config 对象指定 ServerURL 的时候只需要指定 IP 和端口即可,服务的名称不是必须的,如 config.setServerURL(new RL("http://127.0.0.1:7071"))。

XML-RPC 动态代理技术

在客户端编写代码调用服务端远程方法时,需要为 client.execute() 提供两个参数。第一个参数为字符串 String 类型,即远程方法的名称;第二个参数是数组类型,即远程方法的参数列表。这种编程体验不够友好,很容易因为记忆、编写错误而导致问题。动态代理技术可以提供一种舒服的客户端编程方式。工作方式如下:把服务端的远程方法都抽象提炼到相应的接口文件中,接口文件在客户端、服务端都存在。在客户端以 XmlRpcClient 为参数创建 ClientFactory 的一个实例。ClientFactory 以抽象接口为参数,返回服务端对应的实现类的实例,然后使用该实例调用服务端的远程方法。

首先,编写接口文件 Adder.java,该接口在服务端和客户端都存在。接着,在服务端编写实现类 AdderImpl.java,实现上述接口类。见清单 12。


清单 12. 动态代理接口及其实现类
   package dw.xmlrpc;  public interface Adder {  public int add(int pNum1, int pNum2);  }  package dw.xmlrpc;  public class AdderImpl implements Adder {  public int add(int pNum1, int pNum2) {  return pNum1 + pNum2;  }  } 

然后,修改句柄属性配置文件,注意左侧必须为接口类的全限定名称(清单 13)。


清单 13. 属性配置文件
   xmlrpc.Adder=dw.xmlrpc.AdderImpl 

最后,重启服务端,使修改生效。使用动态代理技术编写客户端调用服务端的方法(清单 14)。可以看出使用这种方式,客户端代码可读性增强,编写更加容易。


清单 14. 动态代理客户端测试类
   @Test  public void testDynamicProxy() {  ClientFactory factory = new ClientFactory(client);  Adder adder = (Adder) factory.newInstance(Adder.class);  int sum = adder.add(2, 4);  org.junit.Assert.assertEquals("期望返回整数 6", 6, sum);  System.out.println("2 + 4 = " + sum);  } 

XML-RPC 支持的数据类型

XML-RPC 请求消息体和响应消息体中 Value 元素的下级子元素用于指定数据类型。数据类型元素包含实际的文本数据。XML-RPC 协议规范支持八种数据类型,Apache XML-RPC 都有相应的 Java 数据类型与其对应。如果 XML-RPC 的客户端和服务端都基于 Apache XML-RPC 实现,并且启用了扩展(enabledForExtensions 属性设置为 true),还可以额外支持更多的 Java 数据类型。通过启用扩展,XML-RPC 支持 Java 语言的全部基本数据类型,日历对象,序列化对象等。

下面将要演示 Apache XML-RPC 如何支持扩展的序列化对象。首先,创建一个序列化 Java Bean,其中包含一个可供远程调用的方法 setAndGetBean(), 其参数与返回值均为一个可序列化的对象 SerializableBean。见清单 15。


清单 15. Java 序列化类
   public class SerializableBean implements Serializable {  private static final long serialVersionUID = 3787840155837070188L;  private String str;  public SerializableBean setAndGetBean(SerializableBean bean) {  this.str = bean.getStr()+"--> Server!";  bean.setStr(str);  return bean;  }  String getStr() {  return str;  }  public void setStr(String str) {  this.str = str;  }  @Override  public String toString() {  return "SerializableBean [str=" + str + "]";  }  } 

接着,在句柄属性配置文件添加一行 serializable=dw.xmlrpc.SerializableBean,并重启服务端(Servlet 容器或者 WebServer 进程)。

最后,编写客户端调用远程方法,代码清单 16 如下:


清单 16. 序列化测试类
        @Test  public void testSerializable() {  SerializableBean bean = new SerializableBean();  bean.setStr("Client!");  params = new Object[] { bean };  try {  result = (SerializableBean) client.execute( "serializable.setAndGetBean", params);  System.err.println(result.toString());  } catch (XmlRpcException e) {  e.printStackTrace();  org.junit.Assert.fail(e.getMessage());  }  org.junit.Assert.assertNotNull("返回结果不为 Null", result);  } 

运行客户端的时候,使用 SmartSniff 进行监控协议数据。观察捕获的协议数据,可以看到扩展的标签使用了 ex 前缀,其名称空间 URL 为 http://ws.apache.org/xmlrpc/namespaces/extensions。请求消息、响应消息数据见清单 17。


清单 17. 请求消息数据与响应消息数据
  请求消息: POST /xmlrpc/xmlrpc HTTP/1.1  Content-Type: text/xml  User-Agent: Apache XML RPC 3.1.3 (Jakarta Commons httpclient Transport)  Authorization: Basic Zm9vOmJhcg==  Host: 192.168.1.126:8080  Content-Length: 369  <?xml version="1.0" encoding="UTF-8"?>  <methodCall xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">  <methodName>serializable.setAndGetBean</methodName>  <params>  <param>  <value><ex:serializable>rO0ABXNyABpkdy54bWxycGMuU2VyaWFsaXphYmxlQmVhb  jSRHI2KoPtsAgABTAADc3RydAASTGphdmEvbGFuZy9TdHJpbmc7eHB0AAdDbGllbnQh  </ex:serializable>  </value>  </param>  </params>  </methodCall> 响应消息: HTTP/1.1 200 OK  Server: Apache-Coyote/1.1  Content-Type: text/xml  Content-Length: 342  Date: Thu, 18 Oct 2012 02:45:26 GMT  <?xml version="1.0" encoding="UTF-8"?>  <methodResponse xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">  <params>  <param>  <value><ex:serializable>rO0ABXNyABpkdy54bWxycGMuU2VyaWFsaXphYmxlQmVhbj  SRHI2KoPtsAgABTAADc3RydAASTGphdmEvbGFuZy9TdHJpbmc7eHB0ABJDbGllbnQhLS0+IFNlcnZlciE=  </ex:serializable>  </value>  </param>  </params>  </methodResponse> 

XML-RPC 内省机制

正如上文所示,客户端通过调用像 Calculator.add 这样的远程方法来实现分布式计算。但是客户端如何知道服务端可以提供什么样的方法?客户端怎么样调用远程方法,方法的签名是什么?如何获取关于远程方法的帮助信息? XML-RPC 内省机制(introspection)可以回答这些问题,客户端只需要调用服务端的"system.listMethods", "system.methodSignature" 和 "system.methodHelp"就能得到答案。Apache XMl-RPC 支持这种内省特性,只需要在服务端进行简单的配置即可。对于部署在 Servlet 容器上的应用,需要继承 XmlRpcServlet,获取 PropertyHandlerMapping 对象,并将其添加到系统句柄对象 XmlRpcSystemImpl,详见代码清单 18。


清单 18. 定制 XmlRpcServlet
    public class MyXmlRpcServlet extends XmlRpcServlet {                 protected XmlRpcHandlerMapping newXmlRpcHandlerMapping()                         throws XmlRpcException {                         PropertyHandlerMapping mapping =                            (PropertyHandlerMapping) newXmlRpcHandlerMapping();                        XmlRpcSystemImpl.addSystemHandler(mapping);                 }     } 

如果使用的是嵌入式 WebServer,修改方法与之相似。完整代码见附带的 Eclipse 项目 XML-RPC-EmbeddedWebServer\src\ dw.webserver, 代码片段见清单 19。为了演示启用内省机制前后服务端提供的远程方法的差异,笔者特意在代码中调用了 PropertyHandlerMapping 的 getListMethods() 方法,并在服务端控制台把远程方法打印出来。可以通过此种方式,对启用内省配置前后服务端提供的方法进行比较。


清单 19. 嵌入式 WebServer 内省配置
       WebServer webServer = new WebServer(port);                  XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();                PropertyHandlerMapping phm = new PropertyHandlerMapping();           phm.load(Thread.currentThread().getContextClassLoader(),                    "MyHandlers.properties");           String[] ss=phm.getListMethods();           System.out.println("可以通过 XML-RPC 调用的方法(添加 system 句柄前)");           for(String s:ss){           System.err.println(s);           }           xmlRpcServer.setHandlerMapping(phm);           XmlRpcSystemImpl.addSystemHandler(phm);           Thread.sleep(2000);           System.out.println("可以通过 XML-RPC 调用的方法(添加 system 句柄后)");           ss=phm.getListMethods();           for(String s:ss){           System.err.println(s);           } 

下面的单元测试类代码清单 20 演示如何在客户端调用内省方法 system.listMethods,运行该单元测试类可以打印服务端提供的远程方法列表。更多的例子在附带的源代码项目中可查。


清单 20. 客户端测试内省配置
   @Test  public void testSystem_listMethods() {  Object[] params = new Object[0];  try {  Object[] resultArray = (Object[]) client.execute( "system.listMethods",params);  } catch (XmlRpcException e) {  e.printStackTrace();  org.junit.Assert.fail(e.getMessage());  }  org.junit.Assert.assertTrue(resultArray.length > 0);  for (int i = 0; i < resultArray.length; i++) {  System.err.println(i + "#.   " + resultArray[i]);  }  } 

结束语

通过上面的介绍,我们了解了 XML-RPC 协议规范。通过使用网络数据包捕获工具 SmartSniff, 捕获 XML-RPC 的请求与响应消息并进行了细致的研究。然后以 XML-RPC 的 Apache 实现为例,演示了如何开发 XML-RPC 客户端及服务端 Java 代码。最后对 Apache XML-RPC 的高级特性进行了介绍。相信读者们,如果想实现简单的分布式计算,现在应该很清楚如何操作。对于使用 JIRA、TestLink 等提供 XML-RPC 接口的开源工具的读者,如果想调用其开放 XML-RPC 接口,也是很容易的事情。


参考资料

学习

  • 访问 XML-RPC 官方站点,了解协议规范的详细内容。 

  • 访问 Apache XML-RPC 官方站点,下载 Apache XML-RPC 实现的软件包,阅读使用文档等。

  • 查看文章 “Java 编程中的 XML-RPC”,了解 XML-RPC 的另一种 Java 实现 Marquee XML-RPC。 

  • 访问 SmartSniff 站点,了解更多关于 TCP/IP 网络数据包捕获工具 SmartSniff 的知识。

  • 访问Http Client 站点 和 Codec 站点,下载 Apache XML-RPC 依赖的第三方软件包。

  • Web services 官方网站

  • 在 developerWorks 的 SOA 专区,获取提高您技能所需的资源。

  • IBM developerWorks 中国 WebSphere 专区:为使用 WebSphere 产品的开发人员准备的技术信息和资料。这里提供产品下载、how-to 信息、支持资源以及免费技术库,包含 2000 多份技术文章、教程、最佳实践、IBM Redbook 和在线产品手册。

获得产品和技术

  • 最受欢迎的 WebSphere 试用软件下载:下载关键 WebSphere 产品的免费试用版。

  • IBM developerWorks 软件下载资源中心:IBM deveperWorks 最新的软件下载。

  • IBM developerWorks 工具包:下载关键 WebSphere 最新的产品工具包。

讨论

  • 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。

  • 加入 IBM 软件下载与技术交流群组,参与在线交流。

关于作者

祝尚元,自 2006 年参加工作,先后在浪潮软件、IBM 中国研发中心和海信传媒从事软件的性能测试框架、自动化测试平台的设计与架构工作。

原创粉丝点击