1.8. 使用POJO代替ChannelBuffer
目前为止所有的实例程序都是使用ChannelBuffer做为协议消息的原始数据结构。在这一节,我们将改进时间协议服务的客户/服务端实现,使用POJO 而不是ChannelBuffer做为协议消息的原始数据结构。
在你的ChannelHandler实现中使用POJO的优势是很明显的;从你的ChannelHandler实现中分离从ChannelBuffer获取数据的代码,将有助于提高你的ChannelHandler实现的可维护性和可重用性。在时间协议服务的客户/服务端代码中,直接使用ChannelBuffer读取一个32位的整数并不是一个主要的问题。然而,你会发现,当你试图实现一个真实的协议的时候,这种代码上的分离是很有必要的。
首先,让我们定义一个称之为UnixTime的新类型。
package org.jboss.netty.example.time;
import java.util.Date;
public class UnixTime {
private final int value;
public UnixTime(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return new Date(value * 1000L).toString();
}
}
现在让我们重新修改TimeDecoder实现,让其返回一个UnixTime,而不是一个ChannelBuffer。
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
if (buffer.readableBytes() < 4) {
return null;
}
return new UnixTime(buffer.readInt());
}
FrameDecoder和ReplayingDecoder允许你返回一个任何类型的对象。如果它们仅允许返回一个ChannelBuffer类型的对象,我们将不得不插入另一个可以从ChannelBuffer对象转换 为UnixTime对象的ChannelHandler实现。
有了这个修改后的decoder实现,这个TimeClientHandler便不会再依赖ChannelBuffer。
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
UnixTime m = (UnixTime) e.getMessage();
System.out.println(m);
e.getChannel().close();
}
更加简单优雅了,不是吗?同样的技巧也可以应用在服务端,让我们现在更新TimeServerHandler的实现:
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
UnixTime time = new UnixTime(System.currentTimeMillis() / 1000);
ChannelFuture f = e.getChannel().write(time);
f.addListener(ChannelFutureListener.CLOSE);
}
现在剩下的唯一需要修改的部分是这个ChannelHandler实现,这个ChannelHandler实现需要把一个UnixTime对象重新转换为一个ChannelBuffer。但这却已是相当简单了,因为当你对消息进行编码的时候你不再需要处理数据包的拆分及组装。
package org.jboss.netty.example.time;
import static org.jboss.netty.buffer.ChannelBuffers.*;
@ChannelPipelineCoverage("all")
public class TimeEncoder extends SimpleChannelHandler {
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) {
UnixTime time = (UnixTime) e.getMessage();
ChannelBuffer buf = buffer(4);
buf.writeInt(time.getValue());
Channels.write(ctx, e.getFuture(), buf);
}
}
代码说明
1) 因为这个encoder是无状态的,所以其使用的ChannelPipelineCoverage注解值是“all”。实际上,大多数encoder实现都是无状态的。
2) 一个encoder通过重写writeRequested方法来实现对写操作请求的拦截。不过请注意虽然这个writeRequested方法使用了和 messageReceived方法一样的MessageEvent参数,但是它们却分别对应了不同的解释。一个ChannelEvent事件可以既是一个上升流事件(upstream event)也可以是一个下降流事件(downstream event),这取决于事件流的方向。例如:一个MessageEvent消息事件可以作为一个上升流事件(upstream event)被messageReceived方法调用,也可以作为一个下降流事件(downstream event)被writeRequested方法调用。请参考API手册获得上升流事件(upstream event)和下降流事件(downstream event)的更多信息。
3) 一旦完成了POJO和ChannelBuffer转换,你应当确保把这个新的buffer缓冲转发至先前的 ChannelDownstreamHandler处理,这个下降通道的处理器由某个ChannelPipeline管理。Channels提供了多个可以创建和发送ChannelEvent事件的帮助方法。在这个例子中,Channels.write(...)方法创建了一个新的 MessageEvent事件,并把这个事件发送给了先前的处于某个ChannelPipeline内的 ChannelDownstreamHandler处理器。
另外,一个很不错的方法是使用静态的方式导入Channels类:
import static org.jboss.netty.channel.Channels.*;
...
ChannelPipeline pipeline = pipeline();
write(ctx, e.getFuture(), buf);
fireChannelDisconnected(ctx);
2) 一个encoder通过重写writeRequested方法来实现对写操作请求的拦截。不过请注意虽然这个writeRequested方法使用了和 messageReceived方法一样的MessageEvent参数,但是它们却分别对应了不同的解释。一个ChannelEvent事件可以既是一个上升流事件(upstream event)也可以是一个下降流事件(downstream event),这取决于事件流的方向。例如:一个MessageEvent消息事件可以作为一个上升流事件(upstream event)被messageReceived方法调用,也可以作为一个下降流事件(downstream event)被writeRequested方法调用。请参考API手册获得上升流事件(upstream event)和下降流事件(downstream event)的更多信息。
3) 一旦完成了POJO和ChannelBuffer转换,你应当确保把这个新的buffer缓冲转发至先前的 ChannelDownstreamHandler处理,这个下降通道的处理器由某个ChannelPipeline管理。Channels提供了多个可以创建和发送ChannelEvent事件的帮助方法。在这个例子中,Channels.write(...)方法创建了一个新的 MessageEvent事件,并把这个事件发送给了先前的处于某个ChannelPipeline内的 ChannelDownstreamHandler处理器。
另外,一个很不错的方法是使用静态的方式导入Channels类:
import static org.jboss.netty.channel.Channels.*;
...
ChannelPipeline pipeline = pipeline();
write(ctx, e.getFuture(), buf);
fireChannelDisconnected(ctx);
最后的任务是把这个TimeEncoder插入服务端的ChannelPipeline,这是一个很简单的步骤。