问题
不同的 SimpleChannelInboundHandler 的实现会针对不同的请求数据类型调用 handler 进行处理。那么 Netty 是怎么实现在运行时调用 SimpleChannelInboundHandler 不同的子类处理方法进行处理的呢?
解析
Handler 链
首先我们知道 Netty Pipeline 将多个 Handler 使用双向链表串联起来。
ByteToMessageDecoder
首先我们看字节流怎么转为不同类型的 Message 的。
ByteToMessageDecoder 对字节流进行解码,解码方法不带返回值,是通过在 decode 方法体中解析出具体请求类型再放入 List<Object> out
引用代替方法返回值。
由于 List 的泛型是 Object,我们一股脑的直接将解析到的不同类型请求都放入返回值中。
注:这里只是举例,其实这里的 RpcProtocol<RpcResponse> 与 RpcProtocol<RpcRequest> 都是 RpcProtocol 这一种类型。
SimpleChannelInboundHandler
这个是 InboundHandler,在这里就实现了将外部请求分发给系统内部业务并进行处理。
观察几个不同 SimpleChannelInboundHandler 的实现可以发现,除了名字和泛型参数外别无二致。
那么,我们可以初步得出结论,分发原理基于泛型实现。
原理
一、SimpleChannelInboundHandler 与具体的实现:
通过接口定义发现,子类重写父类 channelRead0 时传入了具体的泛型参数。这个泛型参数将作为判断 Handler 是否支持处理该 Message 的重要依据。
二、是否支持的判断逻辑:
可以发现,父类通过模板方法模式,当检查发现当前 Handler 支持该 Message 的处理时,委托子类实现进行处理;当发现当前 Handler 不支持该 Message 的处理时,直接跳过本次 Handler 的 子类实现,通过 fire 一个 read 事件让 pipeline handler 链上的 下一个 handler 来进行处理。
三、继续跟踪 match 方法的实现:
最后我们发现是通过 Class#isInstance 实现的类型检查,也就是说:只要 msg 是当前 SimpleChannelInboundHandler 的泛型的实例或泛型子类的实例就是当前 Handler 所支持的 Message 类型,msg 也就会被作为参数传入当前子类 Handler 中进行处理。
综上,Netty SimpleChannelInboundHandler 的通过泛型指定特定对 msg 的处理原理分析也就告一段落了。
附
Class#isInstance vs instanceof
相同点:都是实例与类型的关系判断。
不同点:isInstance 的类型可以在运行时指定(如上图 type),instanceOf 的类型写死在编译时。
像我们 SimpleChannelInboundHandler 这里用到了泛型参数,Java 的泛型类型需要在运行时才能知道,显然不能对上述的泛型 I 使用 instanceof,此时 Class#isInstance 也就派上用场了。
Class#isInstance、instanceof 与 泛型
Class#isInstance、instanceof 都不支持嵌套泛型判断 ,如下:
public static void main(String[] args) {
RpcProtocol<RpcRequest> protocol1 = new RpcProtocol<>();
RpcProtocol<RpcResponse> protocol2 = new RpcProtocol<>();
// System.out.println(protocol instanceof RpcProtocol<RpcRequest>); // Illegal generic type for instanceof
System.out.println(protocol1.getClass().isInstance(protocol2)); // true
System.out.println(RpcProtocol.class.isInstance(protocol2));
// 再测试 Netty 工具类效果,由于基于 isInstance 原理,所以提前预测结果和上面一样:
TypeParameterMatcher matcher = TypeParameterMatcher.find(
new RpcRequestHandler(null),
SimpleChannelInboundHandler.class,
"I"); // RpcRequestHandler 实现了 SimpleChannelInboundHandler<I>,即,I 为 channelRead0 的第二个参数类型
System.out.println(matcher.match(protocol1)); // true
System.out.println(matcher.match(protocol2)); // true
}
综上,isInstance 会认为 RpcProtocol<RpcRequest>.class == RpcProtocol<RpcResponse>.class。
这也是为什么在上面说 RpcProtocol<RpcResponse> 与 RpcProtocol<RpcRequest> 是一种类型的原因。
基于 isInstance 实现的 TypeParameterMatcher 对 SimpleChannelInboundHandler造成的影响
SimpleChannelInboundHandler<RpcProtocol<RpcRequest>> 的第二层泛型就不会生效了,也就是说泛型擦除变成:SimpleChannelInboundHandler<RpcProtocol>。
因此,在使用时要注意不要使用 SimpleChannelInboundHandler 嵌套超过一层的泛型进行针对不同类型 Message 进行处理。
解决方法不支持泛型判断的方法
方法一:自定义实现 TypeParameterMatcher 的 matcher 方法, GitHub 上找了个 Java 泛型工具 https://github.com/jhalterman/typetools,看起来可行,虽然我暂时没试。
方法二:对 SimpleChannelInboundHandler<RpcProtocol<RpcRequest>> 和 SimpleChannelInboundHandler<RpcProtocol<RpcResponse>> 使用不同签名的实现,用到不同的位置。
比如,有以下签名:
class RpcRequestHandler extends SimpleChannelInboundHandler<RpcProtocol<RpcRequest>>
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcProtocol<RpcRequest> protocol) throws Exception
class RpcResponseHandler extends SimpleChannelInboundHandler<RpcProtocol<RpcResponse>> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcProtocol<RpcResponse> msg) {
不同泛型实现分别用RpcRequestHandler、RpcResponseHandler,最后判定出哪个是服务端哪个是客户端,分开使用。
- RpcProtocol<RpcRequest> 用在服务端,解析客户端请求:
bootstrap.group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) { socketChannel.pipeline() .addLast(new RpcEncoder()) // RPC 协议报文编码器 .addLast(new RpcDecoder()) // RPC 协议报文解码器 .addLast(new RpcRequestHandler(rpcServiceMap)); // RPC 请求处理器 } }) .childOption(ChannelOption.SO_KEEPALIVE, true);
- RpcProtocol<RpcResponse> 用在客户端,解析服务端响应:
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline() .addLast(new RpcEncoder()) .addLast(new RpcDecoder()) .addLast(new RpcResponseHandler()); } });
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/180265.html