您的位置:首页 > 编程语言 > Java开发

java远程调用中的RMI构建远程服务

2016-08-19 01:27 429 查看
在程序设计中,我们经常会遇到多进程交互,多进程协同工作,分布式任务处理等这样的场景,在这些场景中我们都会涉及到远程通信与远程调用,下面我们用java提供的rmi远程调用来构建一个远程服务


一、RMI用法

java语言提供了一种很简便的远程调用,就是RMI,下面我们来看一下RMI的用法

首先,我们定义一个接口

/**
* 这个接口必须继承自java.rmi.Remote
*/
public interface IRmiService extends Remote {
/**
* 接口方法需要抛出RemoteException异常
*/
public String getName() throws RemoteException;
}


其次,我们定义接口的实现


/**
* 接口的实现类必须继承自java.rmi.server.UnicastRemoteObject
*/
public class RmiServiceImpl extends UnicastRemoteObject implements IRmiService {
private static final long serialVersionUID = 1L;
public RmiServiceImpl() throws RemoteException {
super();
}
@Override
public String getName(){
System.out.println("service ");
return getClass().getName();
}
}


最后,我们分别写服务端服务监听和客户端请求的代码
服务端:


//监听端口
int port = 20010;
LocateRegistry.createRegistry(port);

//定义接口服务的具体实例对象
IRmiService rmiService = new RmiServiceImpl();

//服务名称,绑定服务
String serviceName = "rmi://127.0.0.1:" + port + "/IRmiService";
Naming.bind(serviceName, rmiService);
System.out.println("server listening ...");


客户端:


int port = 20010;
//服务名称
String serviceName = "rmi://127.0.0.1:" + port + "/IRmiService";
IRmiService rmiService = (IRmiService)Naming.lookup(serviceName);
String name = rmiService.getName();
System.out.println("name is " + name);


先运行服务端一直在运行中,并且输出为:


server listening ...


再运行客户端,客户端输出为:


name is com.java.RmiServiceImpl


同时服务端也有输出:


server listening ...
service


二、反射调用

java提供了反射技术,利用反射技术,我们能够在预先不知道具体对象以及具体函数的时候,先写上一些通用的调用代码,然后在运行时动态决定具体调用某个对象的某个函数

首先,我们定义一个通用调用类

/**
* 通用的调用池子,只要注册类的实现,就可以动态控制调用了
*/
public class CommonInvoke {
/**
* 存放接口(类)与具体实现的对应关系
*/
private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
/**
* 注册实现
*/
public void register(Class<?> clasz, Object obj) {
map.put(clasz, obj);
}
/**
* 根据接口(类),函数名,参数类型,参数值,就可以调用被绑定到这个接口(类)的实例的该函数
*/
public Object invoke(Class<?> clasz, String methodName, Class<?>[] paramTypes, Object[] params)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
Method method = clasz.getDeclaredMethod(methodName, paramTypes);
Object obj = map.get(clasz);
return method.invoke(obj, params);
}
}


其次,我们定义一个接口及其两个实现类


/**
* 接口
*/
public interface IMethodService {
String getName();
}
/**
* 实现一
*/
public class MethodServiceImpl1 implements IMethodService{
@Override
public String getName() {
return "impl1 implements";
}
}
/**
* 实现二
*/
public class MethodServiceImpl2 implements IMethodService{
@Override
public String getName() {
return "impl2 implements";
}
}


最后,我们来检测一下动态绑定与动态调用


IMethodService service1 = new MethodServiceImpl1();
IMethodService service2 = new MethodServiceImpl2();
CommonInvoke invoker = new CommonInvoke();

invoker.register(IMethodService.class, service1);
Object result1 = invoker.invoke(IMethodService.class, "getName", new Class<?>[0], new Object[0]);
System.out.println("bind service1 result :[" + result1 + "]");

invoker.register(IMethodService.class, service2);
Object result2 = invoker.invoke(IMethodService.class, "getName", new Class<?>[0], new Object[0]);
System.out.println("bind service2 result :[" + result2 + "]");


运行结果为:


bind service1 result :[impl1 implements]
bind service2 result :[impl2 implements]


可见,在函数调用代码不变的情况下,我们通过某些绑定工作,就可以改变具体的执行行为


三、两者结合

当我们把通用函数调用中的这个方法写到RMI接口中的时候,我们就可以把远程调用做成通用服务了

/**
* 接口
*/
public interface IRmiService extends Remote {
/**
* 通用调用函数放在rmi接口中
*/
public Object invoke(Class<?> clasz, String methodName, Class<?>[] paramTypes, Object[] params) throws Exception;
}
/**
* 实现
*/
public class RmiServiceImpl extends UnicastRemoteObject implements IRmiService {
private static final long serialVersionUID = 1L;
public RmiServiceImpl() throws RemoteException {
super();
}
/**
* 因多线程服务,所以需要做成线程安全的map
*/
private Map<Class<?>, Object> map = new ConcurrentHashMap<Class<?>, Object>();
public void register(Class<?> clasz, Object obj) {
System.out.println("接口:[" + clasz.getName() + "]服务对象:[" + obj + "]");
map.put(clasz, obj);
}
@Override
public Object invoke(Class<?> clasz, String methodName, Class<?>[] paramTypes, Object[] params) throws Exception {
Method method = clasz.getDeclaredMethod(methodName, paramTypes);
Object obj = map.get(clasz);
System.out.println("对象:[" + obj + "]准备服务");
return method.invoke(obj, params);
}
}


