Mina中的过滤器,位于ioService和ioHandler之间,用于在正式的业务处理之前,做一些额外的预处理或者过滤。比如,编解码过滤器,日志过滤器,心跳过滤器等。下图是mina官网上的一个mina应用的结构图:
多个过滤器共同组成过滤器链,这篇文章对mina中的过滤器的自定义、如何进入和退出过滤器链、过滤器之间的传递、过滤器链中过滤器的顺序问题等做一个研究。
mina中默认使用的过滤器链是DefaultIoFilterChain,对于过滤器和过滤器链,基本上所有的需要了解的关键都在这个类里面。
1. 自定义过滤器。
这个问题不是本篇的重点,简单说一下就行。比如,我们需要在mina应用中插入日志功能,mina中有一个现在的LoggingFilter,插入即可:
DefaultIoFilterChain.addLast(new LoggingFilter())
2. 如何进入过滤器链
比如说,服务端要写数据到客户端,流程是什么时候进入过滤器链的?
要发送数据,首先在handler里面调用iosession.write(msg), 稍微一跟踪,就会发现,write方法中,调用了过滤器链的fireFilterWrite()方法:
filterChain.fireFilterWrite(writeRequest);
这就进入了过滤器链,然后接着跟踪,最终会按顺序调用一个个过滤器的filterWrite()方法:
filter.filterWrite(nextFilter, session, writeRequest);
在查看DefaultIoFilterChain类中的源码,fire开头的方法很多,如fireMessageReceived、fireSessionClosed,。。。这些方法的意思也很明显,触发相关的事件。
3. 如何退出过滤器链
现在考虑一个问题,一般的过滤器,他们的事件处理方法中,处理完之后,一般都会调用下一个过滤器的处理方法,如:LoggingFilter中messageReceived方法:
@Override
public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
log(messageReceivedLevel, "RECEIVED: {}", message);
nextFilter.messageReceived(session, message);
}
我们知道,所有过滤器都处理完了之后,messageReceived会转交给IoHandler处理,这件事应该是最后一个过滤器来做,但是自己的业务代码中从来没有写过把消息转交给handler的工作。猜测:DefaultIoFilterChain中应该有一个最后把关的过滤器,来做这个事情,所以翻开代码继续查看:
DefaultIoFilterChain有一个head和tail,构造DefaultIoFilterChain时,对这两个成员进行了初始化:
public DefaultIoFilterChain(AbstractIoSession session) {
if (session == null) {
throw new IllegalArgumentException("session");
}
this.session = session;
head = new EntryImpl(null, null, "head", new HeadFilter());
tail = new EntryImpl(head, null, "tail", new TailFilter());
head.nextEntry = tail;
}
head和tail是一个EntryImpl,里面包含filter。从这里看出head包含一个HeadFilter,tail包含一个TailFilter。
根据猜测,TailFilter是最后一个filter,那么我们看看他的messageReceived是怎么写的:
session.getHandler().messageReceived(s, message);
大部分省略,上面这句是重点,调用了handler的方法,猜测得到验证。
配合DefaultIoFilterChain的addLast、addFirst等方法,我们发现,不管我们往过滤器链中插入了多少过滤器,head始终作为第一个节点,而tail始终作为最后一个。
至于head有什么作为,下面会讲。
4. 过滤器的传递方向问题
继续说write,写数据进入过滤器链中,调用fireFilterWrite方法,在这个方法中调用了另一个方法:callPreviousFilterWrite,这个方法有点奇怪,调用前一个filterWrite方法?
在其他的fire方法中,调用的是next,比如fireMessageReceived中,调用了callNextMessageReceived,调用下一个的messageReceived方法。那为什么在fireFilterWrite 中,要调用前一个的filterWrite方法呢?方法的逻辑是否真和方法名所表明的一样呢?
public void fireFilterWrite(WriteRequest writeRequest) {
callPreviousFilterWrite(tail, session, writeRequest);
}
private void callPreviousFilterWrite(Entry entry, IoSession session, WriteRequest writeRequest) {
try {
IoFilter filter = entry.getFilter();
NextFilter nextFilter = entry.getNextFilter();
filter.filterWrite(nextFilter, session, writeRequest);
} catch (Exception e) {
writeRequest.getFuture().setException(e);
fireExceptionCaught(e);
} catch (Error e) {
writeRequest.getFuture().setException(e);
fireExceptionCaught(e);
throw e;
}
}
查看源码发现,fireFilterWrite方法确实是从tail开始的,首先调用了tail中的filter的filterWrite方法,跟踪发现Tail的filterWrite方法中又调用了他的下一个filter的filterWrite:
@Override
public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
nextFilter.filterWrite(session, writeRequest);
}
不仅如此,其他继承了IoFilterAdapter的普通filter,也都是调用了nextFilter.filterWrite(session, writeRequest); 既然调用的是nextFilter,为什么叫callPreviousFilterWrite?
在创建每个过滤器entry的时候,构造了他的nextFilter,我们发现,每个过滤器的构造,都是一个EntryImpl对象,我们看看这个对象的源码,构造方法中有这么一段:
this.nextFilter = new NextFilter() {
public void sessionCreated(IoSession session) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextSessionCreated(nextEntry, session);
}
public void sessionOpened(IoSession session) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextSessionOpened(nextEntry, session);
}
public void sessionClosed(IoSession session) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextSessionClosed(nextEntry, session);
}
public void sessionIdle(IoSession session, IdleStatus status) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextSessionIdle(nextEntry, session, status);
}
public void exceptionCaught(IoSession session, Throwable cause) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextExceptionCaught(nextEntry, session, cause);
}
public void inputClosed(IoSession session) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextInputClosed(nextEntry, session);
}
public void messageReceived(IoSession session, Object message) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextMessageReceived(nextEntry, session, message);
}
public void messageSent(IoSession session, WriteRequest writeRequest) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextMessageSent(nextEntry, session, writeRequest);
}
public void filterWrite(IoSession session, WriteRequest writeRequest) {
Entry nextEntry = EntryImpl.this.prevEntry;
callPreviousFilterWrite(nextEntry, session, writeRequest);
}
public void filterClose(IoSession session) {
Entry nextEntry = EntryImpl.this.prevEntry;
callPreviousFilterClose(nextEntry, session);
}
public String toString() {
return EntryImpl.this.nextEntry.name;
}
};
这里定义了nextFilter, 注意这里面filterWrite方法和 filterClose方法的不同:
Entry nextEntry = EntryImpl.this.prevEntry;
callPreviousFilterWrite(nextEntry, session, writeRequest);
把prevEntry赋给了nextENtry, 与messageReceived刚好相反,这样就一清二楚了,所谓的nextEntry其实是prevEntry。那么我们继续猜测,在写的时候,head将会是最后一个过滤器,负责把消息交给下层的ioProcessor处理,看看HeadFilter的源码:
private class HeadFilter extends IoFilterAdapter {
@SuppressWarnings("unchecked")
@Override
public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
AbstractIoSession s = (AbstractIoSession) session;
// Maintain counters.
if (writeRequest.getMessage() instanceof IoBuffer) {
IoBuffer buffer = (IoBuffer) writeRequest.getMessage();
// I/O processor implementation will call buffer.reset()
// it after the write operation is finished, because
// the buffer will be specified with messageSent event.
buffer.mark();
int remaining = buffer.remaining();
if (remaining > 0) {
s.increaseScheduledWriteBytes(remaining);
}
} else {
s.increaseScheduledWriteMessages();
}
WriteRequestQueue writeRequestQueue = s.getWriteRequestQueue();
if (!s.isWriteSuspended()) {
if (writeRequestQueue.isEmpty(session)) {
// We can write directly the message
s.getProcessor().write(s, writeRequest);
} else {
s.getWriteRequestQueue().offer(s, writeRequest);
s.getProcessor().flush(s);
}
} else {
s.getWriteRequestQueue().offer(s, writeRequest);
}
}
@SuppressWarnings("unchecked")
@Override
public void filterClose(NextFilter nextFilter, IoSession session) throws Exception {
((AbstractIoSession) session).getProcessor().remove(session);
}
}
重点是下面这句:
if (writeRequestQueue.isEmpty(session)) {
// We can write directly the message
s.getProcessor().write(s, writeRequest);
} else {
s.getWriteRequestQueue().offer(s, writeRequest);
s.getProcessor().flush(s);
}
再次得到证实。
总结:
我们发现,NextFilter中,只有filterClose方法和filterWrite相同,把prevEntry作为nextEntry。
这种现象的本质是,写和连接关闭的事件,在过滤器链中的传递方向,与其他事件的传递方向是刚好相反的,这是为什么?
上图F1、F2、F3是过滤器链中的三个过滤器,事件流的方向有A和B两种方向,服务端向客户端写数据,是一种handler主动的操作,在handler中调用session的write方法,所以事件流是A方向,而当有消息收到时,事件流的方向是B,其他类似。
再假设,F1和F2是两个不同的编解码器,当有消息来时,首先经过F2解码,再经过F1解码,最后交给handler处理。那么,当要发送消息的时候,要对消息进行编码,是不是应该先经过F1编码,然后才让F2编码,如果反了,客户端解码就得不到正确结果。当然,这样做是基于服务端和客户端具有完全相同的过滤器链(包括过滤器顺序也要相同), 这是一个基本原则。