Rpc与RMI服务,java高级软件工程师课程

5.RMI:rmi可以说是java中最早的RPC框架之一,且此rpc框架由sun团队开发,集成于JDK中,可以说完全可以实现开箱即用,不需要任何外部jar依赖,但由于早期的rpc实现,且设计上并不是为了解决互联网企业类的高并发问题,所以不建议现在互联网开发中作为rpc实现

RMI框架基本使用

RMI既然是java团队设计出来的rpc框架,虽然现在已经不适合企业级生产使用,但是其中的思想和规范值得学习,我们就来看看RMI框架如何使用吧:

RMI三大基本类

实现RMI所需要的API基本都在三大类中,如下:

java.rmi:提供客户端需要的类、接口和异常;

java.rmi.server:提供服务端需要的类、接口和异常;

java.rmi.registry:提供注册表的创建以及查找和命名远程对象的类、接口和异常 ;

构建RMI服务端

首先在RMI中服务端供客户端调用的实例称之为远程对象,而在RMI中实现了java.rmi.Remote接口的类或者继承了java.rmi.Remote接口的都是RMI的远程对象。那么我们来定义一个接口,继承java.rmi.Remote

/**
*用户处理器
**/
public interface UserHandler extends Remote {
String getUserName(int id) throws RemoteException;
String getUserPassWord() throws RemoteException;
User getUserByName(String name) throws RemoteException;
}

这里需要注意的一点是,继承了Remote接口的接口中定义的所有的方法必须抛出RemoteException异常,并且该接口的实现类必须直接或者间接继承java.rmi.server.UnicastRemoteObject类,该类中提供了很多支持RMI的方法,可以通过JRMP协议导出一个远程对象的引用,生成动态代理构建的Stub对象,实现代码如下:

public class UserHandlerImpl extends UnicastRemoteObject implements UserHandler {
//这里因为集继承了UnicastRemoteObject类,其构造器要抛出RemoteException,所以申明构造
public UserHandlerImpl() throws RemoteException {
super();
}

@Override
public String getUserName(int id) throws RemoteException {
return “pdc”;
}
@Override
public String getUserPassWord() throws RemoteException{
return 654321;
}
@Override
public User getUserByName(String name) throws RemoteException{
return new User(name, 654321);
}
}

这里我们构造了一个User实体,为了能实现远程传输,所以这里我们将其进行序列化:

public class User implements Serializable {
private static final long serialVersionUID = 42L;

private String name;
private String passWord;

public String getName(){
return this.name;
}

public String getPassWord(){
return this.passWord;
}

public void setName(String name){
this.name = name;
}

public void setPassWord(String passWord){
this.passWord = passWord;
}

public User(String name, String passWord) {
this.name = name;
this.passWord = passWord;
}
}

需要注意的一点是,如果jdk版本低于1.5,需要手动运行rmic命令生成实现类的Stub对象,而1.5开始使用动态代理技术,已经可以自动生成Stub对象了,做完这些就可以启动服务端了:

UserHandler userHandler = null;
try {
userHandler = new UserHandlerImpl();
Naming.rebind(“user”, userHandler);//将当前的实例与名称为user绑定,后面客户端调用查找对应的名称
System.out.println(" RMI 服务端启动成功");
} catch (Exception e) {
System.err.println(" RMI 服务端启动失败");
e.printStackTrace();
}

构建RMI注册表

其实所谓注册表就是保存了RMI服务端启动与绑定的名称的进程,由于jdk已经把RMI代码集成到了JDK中,RMI的注册表其实不需要写任何代码,在JDK的bin目录下已经存在一个叫rmiregistry.exe的程序,不过我们需要在当前的class类路径下启动注册表(所以需要注意JAVA_HOME环境变量一定要配置成功) ,来到class类路径下,输入命令:

rmiregistry 9999

即可指定rmi的注册表在9999端口中运行,如果不指定端口,默认使用1099,当然不想让RMI的注册表在前台显示,也可以输入后台运行命令:

start rmiregistry

构建RMI客户端

前面服务端和注册表都已经运行起来了,接下来我们需要的就是客户端发起访问的请求了,需要注意的是,User实例类和UserHandler接口在客户端代码中也有一份(企业开发过程中会依赖同一份代码),所以这里的客户端调用代码如下:

try {
UserHandler handler = (UserHandler) Naming.lookup(“user”);//这里使用的user是服务端启动的时候绑定的名称
String passWord = handler.getUserPassWord();
String name = handler.getUserName(1);
System.out.println("name: " + name);
System.out.println("passWord: " + passWord);
System.out.println("user: " + handler.getUserByName(“pdc”));
} catch (Exception e) {
e.printStackTrace();
}

这样就可以获取到服务端的远程对象的信息了,当然这里有两点需要注意:

1.这里的UserHandler实体类和服务端的UserHandler接口所在的包名需要一致,即使用的限定全类名需要一致,否则会报如下的错误:

![userHandler包名不一致导致的报错](https://upload-images.jianshu.io/upload_images/

【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】

开源分享完整内容戳这里

15590149-f3f4c68611d2dc1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2.我们这里获取的User实例属于引用类型,需要注意的是获取到的User实例对象也必须和服务端的包名一致,即限定全类名相同,否则,handler.getUserByName(“pdc”)方法调用的结果会报错,如下所示:

User包名不一致导致报错

自定义启动RMI注册表

在本篇的结尾,我们来个彩蛋,还记得上面RMI的注册表吗?前面我们是通过JDK的exe程序启动的,那么我们能不能自己开发或者自己启动RMI注册表呢?其实是可以的,在java.rmi.registry包中有个Registry接口,并且该接口有个默认的实现类LocateRegistry,其实JDK源码中Naming类就是使用的LocateRegistry实现的注册和调用,那么我们来看看LocateRegistry的方法:

createRegistry(int port)
createRegistry(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)
getRegistry()
getRegistry(int port)
getRegistry(String host)
getRegistry(String host, int port)
getRegistry(String host, int port, RMIClientSocketFactory csf)

可以看到这里有两个创建注册表的方法,一个只有端口,开启的默认是本机的注册表,另外一个是可以输入ip,端口,以及一些连接策略的自定义注册表,还有几个获取注册表的方法,很明显这里提供了注册表的创建和调用的方法,同样的我们之前的服务端代码只要稍微改动一下,如下:

UserHandler userHandler = null;
Registry registry = LocateRegistry.createRegistry(9999);;
try {
userHandler = new UserHandlerImpl();
registry.rebind(“user”, userHandler);//将当前的实例与名称为user绑定,后面客户端调用查找对应的名称
System.out.println(" RMI 服务端启动成功");
} catch (Exception e) {
System.err.println(" RMI 服务端启动失败");
e.printStackTrace();
}

很明显申明一下注册表,并且使用注册表替换Naming来绑定服务实例即可,客户端亦是如此,修改后的代码如下:

try {

Registry registry=LocateRegistry.getRegistry(“127.0.0.1”,9999);
UserHandler handler = (UserHandler) registry.lookup(“user”);//这里使用的user是服务端启动的时候绑定的名称
String passWord = handler.getUserPassWord();
String name = handler.getUserName(1);
System.out.println("name: " + name);
System.out.println("passWord: " + passWord);
System.out.println("user: " + handler.getUserByName(“pdc”));
} catch (Exception e) {
e.printStackTrace();
}

stem.out.println("name: " + name);
System.out.println("passWord: " + passWord);
System.out.println("user: " + handler.getUserByName(“pdc”));
} catch (Exception e) {
e.printStackTrace();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值