前言
由于工作需要需要解析设备传输的协议,然后存入数据库,但是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,实现数据插入的操作