现在服务端需要稍作改动:


int port = 20010;
LocateRegistry.createRegistry(port);
RmiServiceImpl rmiService = new RmiServiceImpl();
String serviceName = "rmi://127.0.0.1:" + port + "/IRmiService";
Naming.bind(serviceName, rmiService);

rmiService.register(IMethodService.class, new MethodServiceImpl1());
System.out.println("server listening ...");


服务端输出为:


接口:[com.java.IMethodService]服务对象:[com.java.MethodServiceImpl1@3fd8e081]
server listening ...


客户端增加一个代理类:


/**
* 定义客户端代理
*/
public class DynamicServiceDel implements IMethodService {
private IRmiService rmiService;
public DynamicServiceDel() throws MalformedURLException, RemoteException, NotBoundException {
int port = 20010;
String serviceName = "rmi://127.0.0.1:" + port + "/IRmiService";
rmiService = (IRmiService) Naming.lookup(serviceName);
}
/**
* 利用rmi来实现具体的调用逻辑
*/
public String getName() {
String result1 = null;
try {
result1 = (String) rmiService.invoke(IMethodService.class, "getName", new Class<?>[0], new Object[0]);
} catch (Exception e) {
e.printStackTrace();
}
return (String) result1;
}
}


客户端调用代码为:


IMethodService service = new DynamicServiceDel();
String result = service.getName();
System.out.println("result is : " + result);


客户端输出为:


result is : impl1 implements


同时服务端也有输出:


接口:[com.java.IMethodService]服务对象:[com.java.MethodServiceImpl1@3fd8e081]
server listening ...
对象:[com.java.MethodServiceImpl1@3fd8e081]准备服务


四、接口动态代理

java提供了一种动态代理机制,在只定义了接口的时候,我们可以通过动态代理的方式,生成一个新的类,让这个类来实现该接口,具体实现逻辑可以在运行的时候决定

首先,我们定义一个辅助类,定义通用实现逻辑

/**
* 利用handler来辅助创造对象
*/
public class MyHandler implements InvocationHandler {
/**
* 接口
*/
private Class<?> interfaces;
public MyHandler(Class<?> interfaces) {
this.interfaces = interfaces;
}
/**
* 利用动态代理,实例化接口的一个对象
*/
public Object getObj() {
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { interfaces }, this);
}
/**
* 构造方法的执行内容
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return getClass().getName();
}
}


其次,定义若干接口


/**
* 定义接口一
*/
public interface IDynamicService1 {
String getName();
}
/**
* 定义接口二
*/
public interface IDynamicService2 {
String getName();
}


最后试试通用实例化逻辑:


IDynamicService1 service1 = (IDynamicService1) new MyHandler(IDynamicService1.class).getObj();
System.out.println("name1 is : " + service1.getName());
System.out.println(service1.getClass() + " interfaces:" + Arrays.toString(service1.getClass().getInterfaces()));

IDynamicService2 service2 = (IDynamicService2) new MyHandler(IDynamicService2.class).getObj();
System.out.println("name2 is : " + service2.getName());
System.out.println(service2.getClass() + " interfaces:" + Arrays.toString(service2.getClass().getInterfaces()));


结果为:


name1 is : com.java.MyHandler
class com.sun.proxy.$Proxy0 interfaces:[interface com.java.IDynamicService1]
name2 is : com.java.MyHandler
class com.sun.proxy.$Proxy1 interfaces:[interface com.java.IDynamicService2]


五、接口动态代理与RMI及方法调用结合

在”三”中,每当我们添加一个接口的时候,我们就需要在客户端增加一个代理类,仔细查看发现,每个代理类中逻辑都是一样的,就是查询远程的rmi服务对象,并调用它的一个方法,就是实现的接口不一样,而在”四”中,我们正好解决了这个问题,因此在”三”中,我们加一个客户端通用代理辅助类:

/**
* 通用代理辅助类
*/
public class DeleHandler implements InvocationHandler {
private Class<?> interfaces;
private static IRmiService rmiService;
static {
try {
int port = 20010;
String serviceName = "rmi://127.0.0.1:" + port + "/IRmiService";
rmiService = (IRmiService) Naming.lookup(serviceName);
} catch (Exception e) {
e.printStackTrace();
}
}
public DeleHandler(Class<?> clasz) {
this.interfaces = clasz;
}
public Object getObj() {
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{interfaces}, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return rmiService.invoke(interfaces, method.getName(), method.getParameterTypes(), args);
}
}


客户端的调用就变成了:


DeleHandler handler = new DeleHandler(IMethodService.class);
IMethodService service  = (IMethodService) handler.getObj();
String result = service.getName();
System.out.println("result is : " + result);


结果依然跟之前的一样


于是一个简单的RMI的服务就完成了

当然这里面只有固定ip和端口的定向调用,如果我们加一个zookeeper的注册功能,然后再调用的时候,做一个服务负载,那么一个分布式服务框架就出来了

当然,这里面只是用了简单几个类的代理讲解一个服务的核心逻辑,个中还有许多细节需要优化,如异常处理,超时控制,服务注册,稳定性,并发控制等相关的逻辑,具体需要在项目实施的时候去考虑
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息