本篇内容主要讲解“怎么通过Java代码来理解RPC”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么通过Java代码来理解RPC”吧!
这整个过程可以用一句话概括:机器 A 通过网络与机器B建立连接,A 发送一些参数给 B,B 执行某个过程,并把结果返回给 A。
“在写代码之前,先说一个前置背景,假设我们有一个商品类:
public class Product implements Serializable { private Integer id; private String name; public Product(Integer id, String name) { this.id = id; this.name = name; } //toString() //get set 方法 }
有一个商品服务接口:
public interface IProductService { Product getProductById(Integer id); }
服务端有商品服务接口的实现类:
public class ProductServiceImpl implements IProductService { @Override public Product getProductById(Integer id) { //实际上这里应该去查询数据库获得数据,下面简化了 return new Product(id, "手机"); } }
下面我们通过客户端发送一个商品 id 到服务端,服务端获得 id 后通过商品服务类获取商品信息,返回给客户端:
public class Client { public static void main(String[] args) throws Exception { //建立Socket Socket socket = new Socket("127.0.0.1", 8888); //获取输出流 ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); //把商品Id通过网络传到服务端 dos.writeInt(123); socket.getOutputStream().write(baos.toByteArray()); socket.getOutputStream().flush(); //读取服务端返回的商品信息 DataInputStream dis = new DataInputStream(socket.getInputStream()); Integer id = dis.readInt(); //商品id String name = dis.readUTF(); //商品名称 Product product = new Product(id, name);//通过服务端返回的商品信息生成商品 System.out.println(product); //关闭流资源为了方便阅读,没有做try-catch处理 dos.close(); baos.close(); socket.close(); } } public class Server { private static boolean running = true; public static void main(String[] args) throws Exception { //建立服务端Socket ServerSocket ss = new ServerSocket(8888); //不断监听,处理客户端请求 while (running) { Socket socket = ss.accept(); process(socket); socket.close(); } ss.close(); } private static void process(Socket socket) throws Exception { InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); DataInputStream dis = new DataInputStream(is); DataOutputStream dos = new DataOutputStream(os); //读取客户端发过来的id Integer id = dis.readInt(); //调用服务类生成商品 IProductService service = new ProductServiceImpl(); Product product = service.getProductById(id); //把商品的信息写回给客户端 dos.writeInt(id); dos.writeUTF(product.getName()); dos.flush(); dos.close(); dis.close(); os.close(); is.close(); } }
上面的是 RPC 远程调用的原始简易版本,可以看到联网的代码写死在了客户端中,网络部分代码和getProductById()耦合在了一起,如果想把别的方法改为远程调用还要重新写一遍联网的代码,非常麻烦。
在实际的使用中,我们会编写各种各样的远程调用,打个比方,IProductService接口以后可能会扩展成这样:
public interface IProductService { Product getProductById(Integer id); Product getProductByName(String name); Product getMostExpensiveProduct(); }
我们总不可能为每个方法都编写一段网络连接的代码吧,我们得想到一种办法为所有的方法都嵌入一段共用的网络连接代码。
那具体应该怎样嵌入呢?这里我们可以用到代理模式。
在 Java 中许多优秀的框架都用到了代理模式做代码嵌入,比如说 Mybatis。它把 JDBC 连接部分的代码通过代理模式嵌入到 sql 语句的周围,让我们专注于写 sql。 |
首先,服务端的代码要进行修改,由于多个方法共用一套联网的代码,我们要在服务端识别调用的是哪一个方法:
public class Server { private static boolean running = true; public static void main(String[] args) throws Exception { //...... } private static void process(Socket socket) throws Exception { //获取输入流,输出流 InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); ObjectInputStream ois = new ObjectInputStream(is); ObjectOutputStream oos = new ObjectOutputStream(os); //获取本次远程调用的方法名 String methodName = ois.readUTF(); //获取本次远程调用方法的参数类型 Class[] parameterTypes = (Class[]) ois.readObject(); //获取具体的参数对象 Object[] args = (Object[]) ois.readObject(); //创建商品服务类实例,(往后这里可以继续优化) IProductService service = new ProductServiceImpl(); //根据远程获取的方法名和参数,调用相应的方法 Method method = service.getClass().getMethod(methodName, parameterTypes); Product product = (Product) method.invoke(service, args); //把结果写回给客户端 oos.writeObject(product); oos.close(); ois.close(); socket.close(); } }
然后在客户端,我们创建一个新的代理类,对外提供一个 getStub 获取代理类的方法。使用 JDK 的动态代理需要三个参数,一个是类加载器,一个是接口的 class 类,最后一个是InvocationHandler实例。
JDK 动态代理背后的逻辑是这样的:JVM 会根据接口的 class 类动态创建一个代理类对象,这个代理对象实现了传入的接口,也就是说它拥有了接口中所有方法的实现。方法具体的实现可以由用户指定,也就是调用InvocationHandler的invoke方法。
在invoke方法中有三个参数,分别是proxy代理类,method调用的方法,args调用方法的参数。我们可以在invoke方法中对具体的实现方法进行增强,在本案例中就是进行网络调用。 |
public class Stub { public static IProductService getStub() { InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //和服务端建立Socket连接 Socket socket = new Socket("127.0.0.1", 8888); ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); //拿到远程调用的方法名 String methodmethodName = method.getName(); //拿到远程调用方法的参数类型 Class[] parametersTypes = method.getParameterTypes(); //把方法名传递给服务端 oos.writeUTF(methodName); //把方法参数类型传递给服务端 oos.writeObject(parametersTypes); //把方法参数传递给服务端 oos.writeObject(args); oos.flush(); //获取远程调用的返回结果 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Product product = (Product) ois.readObject(); ois.close(); oos.close(); socket.close(); return product; } }; Object o = Proxy.newProxyInstance(IProductService.class.getClassLoader(), new Class[]{IProductService.class}, h); return (IProductService) o; } }
这个新版本比第一个版本又美好了一些,但是其实还可以继续优化。现在我们的代理只能够返回IProductService的实现类,得想办法让它返回任意类型的服务实现类。
思路和远程调用方法相似,在远程调用方法时,我们把方法的名称,参数类型,参数传递给服务端;现在要动态创建服务类,我们可以把服务接口的名字传给服务端。服务端拿到远程接口的名字后,就可以从服务注册表中找到对应服务实现类。
至于服务实现类如何注册到服务注册表,这里提供一个思路:可以考虑使用 Spring 的注解注入。这和我们平时写 spring 代码是相似的,在创建完服务实现类后我们会加上注解 @Service,这样我们就可以在收到远程调用后,遍历使用了 @Service的Bean,找到对应的实现类。
到此,相信大家对“怎么通过Java代码来理解RPC”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。