应用依赖不同的Netty版本引发的错误

2022-06-02 15:02:23 浏览数 (1)

昨晚服务在发布的时候, 出现如下异常

Caused by: java.lang.NoSuchMethodError: ...

Dubbo在暴露服务的时候, 需要启动Netty服务端, 在启动服务端的过程中, 根据Reactor模型, 它需要创建IO线程.会涉及到使用Netty中的

io.netty.util.concurrent.SingleThreadEventExecutor类, 根据错误提示, 在构造SingleThreadEventExecutor对象的时候, 找不到符合的构造器方法.

查看下应用依赖的Netty包

虽然有2个3.x版本的Netty包, 但是3.x版本的Netty包名都是 org.jboss.netty, 4.x版本的包名都是io.netty, 根据错误提示的包名, 因此排除3.x版本的嫌疑.

剩下的就是4.1.43版本和4.1.29版本, 版本不一致, 很可能就是因为这个原因造成的.

io.netty.util.concurrent.SingleThreadEventExecutor 这个类出现在两个包里.

netty-all-4.1.43.Final.jar 和 netty-common-4.1.29.Final.jar 包中都有SingleThreadEventExecutor 类.

写了一个简单的测试案例

代码语言:javascript复制
// Example.java

package com.infuq;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;

public class Example {

    public static void main(String[] args) throws Exception {
        // 加载SingleThreadEventExecutor类
        Class.forName("io.netty.util.concurrent.SingleThreadEventExecutor");

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);
        EventLoopGroup businessGroup = new NioEventLoopGroup(8);

        ServerBootstrap serverBootstrap = new ServerBootstrap();

        try {

            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) {
                            ChannelPipeline channelPipeline = ch.pipeline();

                            channelPipeline.addLast(new StringEncoder());
                            channelPipeline.addLast(new StringDecoder());
                            channelPipeline.addLast("idleEventHandler", new IdleStateHandler(0, 10, 0));
                            channelPipeline.addAfter("idleEventHandler","loggingHandler",new LoggingHandler(LogLevel.INF
O));
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind("127.0.0.1", 8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

以上代码会使用Netty创建一个服务端, 也是在模拟Dubbo使用Netty创建服务端, 本质是一样的. 只是在我的代码中, 使用

代码语言:javascript复制
Class.forName("io.netty.util.concurrent.SingleThreadEventExecutor");

手动提前加载SingleThreadEventExecutor类.

编译程序

代码语言:javascript复制
javac -d . -classpath ".:./netty-all-4.1.43.Final.jar:./netty-common-4.1.29.Final.jar" Example.java

在这里我们手动指定了jar包的加载顺序

运行程序

服务正常启动了...

接下来改变一下编译与运行时加载Jar包的顺序, 让类加载器在加载SingleThreadEventExecutor类的时候, 先从netty-common-4.1.29.Final.jar包中查找加载.

编译

代码语言:javascript复制
javac -d . -classpath ".:./netty-common-4.1.29.Final.jar:./netty-all-4.1.43.Final.jar" Example.java

运行

出现了与文章一开始一样的错误. 因为提前加载了netty-common-4.1.29.Final.jar版本中的SingleThreadEventExecutor类, 而接下来创建Netty服务端的时候, 在构造SingleThreadEventExecutor对象的时候, 传入的参数格式是按照netty-all-4.1.43.Final.jar包中的SingleThreadEventExecutor类传参. netty-common-4.1.29.Final.jar 和 netty-all-4.1.43.Final.jar 中关于SingleThreadEventExecutor类构造器的确不同, 如下

netty-all-4.1.43.Final.jar 包中的SingleThreadEventExecutor类构造器比netty-common-4.1.29.Final.jar包中的SingleThreadEventExecutor类构造器多一个, 而且就是错误中提示的`缺失`那个构造器.

使用mvn dependency:tree > tmp.txt命令导出来依赖关系, 查看了下, netty-common-4.1.29.Final.jar 和 netty-all-4.1.43.Final.jar 这两个包分别是被架构组A和团队B使用, 而作为使用方的我们, 需要手动解决版本不一样的问题, 否则就会出现许多莫名其妙错误.

在这之前应用没有出现过类似错误, 所以感觉很奇怪, 为什么最近突然出现了这样的错误, 原来是我们最近代码中接入了团队B的一个能力框架, 它的底层依赖了Netty, 只是版本与我们代码中依赖架构组A使用的Netty版本不一致引起的.

世界大同, 版本一致是原则.

问题似乎找到了, 但似乎又没有找到, 虽然知道是因为版本不同导致的, 然而是哪块代码提前类加载了netty-common-4.1.29.Final.jar包中的SingleThreadEventExecutor类呢, Spring类加载jar的顺序是怎样? 难道不是按照我们使用ls -l命令查看到的Jar顺序加载的吗? SpringBoot应用的classpath.idx文件作用是什么? 这些都是待解的疑惑.

解压springboot的jar包就会看到classpath.idx文件

下次见

0 人点赞