XML-RPC 的 Apache 实现

前端之家收集整理的这篇文章主要介绍了XML-RPC 的 Apache 实现前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

-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 协议消息

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 请求消息

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


表 1. 配置 XmlRpcClient
名称 描述
ClientConfig 该对象是 XmlRpcClientConfig 的实例,包括如下原子属性,比如服务端 URL,认证信息,编码字符集等
TransportFactory 该工厂类用于创建负责使用客户端配置与服务端通讯的对象。下文会介绍常用的传输工厂类。
XmlWriterFactory XmlWriter 用于创建 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,更加便携,适用于为非 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"))。

在客户端编写代码调用服务端远程方法时,需要为 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 请求消息体和响应消息体中 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>

正如上文所示,客户端通过调用像 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 接口,也是很容易的事情。


参考资料

学习

获得产品和技术

讨论

关于作者

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

猜你在找的XML相关文章