前言
RMI——Remote Method Invocation,它是Java提供的一组开发分布式应用程序的API,其运用的设计模式的思想是代理模式。
RMI由Java提供,这跟RPC的区别就很明显了,RPC(Remote Procedure Call)远程过程调用是可以由不同的语言实现,RMI是面向对象的,某种意义上来说(目的一样,都是方便程序员进行分布式开发),在Java语言中,RMI是RPC下的一种具体体现。
在维基百科中有这么一段解释 :
The Java Remote Method Invocation (Java RMI) is a Java API that performs remote method invocation, the object-oriented equivalent of remote procedure calls (RPC), with support for direct transfer of serialized Java classes and distributed garbage collection.
根据上述定义,RPC其实更像一种协议,在这个协议下有着各个版本的具体实现,常见的 RPC 框架有阿里的 Dubbo,微博开源的 Motan,Google 开源的 gRpc,百度开源的 brpc,蚂蚁金服开源的 sofa-rpc 等等。
RMI的通信过程
在维基百科中,有如下的解释,意思是指在java 1.2之前,与Stub对象对话的是Skeleton对象,在Stub对象将调用传递给Skeleton的过程中,其实这个过程是通过JRMP协议实现转化的,通过这个协议将调用从一个虚拟机转到另一个虚拟机。在Java 1.2之后,与Stub对象直接对话的是Server程序,而Skeleton对象被移除了。*
A typical implementation model of Java-RMI using stub and skeleton objects. Java 2 SDK, Standard Edition, v1.2 removed the need for a skeleton.
关于Stub(存根)对象是存在于本地的,它客户端也不是直接和Server交互,而是通过这个Stub进行交互的,而这个地方也是代理模式的体现,一方面出于安全的考虑一方面方便模块的解耦。Stub实现了远程对象向外暴露的接口,它的方法和远程对象暴露的方法的签名(方法名,方法的参数类型)是相同的。
RMI的实现(运用了代理模式和外观模式的设计思想)
关于RMI的实现,Java提供了几种方式,这里是通过实现Remote接口实现的。程序的背景模拟一个买卖系统的流程,在实践过程中所遇到的问题也一并记录下来:
在facade包下有账号服务,货品服务,账单服务和支付服务,都很简单,只是在控制台打印简单的信息而已,这一部分大家可以选择性的忽略:
package facade;
//账号服务
public class AccountService {
private static int isLogin = 0;
public boolean login(){
isLogin = 1;
System.out.println("登录成功");
return true;
}
public boolean valid(){
if(isLogin == 0){
System.out.println("您未登录");
return false;
}
System.out.println("您已登录");
return true;
}
public void serviece(){
System.out.println("账户服务被调用");
}
}
//货品服务
public class GoodsService {
public int getHouse() {
return house;
}
public void setHouse(int house) {
this.house = house;
}
public int getCar() {
return car;
}
public void setCar(int car) {
this.car = car;
}
private int house = 0;
private int car = 0;
public void service(){
System.out.println("货物服务被调用");
}
}
//订单服务
public class OrderService {
private int housePrice = 5000000;
private int carPrice = 400000;
public void service(){
System.out.println("订单服务被调用");
}
public int order(int countHouse, int countCar){
if(countHouse != 0)
System.out.println("您购买了"+countHouse+"套房子,单价为"+housePrice);
if(carPrice != 0)
System.out.println("您购买了"+carPrice+"辆车,单价为"+housePrice);
int momey = countHouse * housePrice + carPrice * countCar;
System.out.println("总价格为:"+momey+"元");
return momey;
}
}
//支付服务
public class PayService {
public void service(){
System.out.println("支付服务被调用");
}
public boolean pay(int money, int countMoney){
if(money < countMoney){
System.out.println("您支付的金额小于总价请继续支付");
return false;
}
if(money > countMoney){
int price = money - countMoney;
System.out.println("您支付的金额大于总金额,将退还"+price+"元");
}
return true;
}
}
基础服务就到这里了,接下来是一个外观接口,为了支持RMI,该接口需要继承Remote接口:
package facade;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface CustomerInterface extends Remote {
void shoppingService() throws RemoteException;
void setMoney(int money) throws RemoteException;
void setCoutHourse(int coutHourse) throws RemoteException;
void setCountCar(int countCar) throws RemoteException;
void login() throws RemoteException;
void service() throws RemoteException;
}
该接口的实现:在这个代码中要注意LocateRegistry.createRegistry(port);这个方法,该方法是创建本地注册表,如果你没有这段代码,你需要手动启动,在Java的bin文件夹下的rmiregistry.exe,否则,会抛出拒接连接的异常。
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
public class CustomerFacade extends UnicastRemoteObject implements CustomerInterface {
private AccountService accountService;
private GoodsService goodsService;
private OrderService orderService;
private PayService payService;
public void setMoney(int money) {
this.money = money;
}
private int money;
private int countPrice;
public void setCoutHourse(int coutHourse) {
this.coutHourse = coutHourse;
}
public void setCountCar(int countCar) {
this.countCar = countCar;
}
private int coutHourse;
private int countCar;
public CustomerFacade() throws RemoteException {
super();
intit();
}
private void intit() {
accountService = new AccountService();
goodsService = new GoodsService();
orderService = new OrderService();
payService = new PayService();
}
public void service(){
System.out.println("开始购物");
}
@Override
public void shoppingService() throws RemoteException {
boolean result = isLogin();
if(result){
toGoodsService();
toOrderService();
toPayService();
}else{
System.out.println("");
}
}
private void toPayService() {
payService.service();
payService.pay(money, countPrice);
}
private void toOrderService() {
orderService.service();
countPrice = orderService.order(coutHourse,countCar);
}
public void login() {
accountService.login();
}
private void toGoodsService() {
goodsService.service();
if(countCar != 0)
goodsService.setCar(countCar);
if(coutHourse != 0)
goodsService.setCar(coutHourse);
}
private boolean isLogin() {
return accountService.valid();
}
public static void main(String[] args) throws Exception {
String port = "1099";
String host = "localhost";
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
CustomerFacade facade = new CustomerFacade();
/**
* 创建一个Registry对象.
* LocateRegistry用于获取名字服务或创建名字服务.
* 调用LocateRegistry.createRegistry(int port)方法可以在某一特定端口创建名字服务,
* 从而用户无需再手工启动rmiregistry
*/
LocateRegistry.createRegistry(1099);
Naming.bind("//" + host + ":" + port + "/CustomerFacade",
facade);
System.out.println("Service Bound...");
}
}
另外,由于Java的安全策略原因,也会造成程序运行出现异常,此时要到Java文件下的jre\lib\security目录找到java.policy文件,在最后添加如下语句,表示允许连接的端口
permission java.net.SocketPermission "*:1024-65535",
"connect,accept,resolve";
permission java.net.SocketPermission "*:1-1023",
"connect,resolve";
接下来是关于客户端的程序,这里要注意一下,由前言我们可以知道,客户端是通过存根跟服务器打交道的,所以,客户端需要一个类来模拟服务端,让客户端认为它是直接跟服务端打交道的,在我们这个程序中,承担这个任务的就是CustomerInterface接口。
该接口提供的方法和服务端的CustomerInterface接口一毛一样,直接复制粘贴过来就行。这里要注意一下!!!!改程序的全限定名要一致!否则客户端运行会出现类型无法转换的问题,也就是从包名到类名要跟服务端的程序一致。可以将客户端程序放在另一个项目中~
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import facade.CustomerInterface;
public class CustomerClient {
public static void main(String[] args) {
String port = "1099";
String host = "localhost";
CustomerInterface facade;
try {
//CustomerFacade是服务端绑定的一个标志,你也可以修改成别的
//通过Naming.lookup返回一个引用,而facade对象在得到引用后就是我们的Sub,通过facade我们可以访问远程的服务了
facade = (CustomerInterface) Naming.lookup ("rmi://" +host + ":" + port + "/CustomerFacade");
facade.shoppingService();
facade.login();
facade.service();
facade.setCountCar(5);
facade.setCoutHourse(2);
facade.shoppingService();
facade.setCoutHourse(12000000);
facade.shoppingService();
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
——————————————