首先对上一阶段的源码进行了重构优化,增加了自定义异常处理,commit地址:f8a3acc
在前面的小节中我们设计的是服务器默认只提供一个服务,本节就来探讨如何让服务器提供多个服务
本节commit地址:8b71c6d
服务注册表
我们需要一个容器,用来保存服务端提供的所有服务,方便查询使用,即通过服务名字就能返回这个服务的具体信息(利用接口名字获取到具体接口实现类对象)。创建一个 ServiceRegistry 接口:
public interface ServiceRegistry {
/**
* @description 将一个服务注册进注册表
* @param [service] 待注册的服务实体
* @param <T> 服务实体类
* @return [void]
* @date [2021-02-07 16:59]
*/
<T> void register(T service);
/**
* @description 根据服务名获取服务实体
* @param [serviceName] 服务名称
* @return [java.lang.Object] 服务实体
* @date [2021-02-07 17:06]
*/
Object getService(String serviceName);
}
新建一个默认的注册表类 DefaultServiceRegistry 来实现这个接口,提供服务注册服务,如下:
public class DefaultServiceRegistry implements ServiceRegistry{
private static final Logger logger = LoggerFactory.getLogger(DefaultServiceRegistry.class);
/**
* key = 服务名称(即接口名), value = 服务实体(即实现类的实例对象)
*/
private final Map<String, Object> serviceMap = new ConcurrentHashMap<>();
/**
* 用来存放实现类的名称,Set存取更高效,存放实现类名称相比存放接口名称占的空间更小,因为一个实现类可能实现了多个接口
*/
private final Set<String> registeredService = ConcurrentHashMap.newKeySet();
@Override
public synchronized <T> void register(T service) {
String serviceImplName = service.getClass().getCanonicalName();
if(registeredService.contains(serviceImplName)){
return;
}
registeredService.add(serviceImplName);
//可能实现了多个接口,故使用Class数组接收
Class<?>[] interfaces = service.getClass().getInterfaces();
if(interfaces.length == 0){
throw new RpcException(RpcError.SERVICE_NOT_IMPLEMENT_ANY_INTERFACE);
}
for(Class<?> i : interfaces){
serviceMap.put(i.getCanonicalName(), service);
}
logger.info("向接口:{} 注册服务:{}", interfaces, serviceImplName);
}
@Override
public synchronized Object getService(String serviceName) {
Object service = serviceMap.get(serviceName);
if(service == null){
throw new RpcException(RpcError.SERVICE_NOT_FOUND);
}
return service;
}
}
在注册服务时,默认采用接口名称作为服务名,例如某个对象 A 实现了接口 X 和 Y,那么将 A 注册进去后,会有两个服务名 X 和 Y 对应于 A 对象。相当于创建了两个map(k,v)对象,这种处理方式的好处在于每个接口只会对应一个对象,逻辑更清晰,查找更方便。同时注意使用了ConcurrentHashMap和Synchronized来保证线程安全。
其他处理
由于已将服务注册分离出来,因此需要做相应修改和调整,首先是RpcServer:
public class RpcServer {
private static final Logger logger = LoggerFactory.getLogger(RpcServer.class);
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 50;
private static final int KEEP_ALIVE_TIME = 60;
private static final int BLOCKING_QUEUE_CAPACITY = 100;
private final ExecutorService threadPool;
private RequestHandler requestHandler = new RequestHandler();
private final ServiceRegistry serviceRegistry;
public RpcServer(ServiceRegistry serviceRegistry){
this.serviceRegistry = serviceRegistry;
/**
* 设置上限为100个线程的阻塞队列
*/
BlockingQueue<Runnable> workingQueue = new ArrayBlockingQueue<>(BLOCKING_QUEUE_CAPACITY);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
/**
* 创建线程池实例
*/
threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, workingQueue, threadFactory);
}
/**
* @description 服务端启动
* @param [port]
* @return [void]
* @date [2021-02-05 11:57]
*/
public void start(int port){
try(ServerSocket serverSocket = new ServerSocket(port)){
logger.info("服务器启动……");
Socket socket;
//当未接收到连接请求时,accept()会一直阻塞
while ((socket = serverSocket.accept()) != null){
logger.info("客户端连接!{}:{}", socket.getInetAddress(), socket.getPort());
threadPool.execute(new RequestHandlerThread(socket, requestHandler, serviceRegistry));
}
threadPool.shutdown();
}catch (IOException e){
logger.info("服务器启动时有错误发生:" + e);
}
}
}
在创建 RpcServer 时需要传入一个已经注册好服务的 ServiceRegistry,而原来的 register 方法也被改成了 start 方法,因为服务的注册已经不由 RpcServer 处理了,它只需要启动就行了。
然后交给处理请求的工作线程RequestHandlerThread,它主要是利用客户端传来的RpcRequest对象,从ServiceRegistry 中获取提供服务的对象。
public void run() {
try(ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
RpcRequest rpcRequest = (RpcRequest)objectInputStream.readObject();
String interfaceName = rpcRequest.getInterfaceName();
Object service = serviceRegistry.getService(interfaceName);
Object result = requestHandler.handle(rpcRequest, service);
objectOutputStream.writeObject(RpcResponse.success(result));
objectOutputStream.flush();
}catch (IOException | ClassNotFoundException e){
logger.info("调用或发送时发生错误:" + e);
}
}
在RequestHandler中再利用反射原理实现方法的调用处理,最后将结果返回给客户端。
测试
客户端无需修改,服务端修改测试:
public class TestServer {
public static void main(String[] args) {
//创建服务对象
HelloService helloService = new HelloServiceImpl();
//创建服务容器
ServiceRegistry serviceRegistry = new DefaultServiceRegistry();
//注册服务对象到服务容器中
serviceRegistry.register(helloService);
//将服务容器纳入到服务端
RpcServer rpcServer = new RpcServer(serviceRegistry);
//启动服务端
rpcServer.start(9000);
}
}
执行后应当获得和之前一样的结果。
本节结束…