Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in
its
ChannelPipeline.
Sub-types
ChannelHandler itself does not provide many methods, but you usually have to implement one of its subtypes:
-
ChannelInboundHandler to handle inbound I/O events, and
-
ChannelOutboundHandler to handle outbound I/O operations.
Alternatively, the following adapter classes are provided for your convenience:
-
ChannelInboundHandlerAdapter to handle inbound I/O events,
-
ChannelOutboundHandlerAdapter to handle outbound I/O operations, and
-
ChannelDuplexHandler to handle both inbound and outbound events
For more information, please refer to the documentation of each subtype.
The context object
A
ChannelHandler is provided with a
ChannelHandlerContextobject. A
ChannelHandler is supposed to interact with the
ChannelPipeline it belongs to via a context object. Using the
context object, the
ChannelHandler can pass events upstream or
downstream, modify the pipeline dynamically, or store the information
(using
AttributeKeys) which is specific to the handler.
State management
A
ChannelHandler often needs to store some stateful information.
The simplest and recommended approach is to use member variables:
public interface Message {
// your methods here
}
public class DataServerHandler extends
SimpleChannelInboundHandler<Message> {
private boolean loggedIn;
@Overridepublic void channelRead0(
ChannelHandlerContext ctx, Message message) {
if (message instanceof LoginMessage) {
authenticate((LoginMessage) message);
loggedIn = true;
} else (message instanceof GetDataMessage) {
if (loggedIn) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
} else {
fail();
}
}
}
...
}
Because the handler instance has a state variable which is dedicated to
one connection, you have to create a new handler instance for each new
channel to avoid a race condition where a unauthenticated client can get
the confidential information:
// Create a new handler instance per channel.
// See
ChannelInitializer#initChannel(Channel).
public class DataServerInitializer extends
ChannelInitializer<
Channel> {
@Overridepublic void initChannel(
Channel channel) {
channel.pipeline().addLast("handler", new DataServerHandler());
}
}
Using
AttributeKeys
Although it's recommended to use member variables to store the state of a
handler, for some reason you might not want to create many handler instances.
In such a case, you can use
AttributeKeys which is provided by
ChannelHandlerContext:
public interface Message {
// your methods here
}
@Sharablepublic class DataServerHandler extends
SimpleChannelInboundHandler<Message> {
private final
AttributeKey<
Boolean> auth =
AttributeKey#valueOf(String);
@Overridepublic void channelRead(
ChannelHandlerContext ctx, Message message) {
Attribute<
Boolean> attr = ctx.attr(auth);
if (message instanceof LoginMessage) {
authenticate((LoginMessage) o);
attr.set(true);
} else (message instanceof GetDataMessage) {
if (Boolean.TRUE.equals(attr.get())) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) o));
} else {
fail();
}
}
}
...
}
Now that the state of the handler is attached to the
ChannelHandlerContext, you can add the
same handler instance to different pipelines:
public class DataServerInitializer extends
ChannelInitializer<
Channel> {
private static final DataServerHandler SHARED = new DataServerHandler();
@Overridepublic void initChannel(
Channel channel) {
channel.pipeline().addLast("handler", SHARED);
}
}
The
@Sharable annotation
In the example above which used an
AttributeKey,
you might have noticed the
@Sharable annotation.
If a
ChannelHandler is annotated with the
@Sharableannotation, it means you can create an instance of the handler just once and
add it to one or more
ChannelPipelines multiple times without
a race condition.
If this annotation is not specified, you have to create a new handler
instance every time you add it to a pipeline because it has unshared state
such as member variables.
This annotation is provided for documentation purpose, just like
the JCIP annotations.
Additional resources worth reading
Please refer to the
ChannelHandler, and
ChannelPipeline to find out more about inbound and outbound operations,
what fundamental differences they have, how they flow in a pipeline, and how to handle
the operation in your application.