过滤器IoFilter是MINA核心结构之一,它扮演着一个很重要的角色。它可以过滤所有在MINA服务和对应处理程序之间的I/O事件和请求。如果你有编写Java网络应用程序的经验,你可以放心的把他当做Servlet过滤器的一个远亲。MINA提供了很多现成的过滤器,它们通过简化典型的横切关注点,来加快网络应用程序的开发步伐,例如:
- 日志过滤器LoggingFilter记录所有事件和请求
- 协议编码解码过滤器ProtocolCodecFilter将传入的字节流转换成消息对象,或进行反向操作
- 压缩过滤器CompressionFilter负责压缩所有的数据
- 安全套阶层过滤器SSLFilter提供SSL、TLS、STARTTLS支持
- 还有更多其他的现成的过滤器
目前已提供的过滤器
我们提供了很多已经写好的过滤器,下面的表格列出了所有已存在的过滤器,附带了用法简述:
阻塞使 将IoSession的主键属性注入线程映射表 ,如消息已接收,消息已发送,session打开等事件
过滤器 | 对应类 | 用法简述 |
---|---|---|
Blacklist | BlacklistFilter | 将黑名单中的远程地址的连接置为阻塞状态 |
Buffered Write | BufferedWriteFilter | 缓冲传出的请求,类似于BufferedOutputStream的作用 |
Compression | CompressionFilter | 压缩所有的数据 |
ConnectionThrottle | ConnectionThrottleFilter | 连接调节器,当连接的速度快于指定的时间间隔时进行阻塞操作 |
ErrorGenerating | ErrorGeneratingFilter | |
Executor | ExecutorFilter | |
FileRegionWrite | FileRegionWriteFilter | |
KeepAlive | KeepAliveFilter | |
Logging | LoggingFilter | 记录事件日志信息,如消息已接收,消息已发送,session打开等等 |
MDC Injection | MdcInjectionFilter | 将IoSession的主键属性注入线程映射表MDC中 |
Noop | NoopFilter | 只是用于测试并不进行实际操作的过滤器 |
Profiler | ProfilerTimerFilter | 测量事件执行时间的过滤器,如消息已接收,消息已发送,session打开等事件的执行时间 |
ProtocolCodec | ProtocolCodecFilter | 编码解码过滤器 |
Proxy | ProxyFilter | |
Reference counting | ReferenceCountingFilter | 跟踪过滤器使用次数的过滤器 |
RequestResponse | RequestResponseFilter | |
SessionAttributeInitializing | SessionAttributeInitializingFilter | |
StreamWrite | StreamWriteFilter | |
SslFilter | SslFilter | |
WriteRequest | WriteRequestFilter |
有选择的覆盖事件
你可以通过继承IoFilterAdapter类来代替直接实现IoFilter接口来创建一个过滤器。但是,如果你不重写该类中的方法的话,所有接收到的请求事件都会立即被直接传给过滤器链中的下一个:
public class MyFilter extends IoFilterAdapter { @Override public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { // Some logic here... nextFilter.sessionOpened(session); // Some other logic here... } }
转换写入请求
当你准备通过IoSession.write()方法来转换传入的写入请求时,这实现起来会稍微复杂一点。例如,我们假设当高级别消息对象调用IoSession.write()时,你的过滤器会将高级消息转换为低级消息;你会插入合适的转换代码在你过滤器的filterWrite()方法中,并认为这就足够了。然而,你必须要注意messageSent事件,因为一个处理程序或在你过滤器之后的任何过滤器总认为messageSent()方法会被高级别消息作为一个参数调用;而当调用者明明写入的是高级别消息,却收到低级别消息被发送出去的通知,这是不合理的。所以,如果你的过滤器有转换操作,你必须同时实现filterWrite()和messageSent()方法。
同时注意,即使输入对象和输出对象的类型是相同的,你也要使用类似的机制来实现(例如压缩过滤器CompressionFilter),因为IoSession.write()的调用者希望精确的获得在他们的messageSent()方法中写入的是什么。
假设你实现了一个过滤器用来转换一个字符串String为一个字符数字char[],你过滤器的filterWri()方法会类似于如下实现:
public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest request) { nextFilter.filterWrite( session, new DefaultWriteRequest( ((String) request.getMessage()).toCharArray(), request.getFuture(), request.getDestination())); }
接下来,是在messageSent()中反向操作:
public void messageSent(NextFilter nextFilter, IoSession session, Object message) { nextFilter.messageSent(session, new String((char[]) message)); }
如果是String-to-ByteBuffer转换过滤器呢?我们可以稍微省事儿一点点,因为不需要重新创建原始消息(String字符串消息)。然而,这还是要比上一个例子稍微复杂一点:
public void filterWrite(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())); } public void messageSent(NextFilter nextFilter, IoSession session, Object message) { if (message instanceof MyByteBuffer) { nextFilter.messageSent(session, ((MyByteBuffer) message).originalValue); } else { nextFilter.messageSent(session, message); } } private static class MyByteBuffer extends ByteBufferProxy { private final Object originalValue; private MyByteBuffer(Object originalValue, ByteBuffer encodedValue) { super(encodedValue); this.originalValue = originalValue; } }
如果使用MINA 2.0,它会和1.0和1.1版本有些不同,参见CompressionFilter 和RequestResponseFilter 。(注:两个版本有所不同,这里先留下个疑问,进一步研究过滤器时,再来深究)
注意对sessionCreated事件的过滤
sessionCreated是一个特殊的事件,它必须在I/O处理器的线程中执行(参见配置线程模型配置),请勿将sessionCreated事件转发到其他线程中执行。
public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { // ... nextFilter.sessionCreated(session); } // DON'T DO THIS! public void sessionCreated(final NextFilter nextFilter, final IoSession session) throws Exception { Executor executor = ...; executor.execute(new Runnable() { nextFilter.sessionCreated(session); }); }
注意空缓冲区!
MINA在几个例子中使用空缓冲区作为一个内部信号,但是空缓冲区有时却会带来一些问题,它会导致各种异常,如IndexOutBoundsException。这个部分我们将来讲解如何避免这些异常。
协议编解码过滤器ProtocolCodecFilter就使用空缓冲区来标志消息的结束(即buf.hasRemaining()=0)。如果你自定义的过滤器被放置在了过滤器ProtocolCodecFilter的前面,请务必保证你的过滤器在最后传递一个空缓冲区到下一个过滤器,并实现当缓冲器是空的时可以抛出一个意想不到异常。
public void messageSent(NextFilter nextFilter, IoSession session, Object message) { if (message instanceof ByteBuffer && !((ByteBuffer) message).hasRemaining()) { nextFilter.messageSent(nextFilter, session, message); return; } ... } public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest request) { Object message = request.getMessage(); if (message instanceof ByteBuffer && !((ByteBuffer) message).hasRemaining()) { nextFilter.filterWrite(nextFilter, session, request); return; } ... }
那么,我们是不是必须在每一个过滤器里面都插入类似上面的if模块代码呢?其实没必要的,下面给出了处理空缓冲区的黄金法则:
- 当出现空缓冲区时,你的过滤器仍能够正常工作时,你就没有必要增加这个if模块
- 如果你的过滤器在过滤器链中的位置处于协议编码解码过滤器ProtocolCodecFilter之后,你就不需要增加该if模块
- 其余情况,你都需要添加这个if模块
如果你真的需要使用if模块,也不必非得像上面的例子中那样来实现,你可以在任何一个希望的地方来校验这个缓冲区是否为空,只要保证你的过滤器不抛出不可预料的异常。