处理I/O事件或拦截I/O操作,并将其转发给其{@link ChannelPipeline}中的下一个处理程序。
子类型
{@link ChannelHandler}本身并没有提供很多方法,但是您通常需要实现它的一个子类型:
- {@link ChannelInboundHandler}来处理入站I/O事件
- {@link ChannelOutboundHandler}来处理出站I/O操作。
另外,为了方便您,还提供了以下适配器类:
- {@link ChannelInboundHandlerAdapter}来处理入站I/O事件
- {@link ChannelOutboundHandlerAdapter}来处理出站I/O操作
- {@link ChannelDuplexHandler}来处理入站和出站事件
有关更多信息,请参阅每个子类型的文档。
上下文对象
一个{@link ChannelHandler}拥有一个{@link ChannelHandlerContext}对象。一个{@link ChannelHandler}应该通过上下文对象与它所属的{@link ChannelPipeline}交互。使用上下文对象,{@link ChannelHandler}可以向上或向下传递事件,动态修改管道,或者存储特定于处理程序的信息(使用{@link AttributeKey}s)。
状态管理
{@link ChannelHandler}通常需要存储一些有状态信息。最简单和推荐的方法是使用成员变量:
* public interface Message {
* // your methods here
* }
*
* public class DataServerHandler extends {@link SimpleChannelInboundHandler}<Message> {
*
* <b>private boolean loggedIn;</b>
*
* {@code @Override}
* public void channelRead0({@link ChannelHandlerContext} ctx, Message message) {
* if (message instanceof LoginMessage) {
* authenticate((LoginMessage) message);
* <b>loggedIn = true;</b>
* } else (message instanceof GetDataMessage) {
* if (<b>loggedIn</b>) {
* ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
* } else {
* fail();
* }
* }
* }
* ...
* }
由于处理程序实例具有一个状态变量,该状态变量专用于一个连接,因此必须为每个新通道创建一个新的处理程序实例,以避免竞态条件,即未经身份验证的客户端可以获取机密信息:
* // Create a new handler instance per channel.
* // See {@link ChannelInitializer#initChannel(Channel)}.
* public class DataServerInitializer extends {@link ChannelInitializer}<{@link Channel}> {
* {@code @Override}
* public void initChannel({@link Channel} channel) {
* channel.pipeline().addLast("handler", <b>new DataServerHandler()</b>);
* }
* }
*
使用{@link AttributeKey}s
尽管建议使用成员变量来存储处理程序的状态,但出于某些原因,您可能不希望创建许多处理程序实例。在这种情况下,您可以使用{@link ChannelHandlerContext}提供的{@link AttributeKey}s:
* public interface Message {
* // your methods here
* }
*
* {@code @Sharable}
* public class DataServerHandler extends {@link SimpleChannelInboundHandler}<Message> {
* private final {@link AttributeKey}<{@link Boolean}> auth =
* {@link AttributeKey#valueOf(String) AttributeKey.valueOf("auth")};
*
* {@code @Override}
* public void channelRead({@link ChannelHandlerContext} ctx, Message message) {
* {@link Attribute}<{@link Boolean}> attr = ctx.attr(auth);
* if (message instanceof LoginMessage) {
* authenticate((LoginMessage) o);
* <b>attr.set(true)</b>;
* } else (message instanceof GetDataMessage) {
* if (<b>Boolean.TRUE.equals(attr.get())</b>) {
* ctx.writeAndFlush(fetchSecret((GetDataMessage) o));
* } else {
* fail();
* }
* }
* }
* ...
* }
现在,处理程序的状态附加到{@link ChannelHandlerContext},您可以将相同的处理程序实例添加到不同的管道:
* public class DataServerInitializer extends {@link ChannelInitializer}<{@link Channel}> {
*
* private static final DataServerHandler <b>SHARED</b> = new DataServerHandler();
*
* {@code @Override}
* public void initChannel({@link Channel} channel) {
* channel.pipeline().addLast("handler", <b>SHARED</b>);
* }
* }
{@code @Sharable}注解
在上面使用{@link AttributeKey}的例子中,您可能注意到了{@code @Sharable}注释。
如果一个{@link ChannelHandler}用{@code @Sharable}注释,这意味着您可以只创建一个处理程序实例,并将其多次添加到一个或多个{@link ChannelPipeline}中,而不需要竞态条件。
如果未指定此注释,则必须在每次将其添加到管道时创建一个新的处理程序实例,因为它具有成员变量等未共享状态。
这个注释是为文档目的提供的,就像<a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.
其他值得阅读的资源
请参考{@link ChannelHandler}和{@link ChannelPipeline}来了解更多关于入站和出站操作的信息,它们之间的基本区别是什么,它们如何在管道中流动,以及如何在应用程序中处理操作。
void handlerAdded(ChannelHandlerContext ctx) throws Exception;在将{@link ChannelHandler}添加到实际上下文并准备好处理事件之后调用。
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;在从实际上下文中删除{@link ChannelHandler}并不再处理事件后调用。