常規的demo級別的netty服務端的代碼寫法是這樣的:
try { //創建並初始化 Netty 服務端輔助啟動對象 ServerBootstrap ServerBootstrap serverBootstrap = RpcServer.this.initServerBootstrap(bossGroup, workerGroup); //綁定對應ip和端口,同步等待成功 ChannelFuture future = serverBootstrap.bind(port).sync(); LOGGER.info("rpc server 已啟動,端口:{}", port); //等待服務端監聽端口關閉 future.channel().closeFuture().sync(); } catch (InterruptedException i) { LOGGER.error("rpc server 出現異常,端口:{}, cause:", port, i.getMessage()); } finally { //優雅退出,釋放 NIO 線程組 workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); }
在這里面future.channel().closeFuture().sync();這個語句的主要目的是,方便測試,方便寫一個非springboot的demo,比如一個簡單地junit test方法,closeFuture().sync()可以阻止junit test將server關閉,同時停止test應用的時候也不需要手動再調用關閉服務器的方法workerGroup.shutdownGracefully()...。這樣設計在測試時省心。
但是,當將nettyserver聯系到springboot應用的啟動時,例如nettyserver設置為@Component,當springboot掃描到nettyserver時,springboot主線程執行到nettyserver的postconstruct注解的方法,然后發生了
future.channel().closeFuture().sync();
這樣導致springboot主線程阻塞,無法繼續加載剩下的bean,
更糟糕的是,如果springboot還添加了springboot-web的依賴(自帶tomcat容器),那么被阻塞后將無法啟動tomcat servlet engine和webapplicationcontext.
所以不能簡單地在nettyserver中的構造方法/init方法中寫future.channel().closeFuture().sync();和workerGroup.shutdownGracefully().
只需在構造方法/init方法中bootstrap.bind(port),這是異步的,不會阻塞springboot主線程。
而將stop方法單獨抽取出來。
需要注意的是,即使直接關閉springboot應用,不手動調用上面的stop方法,nettyserver也會將之前綁定的端口解除,為了保險起見,可以將stop方法添加@predestroy注解