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的注册功能,然后再调用的时候,做一个服务负载,那么一个分布式服务框架就出来了
当然,这里面只是用了简单几个类的代理讲解一个服务的核心逻辑,个中还有许多细节需要优化,如异常处理,超时控制,服务注册,稳定性,并发控制等相关的逻辑,具体需要在项目实施的时候去考虑
相关文章推荐
- 调用远程主机上的RMI服务时抛出java.rmi.ConnectException: Connection refused to host: 127.0.0.1异常原因及解决方案
- 调用远程主机上的RMI服务时抛出java.rmi.ConnectException: Connection refused to host: 127.0.0.1异常原因及解决方案
- 调用远程主机上的RMI服务时抛出java.rmi.ConnectException: Connection refused to host: 127.0.0.1异常原因及解决方案
- 调用远程主机上的RMI服务时抛出java.rmi.ConnectException: Connection refused to host: 127.0.0.1异常原因及解决方案
- 调用远程主机上的RMI服务时抛出java.rmi.ConnectException: Connection refused to host: 127.0.0.1异常原因及解决方案
- C#的远程调用与Java的RMI借鉴学习
- 分布式服务架构之java远程调用技术浅析
- Java的RMI(远程方法调用)的实现范例
- Java远程方法调用RMI
- spring整合RMI - Java远程方法调用
- RMI远程方法调用完整示例【java项目】
- Java远程方法调用(RMI)(转)
- java--- RMI远程调用
- Java远程方法调用(RMI)机制
- Java远程调用RMI(Remote Method Invocation)
- Java中RMI远程调用
- Java远程调用(二)实现一个简单的服务框架
- PHP远程调用Java服务
- Java远程方法调用(RMI)
- JAVA 自带的RMI远程调用功能的实现和原理