您的位置:首页 > 其它

MINA2.0用户手册中文版--第五章 MINA中的过滤器

2013-09-11 11:16 676 查看
过滤器IoFilter是MINA核心结构之一,它扮演着一个很重要的角色。它可以过滤所有在MINA服务和对应处理程序之间的I/O事件和请求。如果你有编写Java网络应用程序的经验,你可以放心的把他当做Servlet过滤器的一个远亲。MINA提供了很多现成的过滤器,它们通过简化典型的横切关注点,来加快网络应用程序的开发步伐,例如:

日志过滤器LoggingFilter记录所有事件和请求
协议编码解码过滤器ProtocolCodecFilter将传入的字节流转换成消息对象,或进行反向操作
压缩过滤器CompressionFilter负责压缩所有的数据
安全套阶层过滤器SSLFilter提供SSL、TLS、STARTTLS支持
还有更多其他的现成的过滤器

在本手册中,我们将通过一个实际的案例来看看如何实现一个IoFilter接口。实现一个IoFilter通常都很简单,但你需要了解MINA内部的实现细节,这里将说明各种相关的内部属性。


目前已提供的过滤器

我们提供了很多已经写好的过滤器,下面的表格列出了所有已存在的过滤器,附带了用法简述:

阻塞使 将IoSession的主键属性注入线程映射表 ,如消息已接收,消息已发送,session打开等事件
过滤器对应类用法简述
BlacklistBlacklistFilter将黑名单中的远程地址的连接置为阻塞状态
Buffered WriteBufferedWriteFilter缓冲传出的请求,类似于BufferedOutputStream的作用
CompressionCompressionFilter压缩所有的数据
ConnectionThrottleConnectionThrottleFilter连接调节器,当连接的速度快于指定的时间间隔时进行阻塞操作
ErrorGeneratingErrorGeneratingFilter
ExecutorExecutorFilter
FileRegionWriteFileRegionWriteFilter
KeepAliveKeepAliveFilter
LoggingLoggingFilter记录事件日志信息,如消息已接收,消息已发送,session打开等等
MDC InjectionMdcInjectionFilter将IoSession的主键属性注入线程映射表MDC中
NoopNoopFilter只是用于测试并不进行实际操作的过滤器
ProfilerProfilerTimerFilter测量事件执行时间的过滤器,如消息已接收,消息已发送,session打开等事件的执行时间
ProtocolCodecProtocolCodecFilter编码解码过滤器
ProxyProxyFilter
Reference countingReferenceCountingFilter跟踪过滤器使用次数的过滤器
RequestResponseRequestResponseFilter
SessionAttributeInitializingSessionAttributeInitializingFilter
StreamWriteStreamWriteFilter
SslFilterSslFilter
WriteRequestWriteRequestFilter


有选择的覆盖事件

你可以通过继承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版本有些不同,参见CompressionFilterRequestResponseFilter 。(注:两个版本有所不同,这里先留下个疑问,进一步研究过滤器时,再来深究)


注意对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模块,也不必非得像上面的例子中那样来实现,你可以在任何一个希望的地方来校验这个缓冲区是否为空,只要保证你的过滤器不抛出不可预料的异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: