前言
由于工作需要需要解析设备传输的协议,然后存入数据库,但是Netty整合mybatisplus遇到了不少问题,网上的博客都或多或少有点问题,于是记录下来这次整合
Netty和spring的关系
Netty 是一个独立的网络编程框架,它不依赖于 Spring 框架,因此 Netty 的组件不自动注册到 Spring 的 IOC 容器中。
Netty 的 ChannelHandler
实例是由 Netty 的 pipeline 创建和管理的
由于ioc无法管理,导致我前期踩坑不少
整合
maven
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- netty --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.68.Final</version> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.17</version> </dependency> <!--kafka依赖--> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.6</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>
netty启动类修改
我采用的是springboot 配置bean的方式启动netty
netty启动方式
1. 实现ApplicationRunner
@Slf4j @Component @Order(1) public class EPServer implements ApplicationRunner { /** * Netty服务端监听的端口号 */ public static final int PORT = 9999; // 创建两个EventLoopGroup,boss:处理连接事件,worker处理I/O事件 /** * 分发线程组 处理连接事件 */ private static final EventLoopGroup bossGroup = new NioEventLoopGroup(2); /** * 工作线程组 处理I/O事件 */ private static final EventLoopGroup workerGroup = new NioEventLoopGroup(4); static MessageCode MESSAGE_CODEC = new MessageCode(); /** * 启动服务 */ public static void runEPServer(){ log.info("===== EP Netty Server start ====="); try{ // 创建一个ServerBootstrap服务端(同之前的ServerSocket类似) ServerBootstrap b = new ServerBootstrap(); //创建事件循环组 将前面创建的两个EventLoopGroup绑定在server上 b.group(bossGroup, workerGroup); // 指定服务端的通道为Nio类型 b.channel(NioServerSocketChannel.class); // 为到来的客户端Socket添加处理器 b.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); // netty提供了空闲状态监测处理器 0表示禁用事件 pipeline.addLast(new MessageCode()); pipeline.addLast(new EPServerHandler()); } }); log.info("环保 Netty Server PORT = " + PORT); b.bind(PORT).sync(); }catch (Exception e){ e.printStackTrace(); shutdown(); } } /** * 关闭服务 */ public static void shutdown(){ // 优雅关闭 workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } @Override public void run(ApplicationArguments args) { // 启动环保监测Netty服务端 runEPServer(); } }
2. springboot 配置bean的方式启动netty
在启动方法上添加@PostConstruct
注解
@Slf4j @Component public class EPServerNew { /** * Netty服务端监听的端口号 */ public static final int PORT = 9999; // 创建两个EventLoopGroup,boss:处理连接事件,worker处理I/O事件 /** * 分发线程组 处理连接事件 */ private static final EventLoopGroup bossGroup = new NioEventLoopGroup(2); /** * 工作线程组 处理I/O事件 */ private static final EventLoopGroup workerGroup = new NioEventLoopGroup(4); static MessageCode MESSAGE_CODEC = new MessageCode(); @Autowired private EPServerHandler epServerHandler; /** * 启动服务 */ @PostConstruct public void start() throws InterruptedException { ServerBootstrap b=new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG,128) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new MessageCode()); socketChannel.pipeline().addLast(epServerHandler); } }); ChannelFuture future = b.bind(PORT).sync(); if (future.isSuccess()) { System.out.println("启动 Netty 成功"); } } /** * 关闭服务 */ @PreDestroy public static void shutdown(){ // 优雅关闭 workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } }
两种启动方式对比
使用 CommandLineRunner
接口:
优点:
易于实现:只需创建一个实现
CommandLineRunner
的类,并overriderun
方法以启动 Netty 服务。灵活性:您可以在
run
方法中执行任何必要的设置或清理任务。缺点:
控制有限:
CommandLineRunner
接口旨在运行命令行应用程序,因此您对 Netty 服务的生命周期控制有限。无法管理 bean:Netty 服务不是作为 Spring bean 管理的,因此您无法 inject 依赖项或使用 Spring 的生命周期管理功能。
配置 bean 来启动 Netty 服务:
优点:
更好的控制:通过配置 bean 来启动 Netty 服务,您对服务的生命周期有更多的控制权,并可以使用 Spring 的生命周期管理功能。
依赖注入:您可以将依赖项 inject 到 Netty 服务 bean 中,使得测试和维护变得更加容易。
缺点:
更复杂:您需要创建一个配置类,并定义一个 bean 来启动 Netty 服务,这可能比实现
CommandLineRunner
更复杂。紧耦合:Netty 服务 bean 紧耦合到 Spring 应用程序上下文中,这可能使得测试或在非 Spring Boot 应用程序中使用变得更加困难。
application 配置文件
spring: datasource: url: jdbc:mysql://127.0.0.1:33306/sums_hfcyjs?characterEncoding=utf-8&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver #开启日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
启动类
@SpringBootApplication @EnableScheduling @MapperScan("com.dhj.hj212.mapper") public class Hj212Application { public static void main(String[] args) { SpringApplication.run(Hj212Application.class, args); } }
具体包名根据自己的实际情况修改
EPServerHandler类
这个类加了@Component
和@Sharable
注解
在这里我们就可以注入mybatisplus的mapper,然后执行insert操作
@Sharable
注解解释
在 Netty 中,ChannelHandler
是一个对象,它处理通道的入站和出站事件。默认情况下,每个 ChannelHandler
实例都是为每个通道单独创建的,这意味着每个通道都有其自己的处理器实例。
但是,在某些情况下,您可能想跨多个通道共享同一个 ChannelHandler
实例。这就是 @ChannelHandler.Sharable
注解的用途。
当您在 ChannelHandler
类上使用 @ChannelHandler.Sharable
注解时,它表明该处理器实例可以安全地跨多个通道共享。这意味着 Netty 将重用同一个处理器实例 для多个通道,而不是为每个通道创建一个新的实例。
使用 @ChannelHandler.Sharable
的一些含义是:
线程安全:由于处理器实例是共享的,因此它必须是线程安全的。这意味着处理器的状态必须被仔细地管理,以确保它可以被多个线程安全地访问和修改。
通道独立:由于处理器实例是共享的,因此它不能维护任何通道特定的状态。这意味着处理器不能存储任何通道特定的信息,例如通道的 ID 或地址。
性能:共享同一个处理器实例跨多个通道可以提高性能,因为它减少了对象创建和垃圾回收的数量。
@Slf4j @Component @ChannelHandler.Sharable public class EPServerHandler extends ChannelInboundHandlerAdapter { /** * 定义一个HashMap,用于保存所有的channel和设备ID的对应关系。 */ private static Map deviceInfo = new HashMap(64); @Autowired private SepticHandheldMapper septicHandheldMapper; /** * 消息读取 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException { log.info("=============" + (new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date()) + "============="); if (msg instanceof ByteBuf) { ByteBuf byteBuf = (ByteBuf) msg; log.info("最后总收到数据: {}", ByteBufUtil.hexDump(byteBuf)); log.info("开始解析数据"); ........ // 这里进行插入操作 int insert = septicHandheldMapper.insert(septicHandheld);
mybatisplus
整合mybatisplus可以参考官方文档这个比较简单
总结
问题就是把netty交给spring去管理,然后我们就可以在里面注入mapper,实现数据插入的操作