昨晚服务在发布的时候, 出现如下异常
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文件
下次见