首先对上一阶段的源码进行了重构优化,增加了自定义异常处理,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);
    }
}

执行后应当获得和之前一样的结果。
本节结束…