16.springboot整合netty解决方案

博文背景

netty如果整合到springboot中,让spring的IOC容器可以监管到handler,handler可以使用IOC容器中的bean,开发就会很惬意了。

参考资料

https://blog.csdn.net/yuanzhenwei521/article/details/79194275 方案1
https://juejin.cn/post/6844904197129764871 方案2
https://www.cnblogs.com/chenpi/p/9696310.html CommandLineRunner解析
https://www.jianshu.com/p/5d4ffe267596 CommandLineRunner与ApplicationRunner接口
https://www.jianshu.com/p/0269a8390d5f ApplicationListener 接口相关
https://www.cnblogs.com/loong-hon/p/10917755.html Spring中ApplicationContextAware的作用

解决方案1:CommandLineRunner

利用spring的CommandLineRunner接口,来实现具体的策略。
首先,将你的server和handler都加上@compont注解,并且在server中正常使用handler进行注入,server中写的是ServerBootstrap那套启动代码,handler是继承ChannelInboundHandlerAdapter的子类,代码如下:

@Slf4j
@Component
public class ProtoServer {
    @Autowired
    private ProtoHandler protoHandler;

    /**
     * 默认监听29999端口
     */
    public void listen() {
        this.listenWithPort(29999);
    }
@Slf4j
@Component
public class ProtoHandler extends ChannelInboundHandlerAdapter {

    @Autowired
    private ISessionCache concurrentMapSessionCache;

然后,找一个服务初始化类(根据自己项目结构自己写一个),实现CommandLineRunner接口,将监听的代码放到run方法中。

@Component
@Slf4j
public class ServerStarter implements CommandLineRunner {

    @Autowired
    private ProtoServer protoServer;

    @Value("${proto.server.port}")
    private int protoServerPort;

    @Override
    public void run(String... args) throws Exception {
        try {
            log.info("启动protoserver,监听:"+ protoServerPort);
            protoServer.listenWithPort(protoServerPort);
        } catch (Exception e) {
            log.error("protoServer启动失败...,失败原因:"+e.getMessage());
        }
    }

}

注意:run接口里的代码要加try-catch捕获异常,否则CommandLineRunner接口实现类启动失败,整个系统也无法启动。

解决方案2:ApplicationRunner, ApplicationListener, ApplicationContextAware

解决方案2中用到了几个接口,ApplicationRunner跟CommandLineRunner类似,只是run方法的参数变了,其他没变化。
ApplicationListener 是监听容器关闭的事件,配合netty可以优雅的释放netty的资源。
ApplicationContextAware 通过它Spring容器会自动把上下文环境对象调用ApplicationContextAware接口中的setApplicationContext方法。我们在ApplicationContextAware的实现类中,就可以通过这个上下文环境对象得到Spring容器中的Bean。


/**
 * 初始化Netty服务
 * @author Administrator
 */
@Component
public class NettyBootsrapRunner implements ApplicationRunner, ApplicationListener<ContextClosedEvent>, ApplicationContextAware {

	private static final Logger LOGGER = LoggerFactory.getLogger(NettyBootsrapRunner.class);
	
	@Value("${netty.websocket.port}")
	private int port;

	@Value("${netty.websocket.ip}")
	private String ip;
	
	@Value("${netty.websocket.path}")
	private String path;
	
	@Value("${netty.websocket.max-frame-size}")
	private long maxFrameSize;
	
	private ApplicationContext applicationContext;
	
	private Channel serverChannel;
	
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
	
	public void run(ApplicationArguments args) throws Exception {
		
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(bossGroup, workerGroup);
			serverBootstrap.channel(NioServerSocketChannel.class);
			serverBootstrap.localAddress(new InetSocketAddress(this.ip, this.port));
			serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel socketChannel) throws Exception {
					ChannelPipeline pipeline = socketChannel.pipeline();
					pipeline.addLast(new HttpServerCodec());
					pipeline.addLast(new ChunkedWriteHandler());
					pipeline.addLast(new HttpObjectAggregator(65536));
					pipeline.addLast(new ChannelInboundHandlerAdapter() {
						@Override
						public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
							if(msg instanceof FullHttpRequest) {
								FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
								String uri = fullHttpRequest.uri();
								if (!uri.equals(path)) {
									// 访问的路径不是 websocket的端点地址,响应404
									ctx.channel().writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND))
										.addListener(ChannelFutureListener.CLOSE);
									return ;
								}
							}
							super.channelRead(ctx, msg);
						}
					});
					pipeline.addLast(new WebSocketServerCompressionHandler());
					pipeline.addLast(new WebSocketServerProtocolHandler(path, null, true, maxFrameSize));

					/**
					 * 从IOC中获取到Handler
					 */
					pipeline.addLast(applicationContext.getBean(WebsocketMessageHandler.class));
				}
			});
			Channel channel = serverBootstrap.bind().sync().channel();	
			this.serverChannel = channel;
			LOGGER.info("websocket 服务启动,ip={},port={}", this.ip, this.port);
			channel.closeFuture().sync();
		} finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}

	public void onApplicationEvent(ContextClosedEvent event) {
		if (this.serverChannel != null) {
			this.serverChannel.close();
		}
		LOGGER.info("websocket 服务停止");
	}
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值