第五章 Filters——过滤器
IoFilter是MINA的核心结构之一,在MINA中扮演一个非常重要的角色。它过滤IoService和IoHandler之间的所有I/O事件和请求。如果你有web应用程序的编程经验,你可以把它看做是servlet过滤器的同胞兄弟。我们提供了很多开箱即用的过滤器,通过简化在一些典型的关注点上使用这些开箱即用的过滤器来加快网络应用程序开发的步伐。
(1)LoggingFilter:日志过滤器记录所有的事件和请求。
(2)ProtocolCodecFilter:协议编码过滤器,在输入的ByteBuffer转换及消息对象间做相互转换
(3)CompressionFilter:压缩过滤器,压缩所有数据
(4)SSLFilter:SSL过滤器,添加SSL - TLS – StartTLS支持
还有很多其他的过滤器。
在这个教程中,我们将展示在实际的应用场景中一个IoFilter是多么重要。通常实现一个IoFilter是很简单的,但你也可能需要了解MINA内部实现的细节。所有这些内部属性都将在这里做统一说明。
4.1现存的过滤器
我们有一些已经写好的过滤器,下表是所有现存的过滤器清单,包含个过滤器用法的简短描述。
过滤器名称 |
对应类 |
描述 |
Blacklist |
BlacklistFilter |
阻止在黑名单中的远程地址的连接 |
Buffered Write |
BufferedWriteFilter |
与BufferedOutputStream类似,实现外发请求缓存 |
Compression |
CompressionFilter |
|
ConnectionThrottle |
ConnectionThrottleFilter |
|
ErrorGenerating |
ErrorGeneratingFilter |
|
Executor |
ExecutorFilter |
|
FileRegionWrite |
FileRegionWriteFilter |
|
KeepAlive |
KeepAliveFilter |
|
Logging |
LoggingFilter |
记录所有消息事件,像收到消息,发出消息,打开会话... |
MDC Injection |
MdcInjectionFilter |
在多描述编码(MDC,Multiple Description Coding)中注入IoSession的key属性 |
Noop |
NoopFilter |
一个不做任何事情的过滤器,在测试时是非常有用的 |
Profiler |
ProfilerTimerFilter |
针对消息事件的用户配置过滤器 |
ProtocolCodec |
ProtocolCodecFilter |
一个负责消息编码、解码的过滤器 |
Proxy |
ProxyFilter |
|
Reference counting |
ReferenceCountingFilter |
跟踪使用这个过滤器的数量 |
RequestResponse |
RequestResponseFilter |
|
SessionAttributeInitializing |
SessionAttributeInitializingFilter |
|
StreamWrite |
StreamWriteFilter |
|
SslFilter |
SslFilter |
|
WriteRequest |
WriteRequestFilter |
|
4.2 选择性地覆盖事件
你可以通过继承IoAdapter而不是直接实现IoFilter来定义自己的过滤器。除非覆盖对应的事件方法,否则所有接收到的请求都将被立即发送给下一个过滤器。
publicclassMyFilterextends IoFilterAdapter {
@Override
publicvoidsessionOpened(NextFilter nextFilter, IoSession session)throws Exception {
// Some logic here...
nextFilter.sessionOpened(session);
// Some other logic here...
}
}
4.3转换一个写请求
如果你想转换一个通过IoSession.write()传入的写请求,事情会非常棘手。例如,我们假定你的过滤器要把调用IoSession.write()中的 HighLevelMessage对象转换为LowLevelMessage对象。你可能认为只要在你的过滤器的filterWrite()方法中插入适当的转换代码就够了,但是,不得不注意一点是你也需要考虑messageSent事件,因为一个IoHandler或后续的任何过滤器都希望将HighLevelMessage对象作为参数调用其messageSent()方法。当被调用的方法实际写入的是HighLevelMessage,却被告知发送的是LowLevelMessage对象,这是不合理的。因此,如果你的过滤器要进行转换操作,你就必须同时实现filterWrite()和messageSent()这两个方法。
还需要注意的一点是,即便是输入和输出的对象(例如:CompressionFilters)是完全相同的你也要采取类似的机制。因为这正是IoSession.write()的调用者所期望你写入到它的处理器方法messageSent()中的。
假定你要实现一个将String转换到一个char[]的过滤器,那么你的过滤器的filterWrite()方法看起来将是下面这个样子。
publicvoidfilterWrite(NextFilter nextFilter, IoSession session, WriteRequest request)
{
nextFilter.filterWrite(
session,new DefaultWriteRequest(
((String) request.getMessage()).toCharArray(), request.getFuture(), request.getDestination()));
}
接着,我们需要在messageSent()方法中做相反的转换。
publicvoidmessageSent(NextFilter nextFilter, IoSession session, Object message){
nextFilter.messageSent(session,new String((char[]) message));
}
如何做String到ByteBuffer的转换?因为我们不需要重构原始信息,因此可能更高效一些,即便如此,这个实现也比上一个例子复杂。
publicvoidfilterWrite(NextFilter nextFilter, IoSession session, WriteRequest request){
String m =(String) request.getMessage();
ByteBuffer newBuffer =new MyByteBuffer(m, ByteBuffer.wrap(m.getBytes());
nextFilter.filterWrite(
session,new WriteRequest(newBuffer, request.getFuture(), request.getDestination()));
}
publicvoidmessageSent(NextFilter nextFilter, IoSession session, Object message){
if(message instanceof MyByteBuffer){
nextFilter.messageSent(session,((MyByteBuffer) message).originalValue);
}else{
nextFilter.messageSent(session, message);
}
}
privatestaticclassMyByteBufferextends ByteBufferProxy {
privatefinal Object originalValue;
privateMyByteBuffer(Object originalValue, ByteBuffer encodedValue){
super(encodedValue);
this.originalValue= originalValue;
}
}
假入你使用的是MINA2.0,将和1.0及1.1有所不同。请参考对应的CompressionFilter 和 RequestResponseFilter.
4.4当过滤sessionCreated事件时要小心
sessionCreated是一个必须在I/O处理器线程中执行的事件(请参考配置线程模型)。千万不要把sessionCreated事件转发给其他线程。
publicvoidsessionCreated(NextFilter nextFilter, IoSession session)throws Exception {
// ...
nextFilter.sessionCreated(session);
}
// 千万别这么干
publicvoidsessionCreated(final NextFilter nextFilter,final IoSession session)throws Exception {
Executor executor =...;
executor.execute(new Runnable(){
nextFilter.sessionCreated(session);
});
}
4.5当心空缓冲区
在某些情况下,MINA将一个空缓冲区作为一个内部信号使用。空缓冲区有时会变成一个问题,因为它是引起某些异常的原因,例如IndexOutOfBoundsException。接下来将说明如何避免这种意外情况发生。
ProtocolCodecFilter使用一个空缓冲区(buf.hasRemaining() = 0)来标示一个消息的结束。如果你的过滤器位于ProtocolCodecFilter之前,而且你的过滤器是可以在缓冲区为空时抛出一个异常,请确保你的过滤器把这个空缓冲区转发给下一个过滤器。
publicvoidmessageSent(NextFilter nextFilter, IoSession session, Object message){
if(message instanceof ByteBuffer &&!((ByteBuffer) message).hasRemaining()){
nextFilter.messageSent(nextFilter, session, message);
return;
}
...
}
publicvoidfilterWrite(NextFilter nextFilter, IoSession session, WriteRequest request){
Object message = request.getMessage();
if(message instanceof ByteBuffer &&!((ByteBuffer) message).hasRemaining()){
nextFilter.filterWrite(nextFilter, session, request);
return;
}
...
}
我们是不是必须为每个过滤器都添加上面的if代码块?答案是不需要。下面是处理空缓冲区的黄金法则:
(1)如果缓冲区为空时你的过滤器可以正常运行,你不需要添加上面的If代码块。
(2)如果你的过滤器位于ProtocolCodecFilter之后,你不需要添加上面的if代码块。
(3)除上述两种情况外,你都需要这个if代码块。
如果你需要这个if代码块,请记住你并不需要总是遵循上面的例子。如果你想要你的过滤器不管在什么情况下都不会抛出一个意想不到的异常的话,你可以检测一下缓冲区是否为空。