??RMI 可以让客户端调用位于服务端的暴露方法(暴露方法的具体代码位于服务端),就像是这个暴露方法本来就位于客户端一样。不过要注意的是,虽然位于服务端的暴露方法是由客户端触发调用的,但暴露方法是在服务端运行的,客户端只能为其提供实参,并获得其返回值。
??对于服务端,它需要为自己设置一个端口号,接着设置哪个对象对外暴露,并为每个暴露对象设置一个名称。暴露了这个对象,就相当于暴露了这个对象的 public 方法。然后,RMI 会自动为每个暴露对象生成一个唯一的 URL,URL 将根据服务端 IP、端口号、暴露对象名来生成。
-
编写一个暴露对象接口,这个接口必须继承接口 Remote,而后者是 RMI 提供的接口。因为虽然客户端只需要根据 URL 就可以获得暴露对象,但 Java 的语法要求至少要有一个类型才能接收这个对象。也就是说,所有的暴露对象都必须是一个暴露对象接口的子类,且这个暴露对象接口必须对服务端、客户端都可见。
因此这个暴露对象接口将提供一系列供客户端远程调用的暴露方法。
package org.wangpai.demo.rmi.common;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Expose extends Remote {
Response call(Request request) throws RemoteException;
}
-
编写提供给这个暴露方法的实参、返回值。注意:它们必须实现接口 Serializable,因为通信时,RMI 底层借助了对象的序列化、反序列化。
package org.wangpai.demo.rmi.common;
import java.io.Serializable;
public interface Request extends Serializable {
Object getData();
}
package org.wangpai.demo.rmi.common;
import java.io.Serializable;
public interface Response extends Serializable {
Object getData();
}
-
对于服务端的具体暴露对象所属的类,它必须还要将类 UnicastRemoteObject 继承,同时实现上面的暴露对象接口。对于服务端的这个具体暴露类,不必对客户端可见,因此服务端可以对其自由拓展。
package org.wangpai.demo.rmi.server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import org.wangpai.demo.rmi.common.Expose;
import org.wangpai.demo.rmi.common.Request;
import org.wangpai.demo.rmi.common.Response;
public class Service extends UnicastRemoteObject implements Expose {
protected Service() throws RemoteException {
super();
}
@Override
public Response call(Request request) throws RemoteException {
System.out.println("------ 接收到客户端的数据 -------");
System.out.println(request.getData());
System.out.println("---------------------------");
return () -> "Hello, Client.";
}
}
-
在服务端创建这个暴露类的对象,并注册在 RMI 中。
package org.wangpai.demo.rmi.server;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import org.wangpai.demo.rmi.common.Protocol;
public class Server {
public static void start() throws RemoteException, AlreadyBoundException {
var registry = LocateRegistry.createRegistry(Protocol.SERVER_PORT);
var service = new Service();
registry.bind(Protocol.SERVICE_URL, service);
}
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
start();
}
}
这里,服务端需要与客户端进行一些约定,如服务端端口号、暴露对象的 URL 等。
package org.wangpai.demo.rmi.common;
public class Protocol {
public final static int SERVER_PORT = 7777;
public final static String SERVER_BASE_URL = "rmi://127.0.0.1:" + SERVER_PORT + "/";
public final static String SERVICE_URL = "service";
}
-
现在可以尝试在客户端进行远程调用了。
package org.wangpai.demo.rmi.client;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import org.wangpai.demo.rmi.common.Expose;
import org.wangpai.demo.rmi.common.Protocol;
import org.wangpai.demo.rmi.common.Request;
public class Client {
public static void remoteCall() throws MalformedURLException, NotBoundException, RemoteException {
System.out.println("************ 连接远程服务端 ***********");
var service = (Expose) Naming.lookup(Protocol.SERVER_BASE_URL + Protocol.SERVICE_URL);
System.out.println("************ 远程服务端连接成功 ***********");
System.out.println("************ 开始远程调用 ***********");
var response = service.call((Request) () -> "Hello, I'm a client.");
System.out.println("************** 远程调用结束 ********************");
System.out.println("------ 接收到服务端的数据 -------");
System.out.println(response.getData());
System.out.println("---------------------------");
}
public static void main(String[] args) throws MalformedURLException, NotBoundException, RemoteException {
remoteCall();
}
}
-
注意:项目运行的时候肯定是服务端先启动,然后客户端才能运行。
-
客户端运行效果图:
-
服务端运行效果图: