远程调用方式就是尽可能地使系统间的通信和系统内一样,让使用者感觉调用远程同调用本地一样,但其实没没有办法做到完全透明,例如由于远程调用带来的网络问题、超时问题、序列化/反序列化问题、调式复杂的问题等。
基于Java自身技术
在Java中实现远程调用方式的技术主要有RMI和WeService两种。
RMI
RMI(Remote Method Invocation)是Java用于实现透明远程调用的重要机制。在远处调用中,客户端仅有服务端提供的接口,通过此接口实现对远程服务器端的调用。远程调用基于网络通信来实现,RMI同样如此,其机制如图1.1所示:
Sun JDK6.0 以前版本的RMI实现均是基于TCP/IP+BIO方式,RMI服务器端通过启动RMI注册对象在一个端口上监听对外提供的接口,其实现实例以字符串的方式绑定到RMI注册对象上。RMI客户端通过proxy的方式代理了对服务器端接口的访问,RMI客户端将要访问的服务器端对象字符串、方法和参数封装成一个对象,序列化成流后通过TCP/IP+BIO传输到RMI服务器端。RMI服务器端接收到客户端的请求对象后,解析其中的对象字符串、方法及参数,通过对象字符串从RMI注册对象上找到提供业务功能的实例,之后结合要访问的方法来反射获取到方法示例对象,传入参数完成对服务端对象实例的调用,返回的结果则序列化为流以TCP/IP+BIO方式返回给客户端,客户端在接收到此流后反序列化为对象,并返回给调用者。
RMI要求服务器端的接口继承Remote接口,接口上的每种方法必须抛出RemoteException,服务器端业务类通过实现此接口提供业务功能,然后通过调用UnicastRemoteObject.exportObject来将此对象绑定到某端口上,最后将此对象注册到本地的LocateRegistry上,此时形成了一个字符串对应于对象实例的映射关系。基于RMI实现示例的服务器端代码如下:
//服务器端对外提供的接口
public interface Business extends Remote {
public String echo(String message) throws RemoteException;
}
// 服务器端实现此接口的类
public class BusinessImpl implements Business {
public String echo(String message) throws RemoteException {
if (“quit”.equals(message.toString)) {
System.exit(0);
}
return “Server response:”+message;
}
}
// 基于RMI的服务器端
public class Server {
public static void main(String[] args) throws Exception {
int port = 9527;
String name=”BusinessDemo”;
Business business = new BusinessImpl();
UnicastRemoteObject.exportObject(business, port);
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind(name, business);
}
}
RMI的客户端首先通过LocateRegistry.getRegistry来获取Registry对象,然后通过Registry.lookup字符串获取要调用的服务器端接口的实例对象,最后以接口的方式透明地调用远程对象的方法。基于RMI实现客户端的关键代码如下:
Registry registry = LocateRegistry.getRegistry(“localhost”);
String name = “BusinessDemo”;
// 创建BusinessDemo类的代理类,当调用时则调用localhost:1099上名称为BusinessDemo的对象,如服务器端没有对象名称的绑定,则抛出NotBoundException;如通信错误,则抛出RemoteException
Business business = (Business) registry.lookup(name);
从示例代码可以看出,基于RMI实现的客户端和服务端较之基于TCP/IP+NIO等实现的客户端和服务端简单很多,代码可维护性也高很多。
WebService
WebService是一种跨语言的系统间交互标准。在这个标准中,对外提供功能的一方以HTTP的方式提供服务,该服务采用WSDL(Web Service Description Language)描述,在这个文件中描述服务所使用的协议,所期望的参数,返回的参数格式等。调用端和服务端通过SOAP(Simple Object Access Protocol)方式来进行交互。
在Java中使用WebService服务,首先将服务端的服务根据描述生成相应的WSDL文件,应将应用及此WSDL文件放入HTTP服务器中,借助Java辅助工具根据WSDL文件生成客户端stub代码。此代码的作用是将产生的对象请求信息封装为标准的SOAP格式数据,并发送请求到服务器端,服务器端在接收到SOAP格式数据时进行转化,反射调用相应的Java类,整个过程如下图:
Java SE6中集成了WebService,因此可以直接实现该方式的远程调用,服务器端通过@WebService来标记对外暴露的WebService实现类,通过调用Endpoint.publish将此WebService实现发布到指定的HTTP地址上。客户端通过wsimport来访问相应地址的wsdl文件,从而生成调用服务器端的辅助类,应用立即可通过此类来实现远程调用了。基于WebService实现示例中的服务器端代码如下:
// 对外暴露的接口
public interface Business {
// 显示客户端信息,并返回
public String echo(String message);
}
// 通过@WebService来指定对外提供的WebService的名称和客户端生成的类名与包名
@WebService(name=”Business”,serviceName=”BusinessService”,targetNamespace=”http://WebService.chapter1.book/client”)
@SOAPBinding(style=SOAPBinding.Style.RPC)
public class BusinessImpl implements Business {
public String echo(String message) {
if(“quit”.equals(message.toString()) {
System.exit(0);
}
return “Server response:”+message;
}
}
// 发布WebService类
public static void main(String[] args) {
Endpoint.publish(“http://localhost:9527/BusinessService”, new BusinessImpl());
System.out.println(“Server has been started”);
}
// 客户端通过JDK bin目录下的swimport命令来生成辅助调用代码,
wsimport -keep http://localhost:9527/BusinessService?wsdl
// 执行后在当前目录下生成 book/chapter1/WebService/client/Business.java 和 book/chapter1/WebService/client/BusinessService.java 的代码
// 基于这两个生成的代码编写客户端的关键代码如下
BusinessService businessService = new BusinessService();
Business business = businessService.getBusinessPort();
business.echo(command);
WebService传输的数据协议采用SOAP,SOAP对于复杂的对象结构比较难支持,其好处是能够支持跨语言。无论是采用RMI还是WebService,都封装了网络通信的细节,因此使用起来会比较简单,但如果想对通信的细节做一些调优或控制,也会比较麻烦。
基于开源框架
Spring RMI
Spring RMI是Spring Remoting中的一个子框架,基于Spring RMI可以很简单地就实现RMI方式的Java远程调用,Spring RMI的工作原理如下图:
CXF是Apache的*项目,也是Java社区中实现WebService流行的一个开源框架。基于CXF可以非常简单地以WebService的方式来实现Java甚至跨语言的远程调用,CXF的工作原理如图:
CXF对于WebService的封装较轻,还是Java SE的方式,只是提供了一个JaxWsServerFactoryBean类,从而可以在WebService被调用的时候增加一些连接器的处理。客户端方面CXF则增加了封装,能够直接以接口的方式来调用远程的WebService,简化了WebService的调用复杂性。
小结
以上讲解了Java中实现系统间通信的相关技术、基本原理及使用方法。本章并没有介绍其他的系统间通信的技术,比如JMS、EJB等。这些技术都是基于以上那说描述的原理进一步扩展,以提升对于不同场景的需求满足度及易用性。在大型应用中,分布式的概念不仅仅是跨JVM或跨机器的系统间通信,还会覆盖集群、负载均衡、分布式缓存、分布式文件系统等多方面的内容。