反射 socket RPC
Posted 你错了我来改
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反射 socket RPC相关的知识,希望对你有一定的参考价值。
反射:
反射机制是在 运行状态中
对于任意一个类,都能够知道这个类的属性和方法
对于任意一个对象,都能够调用它的任意一个方法和属性
反射提供的功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法
生成动态代理
获得反射对象 (反射入口):Class
1.Class.forName(全类名) Class<?> perClazz = Class.forName("包名.类名")
2.类名.class Class<?> perClazz2 = Person.class
3.对象.getClass()
Person per = new Person()
Class<?> perClazz3 = per.getClass();
获取所有的公共的方法 (1.本类 父类 以及接口中的所有方法 2. 符合访问修饰符规律)
Method[] methods = perClazz.getMethods()
获取所有的接口
Class<?> [] interface = perClazz.getInterfaces()
获取所有父类
Class<?> superClass = perClazz.getSuperclass()
获得所有的构造方法
Constructor<?>[] constructors = perClazz.gerConstructors()
获取所有的公共属性
Field[] fields = perClazz.getFields()
获取当前类的所有方法(1.只能是当前类 2. 忽略访问修饰符限制)
Method [] declaredMethods = perClazz.gerDeclaredMethods()
获取所有属性
Field [] declaredFields = perClazz.gerDeclaredFields()
获取当前凡是所代表类(接口)的对象(实例)
Object instance = perClazz.newInstance();
通过反射获取对象的实例,并操作该对象
Object instance = perClazz.newInstance();
Person per = (Person) instance;
per.setName("zs")
per.setAge(36)
操作属性
Person per = (Person)perClazz.newInstance();
Field idField = perClazz.getDeclaredField("id")
idField.set(per,1) // 相当于per.setId(1)
修改属性的访问权限 使用反射时,如果因为访问修饰符限制造成异常 可以通过Field/Method的setAccessible(true)来解决。
idField.setAccessible(true)
//操作方法 无参
Method method = perClazz
.getDeclaredMethod("privateMethod",null);
method.invoke(per,null) // 方法的调用 invoke()
//操作方法 有参
Method method2 = perClazz
.getDeclaredMethod("privateMethod2",String.class);
method2.setAccessible(true)
method2.invoke(per,"zs");
//拿本类全部的构造方法
Constructor<?>[] constructors = perClazz.gerDeclaredConstructors();
获取指定的构造方法
Constructor<?> constructor = perClazz.getConstructor(int.class)
Constructor<?> constructor2 = perClazz
.getDeclaredConstructor(String.class);
通过构造方法生成对象
Person per = (Person)constructor.newInstance(12);
constructor2.setAccessible(true);
Person per2 = (Person)constructor2.newInstance("zs");
运行之前不知道,运行之中才知道 弄一个class.txt文件
里面有className = 要访问的类名
methodName = 要使用的方法名
Properties prop = new Properties();//读取属性文件
prop.load(new FileReader("class.txt"));
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
Class<?> perClazz = null;
perClazz = Class.forName(className);
perClazz.getMethod(methodName);//参数是可变参数,可以不写,这里不知道,就没有写 只有构造方法会重名 不用担心只给方法名 回重名的问题,重名看参数
method.invoke(perClazz.newInstance())//同理 这里没有写参数 是可以不写的
反射可以 越过泛型检查
虽然可以通过法反射访问private等访问修饰符不允许访问的属性/方法,也可以越过泛型检查,但实际开发中不推荐这么做
ArrayList<Integer> list = new ArrayList<>();
Class<?> listClazz = list.getClass();//获取反射入口
Method method = listClazz.getMethod("add",Object.class);
method.invoke(list,"zs.....");
可以建一个工具类 做一个万能的set方法
//obj.setPropertyName(value);
public void setProperty(Object obj,String propertyName,Object value){
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(propertyName);
field.setAccessible(true);
field.set(obj,value);
}
使用反射操作动态的一维数组
public static void array1(){
Scanner input = new Scanner(System.in);
sout("请输入数组的类型:")
String type = input.next();
sout("请输入数组的个数:);
int num = input.nextInt();
Class<?> c = Class.forName(type);
Object arr = Array.newInstance(c,num);
//给数组的一个元素赋值
Array.set(arr,0,"zs")'
sout(Array.get(arr,0));
}
使用反射操作动态的二维数组
public static void array1(){
Scanner input = new Scanner(System.in);
sout("请输入数组的类型:")
String type = input.next();
int dim[] = new int[2];
sout("请输入数组的行数 列数:);
dim[0] = input.nextInt();
dim[1] = input.nextInt();
Class<?> c = Class.forName(type);
Object arr = Array.newInstance(c,dim);
//从二维数组中获取一行(第二行),一行就是一个数组(一维的)arr2 代表的是第二行的一维数组
Object arr2 = Array.get(arr,2)
//给数组的一个元素赋值 (第二行 第一列 的值为369)
Array.set(arr2,1,369)'
sout(Array.get(arr2,1));
}
socket:
服务端和客户端交互 从网络上来说 有两种方式:
基于tcp的socket传输
基于udp的
1 服务端要发布服务时需要绑定一个端口号 ServerSocket(端口号)
3 服务端ServerSocket accept()用于监听是否有科幻段访问
4 服务端通过OutputStream() server发送
5 客户端通过InputStream()接受
6 客户端通过OutputStream() client发送
7 服务端通过InputStream()接受
8 close
publie class MyServer{
main{
//绑定服务的端口,其ip为本机ip
ServerSocker server = new ServerSocker(9999);
while(true){
Socket socket = server.accept();
//下载的线程
MyDownload download = new Mydownload(socket);
//Runable -> Thread
Thread downLoadThread = new Thread(download);
downLoadThread.start();
//上面的优化写法
new Thread(new Mydownload(socket)).start();
}
/**
//发送字符串
String info = "hello";//变量在内存中 可以直接拿到
out.write(info.getBytes());
**/
}
}
public class MyClient{
main{
//客户端 连接Server发布的服务
Socket socket = new Socket("127.0.0.1",9999);
//接受服务端发送的消息
InputStream in = socket.getInputStream():
/**
//接收字符串
byte[] bs = new Byte[100];
in.read(bs);//读取发送来的数据
sout(new String(bs));
**/
/**
//客户端向服务端做出反馈
OutputStream out = socket.getOutputStream();
String info = "world";
out.write(info.getBytes());
**/
//接受每次发来的文件切片
byte[] bs = new Byte[100];
int len = -1;
OutputStream fileOut = new FileOutputStream("E:\aaa.rar");
while((len = in.read(bs)) != -1){
fileOut = write(bs,0,len);
}
fileOut.close();
in.close()
out.close()
socket.close();
}
}
//处理下载线程
public class MyDownload implements Runnable{
privare Socket socket;
public MyDownload(Socket socket){
this.socket = socket'
}
@override
run(){
//服务端向客户端发送消息
OutputStream out = socket.getOutputStream();
//准备要发送的文件
File file = new File("E:\nginx-1.4.7.rar");
//将此文件 从硬盘读入到内存
InputStream fileIn = new FileInputStream(file);
int len = -1;
byte[] fileBytes = new byte[100];//定义每次发送的文件大小
//因为文件较大 不能一次发送完毕 因此需要通过循环来分次发送
while((len = fileIn.read(fileBytes)) != -1){
out.write(fileBytes,0,len)
}
/**
//接受客户端消息
InputStream in = socket.getInputStream():
byte[] bs = new Byte[100];
in.read(bs);//读取发送来的数据
sout(new String(bs));
**/
fielIn.close()
in.close()
out.close()
socket.close()
server.close()
}
}
rpc
反射:客户端需要传递一个字符串,而服务端需要通过该字符串解析出
该字符串代表的接口的一切信息 通过反射技术
客户端-服务端通信 socket技术
服务端需要根据客户端的不同请求,返回不同的接口类型。
客户端就要接受到不同的接口类型 通过动态代理。
服务中心ServerCenter要干的事情:
start stop register
//(标准的先写接口,再实现)
public class ServerCenter implements Server{
//map:服务端所有可供客户端访问的接口,都注册到该map中
//key:接口的名字 value:真正的接口实现
private static HashMap<String,Class> serviceRegister =
new HashMap<>();
private static int port;
//线程池:每一个线程可以处理一个客户请求
//获得处理器的个数 创建线程池
private static ExecutorService executor = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static boolean isRunning = false;
//访问服务端的端口号通过构造方法传进来
public ServerCenter(int port){
this.port = port;
}
public void register(Class service,Class serviceImpl){
serviceRegister.put(service.getName(),serviceImpl);
}
public void start(){
ServerSocket server = new ServerSocker();
server.bind(new InetSocketAddress(port));
isRunning = true;//服务已经开启
while(true){
sout("start server");
//等待客户端链接 客户端每发出一次请求
则服务端从线程池,中获取一个线程对象去处理
Socket socket = server.accept();
//启动线程 去处理客户请求
executor.execute(new ServiceTask(socket));
}
}
public void stop(){//关闭服务
isRunning = false;
executor.shutdown();
}
private static class ServiceTask implements Runable{
private Socket socket;
public ServiceTask(){}
public ServiceTask(Socket socket){
this.socket = socket;
}
@Override
public void run(){//线程做的事
ObjectInputStream input = null;
ObjectOutputStream output = null;
//接受客户端连接 处理请求
input = new ObjectInputStream
(socket.getInputStream());
//因为ObjectInputStream对 发送数据的顺序 严格要求,
因此需要按照发送的顺序 逐个接受
String serviceName = input.readUTF();
String methodName = input.readUTF();
Class[] parameterTypes = (Class[])input.readObject();
Object[] arguments = (Object[])input.readObject();
//根据客户请求,找到具体的接口
Class ServiceClass = serviceRegister.get(serviceName);
//找到具体的方法
Method method = ServiceClass
.gerMethod(methodName,parameterTypes);
//执行方法
Object result = method
.invoke(ServiceClass.newInstance(),arguments);
//向客户端 将方法执行完毕的返回值 传给客户端
output = new ObjectOutputStream
(socket.getOutputStream);
output.writeObject(result);
//最后该关的都关了
if(output!= null) output.close();
......
}
}
}
public class Client{
//获取代表服务器接口的动态代理对象
//serviceInterface:请求的接口
//addr:带请求服务器的ip:端口
public static <T> T getRemoteProxyObj(Class serviceInterface,
InetSocketAddress address,){
/**
newProxyInstance(a,b,b)
a:类加载器:需要哪个代理类,就需要将他的类加载器传进去
b:需要代理的对象,具备哪些方法------也就是接口
**/
return (T)Proxy.newProxyInstance(serviceInterface.getClassLoader(),
new Class<?>[]{serviceInterface},new InvocationHandler(){
//proxy:代理对象 method:哪个方法 args:参数列表
@Override
public Object invoke(Object proxy,
Method method,Object[] args){
//客户端向服务端发送请求,请求某一个具体的接口
Socket socket = new Socket();
socket.connect(addrress);
ObjectOutputStream output = new
ObjectOutputStream(socket.getOutputStream());//发送序列化流(对象流)
//接口名 方法名 :writeUTF
output.writeUTF(serviceInterface.getName());
output.writeUTF(me.getName());
//方法参数的类型 方法参数 Object
output.writeObject(method.getParameterTypes());
output.writeObject(args);
//等待服务端处理
//接收服务端处理过的返回值
ObjectInputStream input = new
ObjectInputStream(socket.getInputStream());
return input.readObject();
//该关的关了
......
}
});
}
}
rpc总结
客户端通过socket请求服务端,并且通过字符串形式/Class形式 将需要请求的接口发送给服务端
服务端将可以提供的接口注册到 服务中心(通过map保存 key:接口的名字 value:接口的实现类)
服务端接受到客户端的请求后 通过请求的接口名 在服务中心的map中寻找对应接口的实现类
通过动态代理:发送 接口名 方法名等
服务端找到之后 解析刚才服务端发送来的 接口名 方法名,解析完后
通过反射技术 将该方法执行 执行完毕后 再将该方法的返回值返回给客户端
以上是关于反射 socket RPC的主要内容,如果未能解决你的问题,请参考以下文章
使用Socket&反射&Java流操作进行方法的远程调用(模拟RPC远程调用)
Pythonday7:Python学习(面向对象进阶反射socket介绍)