我们已经实现了一些基础的功能,但是由于我们只是以最简实现的方式来实现这些功能,所以代码远谈不上优化,因此我们在这篇博文中,先暂时不开发新功能,而是对现有功能进行重构。
首先,我们不仅在门户Facade中需要Nio服务器,在消息总线Plato接收微服务注册、接收系统消息时也需要NIO服务器,同时微服务控制器和微服务接收消息总线消息时,也需要NIO服务器,目前这种实现方式,显然不能满足这些重用需求,因此我们需要在common项目中引入NioTcpServer基类,然后门户Facade、消息总线Plato和微服务所需要的NIO服务器均继承此类。
为了更好地实现功能,我们需要借鉴Python等语言,引入元组的功能。元组Turple是一个对象,动态拥有不可变的,任意数量的属性。之所以引入元组,是因为当函数需要返回多个值时,通常的做法为其定义一个新的值对象类,但是这样的结果是产生一大堆仅用于传值的类,不便于管理,所以我们有必要定义一个自己的元组类,让Java也能像Python那样方便的使用元组。我们先定义具有两个元素的元组。代码如下所示:
public class Turple2 {
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 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 readRequest(SelectionKey key, Selector selector) {
SocketChannel channel = (SocketChannel) key.channel();
Turple2 rst = new Turple2("", 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(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 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 ,如果大家觉得项目对大家有用,请点赞支持一下作者,谢谢!