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

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

  • 日志过滤器LoggingFilter记录所有事件和请求
  • 协议编码解码过滤器ProtocolCodecFilter将传入的字节流转换成消息对象,或进行反向操作
  • 压缩过滤器CompressionFilter负责压缩所有的数据
  • 安全套阶层过滤器SSLFilter提供SSL、TLS、STARTTLS支持
  • 还有更多其他的现成的过滤器
在本手册中,我们将通过一个实际的案例来看看如何实现一个IoFilter接口。实现一个IoFilter通常都很简单,但你需要了解MINA内部的实现细节,这里将说明各种相关的内部属性。

目前已提供的过滤器

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

阻塞使    将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模块,也不必非得像上面的例子中那样来实现,你可以在任何一个希望的地方来校验这个缓冲区是否为空,只要保证你的过滤器不抛出不可预料的异常。

你可能感兴趣的:(MINA2.0用户手册中文版--第五章 MINA中的过滤器)