截至Version06:咱们对整个过程梳理一下:
- 1.我在Client对服务器端暴露的或者说提供的服务进行调用(服务器端暴露的服务或者说提供的服务有很多个,咱以咱们例子中的这两个为例
- 比如咱们Version06中的按照id找Pilliow以及按照id找滑板,其实就是两个方法呗:getSkateboardBySkateboardId和getPilliowByPilliowId)
- 2.首先,咱们发现每次客户端调用服务器端服务(也就是那俩方法)时,我得写那些网络细节,很麻烦,所以,我搞了个Stub代理类出来,将这些处理网络细节相关的代码封装了起来并向外提供了一个getStub接口,然后我就可以像调用本地方法(服务)一样去调用远程位于服务器上的方法们(服务们)
- 3.然后,我发现,咱们不是学过动态代理嘛,那咱们这个代理类完全可以用动态代理来动态生成呀,这多灵活多方便呀
- 我只要告诉我工具类,我客户端要调服务端中哪个类中的哪个方法。并且我再调用这个服务端方法的过程中向这个服务端方法传入了哪些类型的哪几个参数过去(因为服务端帮你实现或者完成调用可以得用到这些参数,那你不给人家传过去人家咋帮你完成调用)
- 比如,我现在客户端要调用服务端的滑板类中的通过id找滑板的这个服务(服务端方法),并且为了你服务端帮我实现“通过id找滑板的这个服务(服务端方法)”,我不得不向服务端传过去一个int类型的id值222
- 然后就是动态代理帮咱们客户端生成一个代理类,然后代理类将“我要调用这个滑板类中的“通过id找滑板的这个服务(服务端方法)””“,这些信息传给服务端,服务器读到这些信息之后按照你代理类用InvocationHandler中的invoke方法处理过的(在咱们AOP那里,不就是用InvocationHandler中的invoke方法帮咱们增强目标对象或者说被代理对象嘛,只不过这里没有很明显增强的地方或者代码而已,这里主要体现动态代理帮咱们动态生成,生成完后没有像AOP那样在invoke中对原来目标类或者说被代理类做增强而已)对应的实现类以及服务端方法,执行完之后返回最终结果,也就是一个滑板给你客户端返回过来
- 此时,网络细节、服务端某个方法改变、方法增加、实体类中属性改变或者增减…,我不用改啥,因为有动态生成的代理帮我撑腰
- 比如,我现在客户端要调用服务端的滑板类中的通过id找滑板的这个服务(服务端方法),并且为了你服务端帮我实现“通过id找滑板的这个服务(服务端方法)”,我不得不向服务端传过去一个int类型的id值222
- 我只要告诉我工具类,我客户端要调服务端中哪个类中的哪个方法。并且我再调用这个服务端方法的过程中向这个服务端方法传入了哪些类型的哪几个参数过去(因为服务端帮你实现或者完成调用可以得用到这些参数,那你不给人家传过去人家咋帮你完成调用)
/**
* 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 + "]";
}
}
/**
* Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
*/
package entity;
import java.io.Serializable;
/**
*
* @author HuHongBo
* @version 2022年5月21日
*/
public class Skateboard implements Serializable{
private Integer id;
private String skateboardColor;
public Skateboard(Integer id, String skateboardColor) {
super();
this.id = id;
this.skateboardColor = skateboardColor;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSkateboardColor() {
return skateboardColor;
}
public void setSkateboardColor(String skateboardColor) {
this.skateboardColor = skateboardColor;
}
@Override
public String toString() {
return "Skateboard [id=" + id + ", skateboardColor=" + skateboardColor + "]";
}
}
然后,小胡把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.SkateboardService;
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.class类型的服务端接口,然后动态代理才能返回给你呀
*/
//PilliowService pilliowService = (PilliowService)Stub.getStub(PilliowService.class);
/**
* 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));
/**
* 我现在不仅要Pilliow了,我还想要滑板Skateboard
*/
SkateboardService skateboardService = (SkateboardService)Stub.getStub(SkateboardService.class);
System.out.println(skateboardService.getSkateboardBySkateboardId(222));
}
}
/**
* 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 entity.Pilliow;
import entity.Skateboard;
/**
*
* @author HuHongBo
* @version 2022年5月21日
*/
public interface SkateboardService {
/**
* 客户端通过这个接口调用服务端的实现类
* @param id
* @return
* @author HuHongBo
*/
Skateboard getSkateboardBySkateboardId(Integer id);
}
/**
* Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
*/
package server;
import entity.Pilliow;
import entity.Skateboard;
/**
*
* @author HuHongBo
* @version 2022年5月21日
*/
public class SkateboardServiceImpl implements SkateboardService{
@Override
public Skateboard getSkateboardBySkateboardId(Integer id) {
/**
* 当然,实体类中的这些成员变量的值一般实际中都是从数据库中或者说缓存中查出来的
*/
return new Skateboard(id, "black");
}
}
/**
* Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
*/
package server;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
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();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
String ClazzName = objectInputStream.readUTF();
String methodName = objectInputStream.readUTF();
Class[] parameterTypes = (Class[])objectInputStream.readObject();
Object[] args = (Object[])objectInputStream.readObject();
/**
* 从输入流中解析出,谁把这个id传给我的,我把这个id读出来
* 刚刚咱们在客户端不是发过来一个id为221的整数对应的二进制,然后还把这个二进制转为字节数组了嘛
* 下来咱们通过readInt()把代表221的字节数组都进来,转换成为一个int类型的id
*/
//int id = dataInputStream.readInt();
PilliowServiceImpl serviceImpl = new PilliowServiceImpl();
/**
* getPilliowByUPilliowId()这个方法或者说这个方法所在的类,就是咱们的一个服务呀,供别人调用的
*
* 那我调用服务的思路就是:我想查id为221的那个人,我客户端是不是得先把id=221这个id写给服务端
* 然后服务端查到这个人后,转成二进制给我客户端再返回回来(写回来)
* 我这边收到之后再进行解析,我服务器端,拿到了这个id值然后就能找到一个对应的Pilliow嘛
*/
Class clazz = null;
//从服务注册表找到具体的类
clazz = PilliowServiceImpl.class;
/**
* 服务器端接收到了客户端发过来的方法名、方法的参数类型以及方法的参数(对一个方法来说,这不就齐全了嘛)
*/
//Method method = serviceImpl.getClass().getMethod(methodName, parameterTypes);
Method method = clazz.getMethod(methodName, parameterTypes);
Pilliow pilliow = (Pilliow) method.invoke(clazz.newInstance(), args);
//Pilliow pilliow = serviceImpl.getPilliowByPilliowId(id);
// /**
// * 把得到的pilliow写出去
// * 其实这里我偷了个懒,因为我知道你Pilliow就俩属性id和clothesColor这俩属性,我把这俩属性写给你也就相当于把这个Pilliow对象写给你了嘛
// */
// dataOutputStream.writeInt(pilliow.getId());
// dataOutputStream.writeUTF(pilliow.getClothesColor());
/**
* 这里不再像之前那样偷懒了,偷懒把一个对象拆解开来传过来传过去,而是直接把一个对象传过来传过去
*/
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(pilliow);
objectOutputStream.flush();
}
}
/**
* Copyright (c) 2013-Now http://AIminminAI.com All rights reserved.
*/
package server;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
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方法处理(方法处理方法嘛,调用处理器处理的是被调用的或者说被代理的方法中的代码呗)一下(相当于会生成实现了这个服务端接口的新的代理类)
*
* Version04:强调的是,不管是哪种方法(除了可以接收整数id值,还有其他类型的值的哪些不止一个方法)
* 咱们从本质来看,方法的名字不就是一串字符串吗,所以咱们把动态生成的或者其他咱们想传输的方法的名字通过远程传输过去不就行了嘛
*
*
* @param clazz:你传给我这个什么类型的Class,我就返回给你实现了这个Class类型的动态代理对应生成的那个代理类的对象
* @return
* @author HuHongBo
*/
//public static PilliowService getStub(){
public static Object getStub(Class clazz){
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 连网络,把id这个221值写出去到服务器端,让他帮忙按id找Pillow
*/
Socket socket = new Socket("127.0.0.1", 2221);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
String clazzName = clazz.getName();
String methodName = method.getName();
/**
* 有了方法名,咱们害怕有方法重载出现,所以为了防止重载带来的重复,咱们把调用的方法的参数类型也拿到
*/
Class[] parameterTypes = method.getParameterTypes();
/**
* 把实现了这个Class类型的动态代理对应生成的那个代理类的对象、调用了哪个接口的方法名和参数类型、以及方法要调用的参数都写过去到服务器端,那么服务器端肯定就知道要调用的是哪个方法了呀
*/
objectOutputStream.writeUTF(clazzName);
objectOutputStream.writeUTF(methodName);
objectOutputStream.writeObject(parameterTypes);
objectOutputStream.writeObject(args);
objectOutputStream.flush();
// 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);
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Pilliow pilliow = (Pilliow)objectInputStream.readObject();
objectOutputStream.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;
}
}
Readme.txt
1.服务器开启,等着客户端连接
2.客户端连接来了之后,利用Socket的IP和端口互相连接上之后
3.首先客户端开始发自己的需求:我呢,要查询一下id=221的Pilliow对象,你小子帮我查一下吧
4.服务器说,行呀,那你把id给我发过来嘛,记住,你不转成二进制是传不过来的哦
5.客户端说:行,转成二进制放到字节数组中。服务器那边读了之后,利用这个id查到了对应的Pilliow.下来服务器就得想办法把这个Pilliow给客户端传过去
6.咱们Version01这个版本中是这样干的(本来你该传输Pilliow你就把这个对象传输过去),但是服务器端偷个懒,因为我知道Pilliow中只有倆属性,我就图方便,穿俩属性过去
7.然后,客户端,说你懒我也懒,就接了俩属性,然后在自己按照这俩属性自己造了个Pilliow对象进行使用。
这个最原始的方式非常的麻烦,缺点多多,如下:
如果我这个Pilliow里面属性很多,你不得该一大片。或者说,我想把id或者衣服颜色改改,你就要改很多部分,说明程序耦合的太死。
而且,如果除了Pilliow对象后,如果还有其他对象,我怎么办?一个对象一个对象来搞?
而且这些对象在不断演进,对象的属性在不断的增减,程序得跟着不断的动
截至Version06:咱们对整个过程梳理一下:
1.我在Client对服务器端暴露的或者说提供的服务进行调用(服务器端暴露的服务或者说提供的服务有很多个,咱以咱们例子中的这两个为例
比如咱们Version06中的按照id找Pilliow以及按照id找滑板,其实就是两个方法呗:getSkateboardBySkateboardId和getPilliowByPilliowId)
2.首先,咱们发现每次客户端调用服务器端服务(也就是那俩方法)时,我得写那些网络细节,很麻烦,所以,我搞了个Stub代理类出来,将这些处理网络细节相关的代码封装了起来并向外提供了一个getStub接口,然后我就可以像调用本地方法(服务)一样去调用远程位于服务器上的方法们(服务们)
3.然后,我发现,咱们不是学过动态代理嘛,那咱们这个代理类完全可以用动态代理来动态生成呀,这多灵活多方便呀
我只要告诉我工具类,我客户端要调服务端中哪个类中的哪个方法。并且我再调用这个服务端方法的过程中向这个服务端方法传入了哪些类型的哪几个参数过去(因为服务端帮你实现或者完成调用可以得用到这些参数,那你不给人家传过去人家咋帮你完成调用)
比如,我现在客户端要调用服务端的滑板类中的通过id找滑板的这个服务(服务端方法),并且为了你服务端帮我实现“通过id找滑板的这个服务(服务端方法)”,我不得不向服务端传过去一个int类型的id值222
然后就是动态代理帮咱们客户端生成一个代理类,然后代理类将“我要调用这个滑板类中的“通过id找滑板的这个服务(服务端方法)””“,这些信息传给服务端,服务器读到这些信息之后按照你代理类用InvocationHandler中的invoke方法处理过的(在咱们AOP那里,不就是用InvocationHandler中的invoke方法帮咱们增强目标对象或者说被代理对象嘛,只不过这里没有很明显增强的地方或者代码而已,这里主要体现动态代理帮咱们动态生成,生成完后没有像AOP那样在invoke中对原来目标类或者说被代理类做增强而已)对应的实现类以及服务端方法,执行完之后返回最终结果,也就是一个滑板给你客户端返回过来
此时,网络细节、服务端某个方法改变、方法增加、实体类中属性改变或者增减...,我不用改啥,因为有动态生成的代理帮我撑腰
上面说到了咱们找到不管是滑板对象还是Pilliow对象之后,都会经过远程的网络传输:服务器端<------>客户端,那肯定要经过序列化咯。咱们用RPC时首先得把方法的参数、返回值这一类的东西给序列化为二进制,这是所有的远程调用不可或缺的一步。
- Version07:集成序列化框架,也不能这么说,应该是这样:咱们之前前六个版本中用的是java的JDK自带的Serializable来实现序列化,但是这个有很多缺点,所以咱们换成Hessian等这些其他的序列化框架。但是可以集成到RPC框架中的序列化框架很多,不止Hessian这一个序列化框架,这一点要清楚
- RPC序列化框架,序列化可以分为文本类和二进制类两种。
- 序列化:也叫做编码,将对象序列化为字节数组,再用于网络传输、数据持久化等
- 反序列化:也叫解码,把从网络、磁盘等读取的字节数组还原成为原始对象(通常是原始对象的副本)
- Version06:这个版本中,序列化的方式还是Java的JDK自带的Serializable(需要序列化的Java对象只需要实现java.io.Serializable接口并生成序列化ID,这个对象对应的类就能通过java.io.ObjectInput和java.io.ObjectOutput序列化和反序列化),这个Java自带的JDK的序列化方式有以下缺点:
- 这个序列化方式只支持Java语言,不够灵活
- 效率很低
- 序列化之后的长度还特别长。
- 或者按照序列化和反序列化的三个性能指标:
- 序列化之后的码流大小
- 序列化和反序列化的速度
- 资源占用,主要是CPU和堆内存
- Version06:这个版本中,序列化的方式还是Java的JDK自带的Serializable(需要序列化的Java对象只需要实现java.io.Serializable接口并生成序列化ID,这个对象对应的类就能通过java.io.ObjectInput和java.io.ObjectOutput序列化和反序列化),这个Java自带的JDK的序列化方式有以下缺点:
- RPC序列化框架,序列化可以分为文本类和二进制类两种。
- Version07:除了自己选要集成到RPC框架上的序列化协议,还有网络协议
- RPC网络协议:
所以Version07应运而生—>RPC的序列化框架
…未完待续