我们已经实现了一些基础的功能,但是由于我们只是以最简实现的方式来实现这些功能,所以代码远谈不上优化,因此我们在这篇博文中,先暂时不开发新功能,而是对现有功能进行重构。
首先,我们不仅在门户Facade中需要Nio服务器,在消息总线Plato接收微服务注册、接收系统消息时也需要NIO服务器,同时微服务控制器和微服务接收消息总线消息时,也需要NIO服务器,目前这种实现方式,显然不能满足这些重用需求,因此我们需要在common项目中引入NioTcpServer基类,然后门户Facade、消息总线Plato和微服务所需要的NIO服务器均继承此类。
为了更好地实现功能,我们需要借鉴Python等语言,引入元组的功能。元组Turple是一个对象,动态拥有不可变的,任意数量的属性。之所以引入元组,是因为当函数需要返回多个值时,通常的做法为其定义一个新的值对象类,但是这样的结果是产生一大堆仅用于传值的类,不便于管理,所以我们有必要定义一个自己的元组类,让Java也能像Python那样方便的使用元组。我们先定义具有两个元素的元组。代码如下所示:
public class Turple2<T1, T2> { public final T1 v1; public final T2 v2; public Turple2(T1 v1, T2 v2) { this.v1 = v1; this.v2 = v2; } public String toString() { return "(" + v1 + ", " + v2 + ")"; } }如上所示,利用泛型,我们定义Turple2具有两种类型的属性,且为不可变的,打印时显示格式为(v1, v2),与Python语言中的元素表现一致。
下面我们来看在common项目中的NIO服务器基类NioTcpServer的启动部分,如下所示:
public abstract class NioTcpServer { private short port = 8088; // 服务器监听端口 protected abstract void processRequest(SelectionKey key, Selector selector); protected abstract void processResponse(SelectionKey key); /** * 程序总入口,启动Imsa服务器 * @throws Exception */ public void start() throws Exception { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); serverSocketChannel.socket().setReuseAddress(true); serverSocketChannel.socket().bind(new InetSocketAddress(port)); while(true){ while (selector.select() > 0) { Iterator<SelectionKey> selectedKeys = selector.selectedKeys() .iterator(); while (selectedKeys.hasNext()) { SelectionKey key = selectedKeys.next(); if (key.isAcceptable()) { acceptConnection(key, selector); } else if (key.isReadable()) { //readRequest(key, selector); processRequest(key, selector); } else if (key.isWritable()) { //sendResponse(key, prepareTestResponse()); processResponse(key); } } } } }代码与之前博客上介绍的内容差不多,但是有两个重要的区别,我们定义了两个抽象方法:processRequest需要子类来实现,其会调用基类readRequest方法获取到请求内容,同时再执行一些额外的逻辑。processResponse方法,会先根据子类特定需要,生成需要发送的请求,然后调用基类的sendResponse方法发送相应的响应。
基类中readRequest方法需要返回一个二元元组,代码如下所示:
/** * 读取消息内容,并向消息总线plato发送消息 * @param key * @param selector * @return 二元元组,a代表请求文本内容,b为二进制对象URL数组 */ protected Turple2<String, String[]> readRequest(SelectionKey key, Selector selector) { SocketChannel channel = (SocketChannel) key.channel(); Turple2<String, String[]> rst = new Turple2<String, String[]>("", null); try { channel.configureBlocking(false); String receive = receive(channel); // 如果没有接收到内容,就直接返回 if (receive.equals("")) { return rst; } BufferedReader b = new BufferedReader(new StringReader(receive)); String s = b.readLine(); StringBuilder req = new StringBuilder(); while (s != null) { req.append(s + "\r\n"); s = b.readLine(); } b.close(); String[] urls = null; channel.register(selector, SelectionKey.OP_WRITE); rst = new Turple2<String, String[]>(req.toString(), urls); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return rst; }其他方法的代码与原来的代码相同,这里就不再重复了,如果有疑问,请参考Github项目:https://github.com/yt7589/imsa。
下面我们再来看原来讨论的门户Facade中的NIO服务器,在继承NioTcpServer的情况如何实现,代码如下所示:
public class FacadeServer extends NioTcpServer { private short port = 8088; // 服务器监听端口 /** * 程序总入口,启动Imsa服务器 * @throws Exception */ public void start() throws Exception { super.start(); } /** * 接受客户端的连接请求 * @param key * @param selector */ protected void acceptConnection(SelectionKey key, Selector selector) { super.acceptConnection(key, selector); } /** * 读取消息内容,并向消息总线plato发送消息 * @param key * @param selector */ protected void processRequest(SelectionKey key, Selector selector) { Turple2<String, String[]> reqObj = super.readRequest(key, selector); String msgStr = ImsaMsgEngine.createMsg(AppConsts.MT_HTTP_GET_REQ, AppConsts.MT_MSG_V1, reqObj.v1, null); // 发送消息到消息总线 } /** * 从消息总线接收到需要发送的HTTP响应,将响应发送给客户端 * @param key * @param resp */ protected void processResponse(SelectionKey key) { String resp = prepareTestResponse(); super.sendResponse(key, resp); }如上所示,start和acceptConnection方法只需要简单的调用基类方法就可以了。
对processRequest方法,我们调用基类的readRequest方法,获取到包含原始请求内容的二元元组,调用消息引擎生成消息,然后再将消息发送到消息总线Plato上。
对processResponse方法,我们首先生成响应的内容,然后调用基类的sendResponse方法来进行发送。
好了,到此为止,一次简单的代码重构就完成了。在实际开发中,我们一定不要偷懒,要经常进行代码重构,否则代码会变得越来越臃肿,质量越来越差,越来越不可维护。
如果有不请楚的地方,请大家参考Github上的开源项目:https://github.com/yt7589/imsa ,如果大家觉得项目对大家有用,请点赞支持一下作者,谢谢!