java基础巩固-宇宙第一AiYWM:为了维持生计,手写RPC~Version03(加入动态代理,屏蔽网络等细节)整起

Version03:
Version02中,

  • 现在这个代理Stub只能代理这一个方法,也就是通过id找抱枕,那我想找其他的东西怎么办,你这代理就找不了了,这不坏事了嘛。况且,举个例子,如果找中介还得我自己一个方法手动生成一个代理,这多麻烦,你代理能不能识别我的需求然后帮我自动生成方法或者服务端的代理,我只需要调用方法后,得到你中间人增强后的对象,我再去使用就行。
  • 那我想调用服务端两个方法呢?
  • 客户端不够通用,host,port, 与调用的方法都是特定的,也就是写死了,我这个客户端只能远程调用你那个客户端暴露的服务,这不明显不灵活嘛,这找谁说理去。
    在这里插入图片描述
/**
 * Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
 */
package entity;

import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 客户端敏小言与服务端小胡都已知的实体,客户端需要得到这个pojo对象数据,服务端需要操作这个对象,咱们这种情境下就是服务器需要去按照客户端提供的信息查询一下这个对象,再把查找到对象返还给客户端
 * 
 * 比如,本机上另外一个类想拿到我这个Pilliow对象去使用,拿到给他这不就是本地通信或者传输嘛 ,但是,如果另外一台机器上的一个东西想拿到我这个Pilliow对象去使用,拿给他这不就是远程通信嘛
 * @author HuHongBo
 * @version 2022年5月5日
 */
//@Builder
//@Data
//@NoArgsConstructor
//@AllArgsConstructor
public class Pilliow implements Serializable {
	private static final long serialVersionUID = 1L;
	private Integer id;
	private String clothesColor;

	public Pilliow(Integer id, String clothesColor) {
		super();
		this.id = id;
		this.clothesColor = clothesColor;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getClothesColor() {
		return clothesColor;
	}

	public void setClothesColor(String clothesColor) {
		this.clothesColor = clothesColor;
	}

	@Override
	public String toString() {
		return "Pilliow [id=" + id + ", clothesColor=" + clothesColor + "]";
	}

}

然后,小胡把id给敏小言发过去时,自己就开始阻塞等待:
什么时候把Pillow发送来呢?
什么时候把Pillow发送来呢?什么时候把Pillow发送来呢?
什么时候把Pillow发送来呢?什么时候把Pillow发送来呢?什么时候把Pillow发送来呢?

然后敏小言那边作为服务端,调用服务(方法)用id查找到那个Pillow后,就开始把这个Pillow给小胡发过去。
然后小胡收到了Pillow后,就去把Pillow塞到柜子里等顾客去取就行了。
其中有些偷懒细节,看注释:
在这里插入图片描述

/**
 * Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
 */
package client;

import entity.Pilliow;
import server.PilliowService;
import server.Stub;

/**
 * 客户端是需要把id=221这个id值写给服务器端,这样服务器端才能帮咱们按照这个id去找到这个Pillow呀
 * 	由于网络上只能传输二进制,所以咱们得把这个id值,也就是221转换成为二进制
 * 	Java中可以用这三句代码,就会自动帮咱们把id这个221值转为221对应的二进制
 * 		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
		dataOutputStream.writeInt(221);
 * 
 * @author HuHongBo
 * @version 2022年5月12日
 */
public class Client {
	public static void main(String[] args) throws Exception {
//		Stub stub = new Stub();
//		System.out.println(stub.getPilliowByPilliowId(221));
		/**
		 * 我现在就这样升级,我客户端,只想要你代理提供或者说返回给我一个服务端(也就是咱们那个服务端接口,
		 * 然后咱们就可以调用服务端的方法进行远程访问(不就相当于实现了远程访问了嘛,因为我已经可以得到你服务端的服务了呀
		 * (也就是那个getPilliowByPilliowId方法,)),接收到客户端传过来的id,赶紧找到id对应的Pilliow,然后赶紧传
		 * 到客户端,我的事就完成了)
		 * 
		 * 
		 * 总而言之,就是Stub.getStub()给咱们返回了这个动态生成的对象,而这个对象所属的类就是实现了服务端PilliowService接口的一个类
		 */
		PilliowService pilliowService = Stub.getStub();
		/**
		 * pilliowService.getPilliowByPilliowId(221),别看这是个简简单单的方法调用,这是通过网络远程访问到人家别处的getPilliowByPilliowId
		 * ,你想呀,id是人家client的,这个方法是人家服务器端的,这不是远程访问是啥?
		 * 所以,也就是说当咱们调用getPilliowByPilliowId方法时,这个方法还帮咱们加进去了一些处理网络细节的代码,以至于咱们才能实现远程调用这个服务器端的方法或者说远程调用也行
		 * 
		 * pilliowService就是咱们通过动态代理动态产生的代理类。当pilliowService调用getPilliowByPilliowId方法时,这个调用会被动态代理的调用处理器InvocationHandler进行处理
		 * 是怎么处理的呢,就是invoke方法中咱们自己实现的代码
		 * 
		 * 那是加进去了什么处理网络细节的代码呢----->代理模式中的动态代理
		 * 		假如我有一个类A,这个类A实现了PilliowService接口,那我这个类A中肯定是有个实现了或者说重写了这个接口中的所谓的服务端的那个(抽象)方法
		 * 				在这个被重写的方法里面我加了各种各样的处理网络细节的代码。那么我直接调用类A中这个所谓的服务器端方法就可以实现“远程访问服务器”
		 * 			现在问题来了,我这个类A是自己手写写死呢还是模仿spring那样把控制权或者说类的创建权交出去呢?
		 * 				写死的话,如果有新接口(中的新的所谓的服务端的方法)出来,那我是不是又得再重写类A,又有新的又得再重写类AAA...肯定不方便
		 * 				所以我们就模仿spring,让别人帮咱们动态创建这个类A(也就是让这个类A动态产生)。要动态产生一个新的类,这不就用上了动态代理了嘛
		 */
		System.out.println(pilliowService.getPilliowByPilliowId(221));
		
	}
}

java基础巩固-宇宙第一AiYWM:为了维持生计,手写RPC~Version03(加入动态代理,屏蔽网络等细节)整起_第1张图片

/**
 * Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
 */
package server;

import entity.Pilliow;

/**
 * 定义客户端需要调用,服务端需要提供的服务接口
 * getUserByUserId()这个方法或者说这个方法所在的类,就是咱们的一个服务呀,供别人调用的
		 * 
		 * 那我调用服务的思路就是:我想查id为221的那个人,我客户端是不是得先把id=221这个id写给服务端
		 * 然后服务端查到这个人后,转成二进制给我客户端再返回回来(写回来)
		 * 我这边收到之后再进行解析
 * @author HuHongBo
 * @version 2022年5月5日
 */
public interface PilliowService {
	/**
	 * 客户端通过这个接口调用服务端的实现类
	 * @param id
	 * @return
	 * @author HuHongBo
	 */
    Pilliow getPilliowByPilliowId(Integer id);
}

/**
 * Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
 */
package server;
import entity.Pilliow;

/**
 * getPilliowByPilliowId()这个方法或者说这个方法所在的类,就是咱们的一个服务呀,供别人调用的
		 * 
		 * 那我调用服务的思路就是:我想查id为221的那个人,我客户端是不是得先把id=221这个id写给服务端
		 * 然后服务端查到这个人后,转成二进制给我客户端再返回回来(写回来)
		 * 我这边收到之后再进行解析
 * @author HuHongBo
 * @version 2022年5月5日
 */
public class PilliowServiceImpl implements PilliowService{

	@Override
	public Pilliow getPilliowByPilliowId(Integer id) {
		/**
		 * 当然,实体类中的这些成员变量的值一般实际中都是从数据库中或者说缓存中查出来的
		 */
		return new Pilliow(id, "black 卫衣");
	}

}

/**
 * Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
 */
package server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import entity.Pilliow;


/**
 * 
 * getPilliowByPilliowId()这个方法或者说这个方法所在的类,就是咱们的一个服务呀,供别人调用的
* 
* 那我调用服务的思路就是:我想查id为221的那个人,我客户端是不是得先把id=221这个id写给服务端
* 然后服务端查到这个人后,转成二进制给我客户端再返回回来(写回来)
* 我这边收到之后再进行解析
 * 
 * @author HuHongBo
 * @version 2022年5月12日
 */
public class Server {
	private static boolean flag = true;
	
	public static void main(String[] args) throws Exception{
		/**
		 * 别人可以通过这个端口来远程连接我这个Server,也就是服务
		 */
		ServerSocket serverSocket = new ServerSocket(2221);
		while(flag){
			/**
			 * 通过accept接收一个客户端连接
			 */
			Socket socket = serverSocket.accept();
			/**
			 * 对客户端的连接进行处理
			 */
			process(socket);
			socket.close();
		}
	}

	/**
	 * 
	 * @param socket
	 * @throws Exception
	 * @author HuHongBo
	 */
	private static void process(Socket socket) throws Exception {
		/**
		 * 拿到输入流
		 */
		InputStream inputStream = socket.getInputStream();
		OutputStream outputStream = socket.getOutputStream();
		DataInputStream dataInputStream = new DataInputStream(inputStream);
		DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
		
		/**
		 * 从输入流中解析出,谁把这个id传给我的,我把这个id读出来
		 * 	刚刚咱们在客户端不是发过来一个id为221的整数对应的二进制,然后还把这个二进制转为字节数组了嘛
		 * 下来咱们通过readInt()把代表221的字节数组都进来,转换成为一个int类型的id
		 */
		int id = dataInputStream.readInt();
		PilliowServiceImpl serviceImpl = new PilliowServiceImpl();
		/**
		 * getPilliowByUPilliowId()这个方法或者说这个方法所在的类,就是咱们的一个服务呀,供别人调用的
		 * 
		 * 那我调用服务的思路就是:我想查id为221的那个人,我客户端是不是得先把id=221这个id写给服务端
		 * 然后服务端查到这个人后,转成二进制给我客户端再返回回来(写回来)
		 * 我这边收到之后再进行解析,我服务器端,拿到了这个id值然后就能找到一个对应的Pilliow嘛
		 */
		Pilliow pilliow = serviceImpl.getPilliowByPilliowId(id);
		/**
		 * 把得到的pilliow写出去
		 * 	其实这里我偷了个懒,因为我知道你Pilliow就俩属性id和clothesColor这俩属性,我把这俩属性写给你也就相当于把这个Pilliow对象写给你了嘛
		 */
		dataOutputStream.writeInt(pilliow.getId());
		dataOutputStream.writeUTF(pilliow.getClothesColor());
		dataOutputStream.flush();
		
	}
}

/**
 * Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
 */
package server;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;

import com.sun.org.apache.bcel.internal.generic.RETURN;

import entity.Pilliow;

/**
 * 封装一个代理出来,别人只需看懂或者说调用这个getPilliowByPilliowId方法即可。我其实很想把名字起为Proxy而不是Stub,但是人家约定俗成代理中介叫stub,就按照人家的来吧
 * @author HuHongBo
 * @version 2022年5月12日
 */
public class Stub {
	/**
	 * 客户端通过这个接口调用服务端的实现类
	 * @param id
	 * @return
	 * @author HuHongBo
	 */
//    public Pilliow getPilliowByPilliowId(Integer id) throws Exception{
//    	/**
//    	 * 连网络,把id这个221值写出去到服务器端,让他帮忙按id找Pillow
//    	 */
//    	Socket socket = new Socket("127.0.0.1", 2221);
//    	ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//    	DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
//    	dataOutputStream.writeInt(221);
//    	
//    	/**
//    	 * 把表示221这个id值的二进制转为真正的int字节数组,再通过.write方法把这个字节数组给写出去,然后这个字节数组,也就是id=221才能到服务器端
//    	 */
//    	socket.getOutputStream().write(byteArrayOutputStream.toByteArray());
//    	socket.getOutputStream().flush();
//    	
//    	/**
//    	 * 他服务器那边偷了个懒嘛,虽然说后面有更高级的方式可以把Pillow些过来给我,但是现在他就是在偷懒,只把Pillow的俩属性写过来了
//    	 * 那我客户端DataInputStream就在这里阻塞着,等着读,服务器端发来的Pillow对象
//    	 */
//    	DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
//    	/**
//    	 * 废话,既然你是偷懒,传俩属性过来,我这边也是把id和clothesColor分别读进来就行
//    	 */
//    	int receivedId = dataInputStream.readInt();
//    	String clothesColor = dataInputStream.readUTF();
//    	Pilliow pillow = new Pilliow(receivedId, clothesColor);
//    	
//    	dataOutputStream.close();
//    	socket.close();
//    	return pillow;
	/**
	 * 动态代理的重点,就是先看这个返回值,就是,你想要哪个接口我就(通过动态代理动态产生这个接口的这个类)返回给你实现了哪个接口的具体的代理类(你看返回值嘛)。
	 * 
	 * 		我们现在用动态代理产生的是是实现了咱们服务端接口的新的类
	 * 这样写的好处就是,以后不管咱们往这个PilliowService添加任何方法时,这些个方法被调用时都会被动态代理的调用处理器用自己的invoke方法处理(方法处理方法嘛,调用处理器处理的是被调用的或者说被代理的方法中的代码呗)一下(相当于会生成实现了这个服务端接口的新的代理类)
	 * @return
	 * @author HuHongBo
	 */
	public static PilliowService getStub(){
		InvocationHandler handler = new InvocationHandler() {
			@Override
			/**
			*Object proxy:代表产生的代理对象
			*Method method:正在调用的方法,此时就指的是咱们的getPilliowByPilliowId这个被客户端远程调用的位于服务器端的远程方法
			*Object[] args:给正在调用的方法传入的参数
			*/
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				/**
		    	 * 连网络,把id这个221值写出去到服务器端,让他帮忙按id找Pillow
		    	 */
		    	Socket socket = new Socket("127.0.0.1", 2221);
		    	
		    	ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		    	DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
		    	dataOutputStream.writeInt(221);
		    	
		    	/**
		    	 * 把表示221这个id值的二进制转为真正的int字节数组,再通过.write方法把这个字节数组给写出去,然后这个字节数组,也就是id=221才能到服务器端
		    	 */
		    	socket.getOutputStream().write(byteArrayOutputStream.toByteArray());
		    	socket.getOutputStream().flush();
		    	
		    	/**
		    	 * 他服务器那边偷了个懒嘛,虽然说后面有更高级的方式可以把Pillow些过来给我,但是现在他就是在偷懒,只把Pillow的俩属性写过来了
		    	 * 那我客户端DataInputStream就在这里阻塞着,等着读,服务器端发来的Pillow对象
		    	 */
		    	DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
		    	/**
		    	 * 废话,既然你是偷懒,传俩属性过来,我这边也是把id和clothesColor分别读进来就行
		    	 */
		    	int id = dataInputStream.readInt();
		    	String clothesColor = dataInputStream.readUTF();
		    	Pilliow pilliow = new Pilliow(id, clothesColor);
		    	
		    	dataOutputStream.close();
		    	socket.close();
		    	//服务端按照客户端的需求,并且依照客户端传递过来的信息处理好之后,返回XXX给客户端
		    	return pilliow;
			}
		};
		/**
		 * Proxy.newProxyInstance()中new出啥来了,不就是new出了代理类嘛
		 * 		这个代理类有几个参数:
		 * 			第一个参数是产生这个代理类的那个ClassLoader
		 * 			第二个参数是说这个代理类实现了哪些接口(实现了哪些接口,你心里没数吗,你要产生哪个接口的子实现类的代理类,你得表示出这一层关系呀,也就是实现了这个接口呀)
		 * 			第三个参数是,我想调用这个通过动态代理动态产生的代理类中的方法,我只能用这其中的第三个参数handler来实现给咱们原来的方法做增强,也就是添油加醋
		 * 				具体这个第三个参数是怎么添油加醋做增强的,就是因为这第三个参数InvocationHandler也是一个接口嘛,那你接口和类一样也还是通过你肚子里面的方法来实现功能的嘛,而这个第三个参数InvocationHandler这个接口就是通过自己肚子里面的invoke()这个方法来实现添油加醋做增强的
		 * 					这个第三个参数肚子里面的invoke()方法也有三个参数分别是:
		 * 						第一个参数是:谁在调用我这个代理对象,我要对谁添油加醋做增强呀
		 * 						第二个参数是:正在调用(通过动态代理动态产生的)代理类中的哪个方法
		 * 						第三个参数是:正在调用(通过动态代理动态产生的)代理类中的哪个方法,这个方法有什么参数传进来了
		 */
		/**
		 * 这个object是咱们代理产生的新的对象
		 */
		Object object = Proxy.newProxyInstance(PilliowService.class.getClassLoader(), new Class[]{PilliowService.class}, handler);
		/**
		 * 打印结果是com.sun.proxy.$Proxy0,这个类很明显就是动态产生的呀,咱们哪里写过这个类了呀
		 */
		System.out.println(object.getClass().getName());
		/**
		 * 打印结果是:interface server.PilliowService,说明咱们动态代理帮咱们动态产生的这个类是继承了咱们写的那个服务端接口的,(为啥打印时要[0]呢,因为有可能人家动态代理生成的代理类继承了不止一个类呢,底层呢谁知道呢)
		 */
		System.out.println(object.getClass().getInterfaces()[0]);
		return (PilliowService)object;
	}
}

但是此时缺陷依旧:如果我的接口里面有一些其他的方法,你难道客户端每次都得传一个int类型的,比如221过来嘛,每个方法都只能接收整数吗,其他类型不行吗,所以我得再进一步,把这种差异屏蔽掉,就像刚开始敏小言不想看到处理网络的细节一样,咱们怎么做的,还不是屏蔽了一些细节嘛

所以Version04应运而生
…未完待续

你可能感兴趣的:(java,开发语言,rpc)