Netty SimpleChannelInboundHandler 自动判断是否需要调用 Handler 处理 Message 的原理

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。Netty SimpleChannelInboundHandler 自动判断是否需要调用 Handler 处理 Message 的原理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

问题

不同的 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

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!