简介:本资源是一个基于Spring Boot集成RXTX库实现串口通信的完整Java项目,面向物联网、嵌入式系统及工业自动化领域的Java开发者,尤其适合需在Web后端与传感器、PLC、串口打印机等硬件设备交互的中高级学习者。项目提供开箱即用的串口配置、数据收发与事件监听能力,覆盖Windows/Linux/Mac多平台适配要点,有效解决Spring Boot生态中串行通信集成门槛高、文档零散的问题。压缩包共47个文件,含29个XML(以pom.xml为核心,管理RXTX依赖与构建配置)、6个Java源码(涵盖Controller、Service及串口工具类)、2个YML/properties配置文件(预置波特率、校验位等串口参数),以及README.md、.gitignore、mvnw等工程必需文件,整体8.96MB,结构规范,便于快速导入IDE运行调试。目前已有1415人学习下载,读者可直接获取可运行的Spring Boot串口通信骨架代码、跨平台RXTX环境配置方案、串口参数动态加载逻辑及典型异常处理范例,显著降低硬件通信模块的开发试错成本。
1. Spring Boot 项目集成 RXTX 串口通信:为什么 ZIP 包里总缺这一步?
你下载了一个名为spring-boot-rxtx.zip的资源包,解压后发现只有pom.xml、几个 Java 类和一个空lib/目录——没有预编译的rxtxSerial.dll(Windows)或librxtxSerial.so(Linux),也没有RXTXcomm.jar的完整依赖树。更困惑的是:用 IDEA 或 Eclipse 导入后,SerialPortEventListener报NoClassDefFoundError,运行java -jar app.jar时提示java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path。这不是环境配置遗漏,而是 Spring Boot + RXTX 组合天然存在的类路径隔离与本地库加载路径断裂问题。它不发生在普通 Java SE 项目里,却在 Spring Boot 的 Fat Jar 模式下高频触发。本文面向已能跑通 Spring Boot Web 应用、但首次接入串口设备(如 PLC、温湿度传感器、工业扫码枪)的开发者,聚焦「如何让 RXTX 在 Spring Boot 的打包、部署、运行全流程中真正可用」,不讲串口协议细节,只解决从pom.xml声明到java -jar成功打开 COM3 的全链路断点。
2. 为什么不能直接<dependency>引入 RXTX?选型与依赖声明的底层逻辑
2.1 RXTX 的特殊性:它不是纯 Java 库,而是 JNI 桥接层
RXTX 的核心能力(如openPort()、setSerialPortParams())必须调用操作系统原生串口驱动接口。这意味着:
RXTXcomm.jar仅包含 Java 接口类和 JNI 调用桩;- 实际功能由平台相关
.dll(Windows)、.so(Linux)或.dylib(macOS)提供; - JVM 启动时需通过
-Djava.library.path=...显式指定这些本地库所在目录; - Spring Boot 默认的 Fat Jar 打包机制(
spring-boot-maven-plugin)不会自动提取并加载 native 库,也不会修改java.library.path。
提示:网上常见错误是直接在
pom.xml中添加rxtx的 Maven 依赖(如org.rxtx:rxtx:2.1.7),这只能解决编译期import gnu.io.*的问题,但运行时仍会因找不到 native 库而崩溃。这是选型的第一道坎。
2.2 替代方案对比:为什么仍选 RXTX 而非 PureJavaComm 或 jSerialComm
| 方案 | 是否纯 Java | Windows 支持 | Linux 支持 | macOS 支持 | Spring Boot Fat Jar 兼容性 | 社区维护状态 |
|---|---|---|---|---|---|---|
| RXTX | ❌(需 native) | ✅(稳定) | ✅(需手动编译) | ⚠️(旧版有兼容问题) | ⚠️(需定制打包) | ❌(官方已停更,但工业现场存量大) |
| PureJavaComm | ✅ | ❌(无 WinAPI 支持) | ✅(依赖udev规则) | ✅ | ✅(无 native 依赖) | ❌(长期未更新) |
| jSerialComm | ✅ | ✅(JNI 封装,但 native 库内置) | ✅(同上) | ✅(同上) | ✅(Fat Jar 自动解压 native) | ✅(持续维护,GitHub Star > 1.2k) |
注意:标题明确为
spring-boot-rxtx.zip,说明项目已锁定 RXTX 技术栈(常见于 legacy 工业系统对接)。因此我们不替换技术选型,而是解决其与 Spring Boot 的集成痛点。若新项目,强烈建议优先评估jSerialComm(Maven 坐标:com.fazecast:jSerialComm:2.10.4)。
2.3 正确声明 RXTX 依赖:排除传递依赖 + 指定 classifier
RXTX 官方 Maven 仓库(https://mvnrepository.com/artifact/org.rxtx/rxtx)提供的 artifact 不含 native 库,且存在多个 classifier 变体。必须显式声明平台 classifier,并排除冲突的javax.comm:
<!-- pom.xml --> <dependency> <groupId>org.rxtx</groupId> <artifactId>rxtx</artifactId> <version>2.2</version> <!-- 关键:指定 Windows 平台 native 库 --> <classifier>windows-i386</classifier> <!-- 排除 javax.comm 冲突(Spring Boot 2.x+ 已弃用) --> <exclusions> <exclusion> <groupId>javax.comm</groupId> <artifactId>comm</artifactId> </exclusion> </exclusions> </dependency>逻辑说明:
<classifier>windows-i386</classifier>告诉 Maven 下载rxtx-2.2-windows-i386.jar,该 JAR 内含rxtxSerial.dll(位于win32/目录下)。其他平台对应 classifier:linux-x86、linux-x86_64、macosx。若需多平台支持,需在构建时动态选择 classifier,或采用 profile 分离。
3. 解决 Fat Jar 运行时 native 库缺失:三步法打包与启动
3.1 步骤一:将 native 库从依赖 JAR 中提取到项目资源目录
Maven 依赖中的rxtx-2.2-windows-i386.jar是一个“fat jar”,内部结构为:
rxtx-2.2-windows-i386.jar ├── gnu/io/... ├── win32/ │ └── rxtxSerial.dll ← 我们需要这个文件 └── META-INF/...使用 Maven Resources Plugin 在compile阶段自动解压并复制:
<!-- pom.xml --> <build> <plugins> <!-- 提取 native 库到 target/classes/native/ --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.6.1</version> <executions> <execution> <id>extract-rxtx-native</id> <phase>compile</phase> <goals> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>org.rxtx</groupId> <artifactId>rxtx</artifactId> <version>2.2</version> <classifier>windows-i386</classifier> <outputDirectory>${project.build.outputDirectory}/native</outputDirectory> <includes>win32/**</includes> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> </plugins> </build>参数说明:
<includes>win32/**</includes>确保只提取win32/目录下的 DLL;outputDirectory设为${project.build.outputDirectory}/native,即target/classes/native/,使 native 文件随 classpath 一起被 Spring Boot 加载。
3.2 步骤二:在 Spring Boot 启动类中动态加载 native 库
Spring Boot 的ClassLoader无法直接加载classpath:/native/win32/rxtxSerial.dll。必须将其复制到临时目录再加载:
// Application.java @SpringBootApplication public class Application { public static void main(String[] args) { // 在 SpringApplication.run() 前执行 loadRxtxNative(); SpringApplication.run(Application.class, args); } private static void loadRxtxNative() { try { // 1. 从 classpath 获取 native DLL 资源 InputStream is = Application.class.getClassLoader() .getResourceAsStream("native/win32/rxtxSerial.dll"); if (is == null) { throw new RuntimeException("rxtxSerial.dll not found in classpath"); } // 2. 复制到系统临时目录 Path tempDll = Files.createTempFile("rxtx-", ".dll"); Files.copy(is, tempDll, StandardCopyOption.REPLACE_EXISTING); tempDll.toFile().deleteOnExit(); // JVM 退出时自动清理 // 3. 设置 java.library.path 并加载 System.setProperty("java.library.path", tempDll.getParent().toString()); Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); fieldSysPath.setAccessible(true); fieldSysPath.set(null, null); // 强制刷新系统库路径缓存 System.load(tempDll.toString()); System.out.println("✅ Loaded RXTX native library: " + tempDll); } catch (Exception e) { throw new RuntimeException("Failed to load RXTX native library", e); } } }逻辑说明:
System.load()要求传入绝对路径;System.setProperty("java.library.path")单独设置无效(JVM 启动后不可变),必须配合反射清空ClassLoader.sys_paths缓存,否则System.loadLibrary("rxtxSerial")仍会失败。
3.3 步骤三:构建可运行的 Fat Jar 并验证 native 路径
执行mvn clean package后,检查生成的target/*.jar是否包含 native 文件:
# 解压查看结构 unzip -l target/myapp-0.0.1-SNAPSHOT.jar | grep "native/" # 输出应包含: # 123456 00-00-1980 00:00 BOOT-INF/classes/native/win32/rxtxSerial.dll运行时需确保-Djava.library.path指向正确位置(虽然代码中已动态加载,但部分 JVM 版本仍需显式声明):
# 推荐:直接运行(依赖代码中 load 逻辑) java -jar target/myapp-0.0.1-SNAPSHOT.jar # 备用:显式指定 library path(指向解压后的临时目录) java -Djava.library.path="/tmp" -jar target/myapp-0.0.1-SNAPSHOT.jar提示:若报错
Can't load IA 32-bit .dll on a AMD 64-bit platform,说明 JDK 是 64 位,但下载了windows-i386classifier。此时需改用windows-x86_64classifier,并确认rxtx-2.2-windows-x86_64.jar中的 DLL 是 64 位版本。
4. 生产环境部署避坑指南:跨平台、权限与服务化
4.1 Linux 系统下必须配置 udev 规则,否则 Permission Denied
即使 native 库加载成功,new SerialPort("/dev/ttyUSB0")仍可能抛gnu.io.PortInUseException。根本原因是 Linux 用户无权访问串口设备文件:
# 查看当前用户是否在 dialout 组 groups # 若无 dialout,加入: sudo usermod -a -G dialout $USER # 重启终端生效 # 创建 udev 规则(避免每次插拔设备改变 /dev/ttyUSB* 编号) echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="arduino"', \ 'SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ch340"' \ | sudo tee /etc/udev/rules.d/99-serial.rules sudo udevadm control --reload-rules sudo udevadm trigger参数说明:
idVendor和idProduct可通过lsusb查看 USB 转串口芯片型号(FTDI:0403:6001,CH340:1a86:7523)。SYMLINK+="arduino"创建固定软链接/dev/arduino,代码中直接使用该路径,避免硬编码/dev/ttyUSB0。
4.2 Windows 服务化部署:bat 脚本需处理 DLL 路径与 JVM 参数
将 Spring Boot 应用注册为 Windows 服务时,bat 脚本必须显式设置java.library.path:
@echo off set JAVA_HOME=C:\Program Files\Java\jdk-11.0.12 set APP_JAR=target\myapp-0.0.1-SNAPSHOT.jar set NATIVE_PATH=%~dp0native\win32 "%JAVA_HOME%\bin\java.exe" ^ -Djava.library.path="%NATIVE_PATH%" ^ -Xms256m -Xmx512m ^ -jar "%APP_JAR%" ^ --spring.profiles.active=prod ^ > app.log 2>&1 pause注意:
%~dp0表示 bat 文件所在目录,native\win32必须与项目中src/main/resources/native/win32/结构一致。若使用 NSSM 封装为服务,需在nssm install MyApp的 GUI 中,在 “Details” 标签页填写Startup directory为 bat 所在目录。
4.3 Docker 容器内串口访问:--device 与特权模式的取舍
在容器中访问宿主机串口,禁止使用--privileged(安全风险过高),应精确挂载设备:
# Dockerfile FROM openjdk:17-jre-slim COPY target/myapp-0.0.1-SNAPSHOT.jar app.jar # 复制 native 库(Linux x64) COPY src/main/resources/native/linux-x86_64/ /app/native/ ENTRYPOINT ["java", "-Djava.library.path=/app/native", "-jar", "/app.jar"]启动命令:
# 仅挂载指定串口设备(推荐) docker run -d \ --device=/dev/ttyUSB0:/dev/ttyUSB0:rwm \ -v /dev:/dev:ro \ myapp-image # 或挂载整个 serial 设备组(需确认宿主机 /dev/serial/ 存在) docker run -d \ --device=/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AH02QKZL-if00-port0:/dev/ttyUSB0:rwm \ myapp-image提示:
/dev/serial/by-id/...是 USB 设备的稳定路径,比/dev/ttyUSB0更可靠。容器内应用代码仍使用/dev/ttyUSB0,但实际映射到宿主机的物理端口。
5. 验证 RXTX 是否真正就绪:一个可复用的端口探测工具类
5.1 编写SerialPortDetector:列出所有可用端口并测试读写
避免在业务逻辑中直接new SerialPort(),先用探测工具确认环境:
@Component public class SerialPortDetector { public List<String> listAvailablePorts() { Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers(); List<String> ports = new ArrayList<>(); while (portEnum.hasMoreElements()) { CommPortIdentifier portId = portEnum.nextElement(); if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { ports.add(portId.getName()); } } return ports; } public boolean testPort(String portName) { try (SerialPort port = (SerialPort) CommPortIdentifier.getPortIdentifier(portName) .open("SerialPortDetector", 2000)) { port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); // 发送 AT 命令测试(适用于多数串口设备) OutputStream out = port.getOutputStream(); out.write("AT\r\n".getBytes(StandardCharsets.US_ASCII)); out.flush(); Thread.sleep(500); return true; } catch (Exception e) { System.err.println("❌ Test failed on " + portName + ": " + e.getMessage()); return false; } } }5.2 在 Spring Boot Actuator 端点暴露串口健康状态
创建自定义 HealthIndicator,集成到/actuator/health:
@Component public class SerialPortHealthIndicator implements HealthIndicator { private final SerialPortDetector detector; public SerialPortHealthIndicator(SerialPortDetector detector) { this.detector = detector; } @Override public Health health() { List<String> available = detector.listAvailablePorts(); if (available.isEmpty()) { return Health.down() .withDetail("reason", "No serial ports found") .build(); } String firstPort = available.get(0); boolean ok = detector.testPort(firstPort); Health.Builder builder = ok ? Health.up() : Health.down(); return builder .withDetail("availablePorts", available) .withDetail("testedPort", firstPort) .withDetail("testResult", ok) .build(); } }启动应用后访问http://localhost:8080/actuator/health,返回:
{ "status": "UP", "components": { "serialPort": { "status": "UP", "details": { "availablePorts": ["COM3", "COM4"], "testedPort": "COM3", "testResult": true } } } }提示:此端点可被 Prometheus 抓取,结合 Grafana 做串口设备在线率监控。若
testResult为 false,检查COM3是否被其他程序占用(如串口调试助手),或硬件连接是否松动。
本文还有配套的精品资源,点击获取