直接加 netty-all 会与 Spring Boot 冲突,因其异步 NIO 模型与 Spring Boot 的 Servlet 容器(如 Tomcat)架构不兼容,导致端口抢占、自动配置失效及启动卡死;正确做法是排除默认 Web 容器或按需引入核心模块并手动管理 Netty 生命周期。

为什么直接加 netty-all 会和 Spring Boot 冲突
Spring Boot 默认用 Tomcat 做 Web 容器,底层依赖的是 spring-webmvc + Servlet API;而 netty-all 是纯异步 NIO 框架,不走 Servlet 规范。强行引入后,Spring 的自动配置(比如 DispatcherServlet 初始化、端口绑定)会和 Netty 的 EventLoopGroup、ServerBootstrap 抢资源,常见报错是 Port already in use 或启动卡在 NettyWebServerFactory。
- 别在
spring-boot-starter-web项目里“混搭” Netty 服务端——这不是扩展,是架构冲突 - 真正需要 Netty 时,应排除默认 Web 容器:
spring.main.web-application-type=none
- 如果只是想用 Netty 做 TCP/UDP 长连接服务(比如 IM、设备通信),就别依赖
spring-boot-starter-web,只留spring-boot-starter
如何正确声明 netty-all 依赖(Maven)
用 netty-all 虽省事,但会把所有模块(包括 netty-tcnative、netty-resolver-dns)全拉进来,容易引发类冲突或冗余依赖。更稳妥的做法是按需引入核心模块:
- 必须的:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-transport</artifactId> <version>4.1.100.Final</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-handler</artifactId> <version>4.1.100.Final</version> </dependency>
- 如果要用 SSL:
netty-handler已包含基础支持,无需额外加netty-tcnative(除非要高性能 OpenSSL 绑定) - 避免写
<scope>provided</scope>——Netty 不是容器提供的,必须 runtime 可见
服务端启动类怎么写才不被 Spring Boot 干预
Spring Boot 启动类默认扫描并初始化所有 @Component、@Configuration,如果你把 ServerBootstrap 声明成 @Bean,它会在主线程阻塞等待连接,导致 Spring 上下文卡住。正确做法是:自己管理生命周期,用 @PostConstruct 启动,@PreDestroy 关闭。
- 启动逻辑不要放在构造函数或字段初始化中,否则可能早于 Spring 容器就绪
- 监听端口别硬编码,从
@Value("${netty.port:8081}")读取 - 务必调用
channelFuture.sync(),否则启动方法会立即返回,Netty 实际没起来 - 示例关键片段:
private ChannelFuture channelFuture; @PostConstruct public void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new EchoServerHandler()); } }); channelFuture = bootstrap.bind(port).sync(); channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
为什么 EchoServerHandler 不能直接注入 Spring Bean
Netty 的 ChannelHandler 是由 EventLoop 线程创建和复用的,而 Spring 的 Bean 默认是单例且线程不安全的。如果你在 ChannelHandler 构造器里 @Autowired 一个 service,很可能在多连接场景下出现状态污染或空指针。
- 解决办法只有两个:
— 把 handler 设为@Scope("prototype"),并在ChannelInitializer.initChannel()中用applicationContext.getBean(EchoServerHandler.class)获取新实例
— 更推荐:handler 里只做编解码和简单转发,业务逻辑通过ApplicationContext.publishEvent()或线程安全的队列交给 Spring 管理的 service 处理 - 别在
channelRead()里直接调用耗时 service 方法——这会堵住整个 EventLoop - 如果 handler 需要访问数据库或 HTTP 客户端,务必用
eventLoop.execute()切出 IO 线程,或委托给独立线程池
ChannelHandler 是属于 Netty 的,还是属于 Spring 的。










