项目结构如下
一、service是服务层
实现了如下接口:
HelloServices 代码如下:
package consumer.service;
public interface HelloServices {
String sayHi(String name);
}
HelloServiceImpl 代码如下:
public class HelloServiceImpl implements HelloServices {
@Override
public String sayHi(String name) {
return "Hello "+name+" ^_^";
}
}
上部分的代码,是声明微服务的接口规范(HelloServices)以及对应接口规范的具体实现(HelloServiceImpl )
通过上面的分析我们可以知道,微服务本身,是不具备任何底层的能力。
他只与业务相关
二、factory层,这里只是我个人的命名。
他主要是实现了如下接口
Service.class代码如下:功能说明参考注释
public interface Service {
//停止服务
public void stop();
//开始服务
public void start() throws IOException;
//注册服务,注册服务,就是讲接口,以及对应的实现,放到了一个Map中
public void register(Class serviceInterface, Class impl);
//判断当前服务是否在运行
public boolean isRunning();
//获取使用的端口
public int getPort();
}
FactoryService.class的成员变量
private static ExecutorService executorService=
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static final HashMap<String,Class> serviceRegistry=
new HashMap<>();
private static int port;
private static boolean isRunning = false;
1、ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。
2、serviceRegistry 本质上是一个Map,存放的key是服务的全路径名称(接口),value是具体类(接口实现类)
相关的put操作如下:
serviceServer.register(HelloServices.class, HelloServiceImpl.class);
@Override
public void register(Class serviceInterface, Class impl) {
print("Map key>"+serviceInterface.getName());
print("Map value>"+impl);
serviceRegistry.put(serviceInterface.getName(), impl);
}
start方法,是所有接口中最核心的接口
@Override
public void start() throws IOException{
ServerSocket serverSocket=new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
try {
while (true){
executorService.execute(new ServiceTask(serverSocket.accept()));
}
}finally {
serverSocket.close();
}
}
start的相关类以及方法说明:
1、ServerSocket类
ServerSocket(int port):创建绑定到特定端口的服务器套接字。
accept():侦听并接受到此套接字的连接。
getInetAddress():返回此服务器套接字的本地地址。
2、executorService.execute(new Runable());
启动一个线程 ,加入线程池
具体线程如下:
public ServiceTask(Socket client){this.client=client;}
@Override
public void run() {
//将对象作为输入输出流
ObjectInputStream input=null;
ObjectOutputStream output=null;
try {
input=new ObjectInputStream(client.getInputStream());
String serviceName=input.readUTF();
String methodName=input.readUTF();
Class<?>[] paramterTypes= (Class<?>[]) input.readObject();
Object[] arguments=(Object[]) input.readObject();
Class serviceClass=serviceRegistry.get(serviceName);
if(serviceClass==null){throw new ClassNotFoundException();}
Method method=serviceClass.getMethod(methodName,paramterTypes);
Object result=method.invoke(serviceClass.newInstance(),arguments);
output = new ObjectOutputStream(client.getOutputStream());
output.writeObject(result);
}
...
}
相关方法说明:
readUTF():readUTF读取的必须是writeUTF写下的字符串。
扩展【read()、readLine()】与readUTF()的区别于联系。
Method getMethod(String name, Class<?>… parameterTypes)
//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
//getMethod第一个参数是方法名,第二个参数是该方法的参数类型,
//因为存在同方法名不同参数这种情况,所以只有同时指定方法名和参数类型才能唯一确定一个方法
Method method = XXX.getClass().getMethod(methodName,new Class[0]);
获取该类的Class Type,通过getMethod方法获取Method对象,通过调用invoke方法来执行对象的某个方法;
构造方法,传递socket连接的client
这个client,会被【消费者】连接,消费者连接到端口之后,进行请求。
run方法执行说明:
1、获取client中,客户端中输入流。(按照消费者中心的消息发送规则,获取到相关的输入流)
/*
参数 proxy 指代理类,
method表示被代理的方法,
args为 method 中的参数数组,
返回值Object为代理实例的方法调用返回的值
*/
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceInterface.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
3、获取到serviceName即,serviceInterface.getName()
获取到methodName即,method.getName()
获取到 Class<?>[] paramterTypes 即 method.getParameterTypes()【参数类型数组】
获取到 Object[] arguments 即、args【参数对象数组】
4、通过客户端在socket中传输的serviceName,
在注册中心的注册列表【HashMap<String,Class> serviceRegistry】中进行查询
可以获取到注册列表中,以及被注册了的该服务名称,所对应的具体类
5、得到了具体类之后,使用
serviceClass.getMethod(methodName,paramterTypes);
可以获取到具体的方法,因为java语言中,有重载的存在,此方法的参数中除了需要方法名称以外,还需要方法的具体参数
6、得到具体的方法之后,可以代理执行此方法
method.invoke(serviceClass.newInstance(),arguments);
invoke参数中的arguments,是传递到此方法的参数
7、通过步骤6,可以获取到方法执行后的结果。再讲结果写入socket,返回到消费者。
消费者如何接受的呢?这里我们就要讲到消费者中心的动态代理的实现。
public class RPCClient<T> {
public static <T> T getRemoteProxyObj(final Class<?> serviceInterface
, final InetSocketAddress addr) {
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
try {
socket = new Socket();
socket.connect(addr);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceInterface.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
input = new ObjectInputStream(socket.getInputStream());
return input.readObject();
}
......
}
});
}
}
getRemoteProxyObj方法,是客户端调用远程服务的核心原理。
该方法有两个参数
Class<?> serviceInterface。需要远程服务的接口。消费者只关心服务的接口是什么,接口中定义有哪些方法,并不关心方法的具体实现。
InetSocketAddress addr。与服务通信的连接地址
/*
参数 proxy 指代理类,
method表示被代理的方法,
args为 method 中的参数数组,
返回值Object为代理实例的方法调用返回的值
*/
这里,再写一遍invoke方法的参数说明
然后,我们再来看此方法内部究竟实现了什么。
【扩展:Proxy 动态代理的原理】
通过如下代码实现对象的创建:
HelloServices service = RPCClient.
getRemoteProxyObj(HelloServices.class,
new InetSocketAddress("localhost", 8088));
通过如下代码实现方法调用:
String str=service.sayHi("test")
请求流程简述:
1、客户端得到接口,以及和服务器通信的地址
2、声明动态代理。
3、当客户端,请求调用接口的方法时,实际执行的是invoke内的代码。
4、在invoke中,会通过socket向服务器发送输入流
5、在invoke中,等待服务器响应输出流(参考service部分,请求的业务实现是在service层,service层执行了方法,并写入了执行结果到输出流)
6、在invoke中,返回输出流中的Object对象,到调用的方法。
(T) Proxy.newProxyInstance(
serviceInterface.getClassLoader(),
new Class<?>[]{serviceInterface},
new InvocationHandler() {}
);
动态代理说明:
1、在java中规定,要想产生一个对象的代理对象,那么这个对象必须要有一个接口
2、参数说明
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
Handler:动态代理方法在执行时,会调用Handler里面的invoke方法去执行
参考
https://www.cnblogs.com/codingexperience/p/5930752.html#4098476