前言
由於工作需要需要解析裝置傳輸的協議,然後存入資料庫,但是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,實現資料插入的操作