Version04:
Version03中,如果我的接口里面有一些其他的方法,你难道客户端每次都得传一个int类型的,比如221过来嘛,每个方法都只能接收整数吗,其他类型不行吗,所以我得再进一步,把这种差异屏蔽掉,就像刚开始敏小言不想看到处理网络的细节一样,咱们怎么做的,还不是屏蔽了一些细节嘛
/**
* 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));
}
}
/**
* 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.ObjectInputStream;
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 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嘛
*/
/**
* 服务器端接收到了客户端发过来的方法名、方法的参数类型以及方法的参数(对一个方法来说,这不就齐全了嘛)
*/
Method method = serviceImpl.getClass().getMethod(methodName, parameterTypes);
Pilliow pilliow = (Pilliow) method.invoke(serviceImpl, args);
//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.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import org.checkerframework.checker.units.qual.s;
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方法处理(方法处理方法嘛,调用处理器处理的是被调用的或者说被代理的方法中的代码呗)一下(相当于会生成实现了这个服务端接口的新的代理类)
*
* Version04:强调的是,不管是哪种方法(除了可以接收整数id值,还有其他类型的值的哪些不止一个方法)
* 咱们从本质来看,方法的名字不就是一串字符串吗,所以咱们把动态生成的或者其他咱们想传输的方法的名字通过远程传输过去不就行了嘛
* @return
* @author HuHongBo
*/
public static PilliowService getStub(){
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 methodName = method.getName();
/**
* 有了方法名,咱们害怕有方法重载出现,所以为了防止重载带来的重复,咱们把调用的方法的参数类型也拿到
*/
Class[] parameterTypes = method.getParameterTypes();
/**
* 把方法名和参数类型、以及方法要调用的参数都写过去到服务器端,那么服务器端肯定就知道要调用的是哪个方法了呀
*/
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);
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;
}
}
但是此时缺陷依旧:还记不记得,咱们现在客户端的需求是要调用远程服务器端的那个方法去通过id得到pilliow,但是咱们服务器端那边找到Pilliow时是把Pilliow拆解成对象里面的一个又一个成员对象或者叫属性,给咱们传过来了,而咱们客户端这边也是把一个又一个成员属性当作一个完整对象了,这很明显不对嘛,人家一个对象怎么能和一堆成员变量等价呢?
另外,除了上面的扯皮原因,更符合咱们耦合性方面的原因就是,如果按照拆解对象那样来搞的话,如果冷不丁咱们的对象中的某个属性变了或者说属性们发生增删的话,很大概率对象需要跟着改改,这多麻烦呀
所以
所以Version05应运而生
…未完待续