尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于Eclipse Milo封装Spring Boot Starter实现OPC UA工业数据采集

基于Eclipse Milo封装Spring Boot Starter实现OPC UA工业数据采集 1. 项目概述与核心价值最近在做一个工业数据采集的项目甲方现场的设备五花八门协议也是七国八制。其中几台新上的高端机床和质检设备厂商明确说了只支持OPC UA协议进行数据交互。这OPC UA在工业物联网圈子里现在可是个“明星协议”它不依赖特定的Windows平台老OPC DA得靠COM/DCOM在跨平台和防火墙穿透上就是个噩梦自带安全模型还能描述复杂的数据结构确实是面向未来的工业通信标准。但要在Java生态里快速、稳定地接入它并且还得方便项目组里其他兄弟复用这事儿就得好好琢磨一下了。直接上官网的SDK功能是强大但配置起来颇为繁琐每个服务端地址、安全策略、证书都得手动处理分散在业务代码里显得很乱。于是我的思路很明确基于一个成熟的开源Java OPC UA实现——Eclipse Milo把它封装成一个Spring Boot Starter。这样一来任何需要接入OPC UA的服务只需要引入这个starter在配置文件里写几行再通过简单的API就能完成连接、订阅、读写数据等一系列操作把复杂的通信细节完全屏蔽掉。这不仅仅是写个工具类而是打造一个即插即用、生产可用的基础设施组件。今天我就把这个从选型、封装到踩坑填坑的全过程以及最终成型的starter设计思路和核心代码给大家掰开揉碎了讲清楚。2. 技术选型为什么是Eclipse Milo在Java世界里实现OPC UA客户端的库有几个选择比如官方的org.opcfoundation、prosys的商业库以及开源的Eclipse Milo。我最终锁定Milo是基于下面几个实实在在的考量这些都是在项目实战中总结出来的硬指标。2.1 生态与活跃度Milo是Eclipse基金会旗下的项目这本身就是一块“金字招牌”意味着规范的治理和长期维护的承诺。你去GitHub上看它的提交记录非常活跃Issues的响应和修复速度也很快。社区里有不少来自自动化大厂的开发者参与这保证了它在应对真实工业场景复杂性问题时有足够的经验支撑。相比之下一些其他开源方案可能已经停滞更新而官方SDK虽然标准但有时在易用性和“Java味”上差了点意思。2.2 纯Java实现与依赖管理Milo是一个纯粹的Java实现不依赖任何本地库Native Library。这一点至关重要。回想被某个数据库驱动本地依赖支配的恐惧部署时总得操心服务器上的GLIBC版本。Milo的纯Java特性让它能真正做到“一次打包到处运行”无论是部署在Linux服务器、Windows工控机还是Docker容器里都无需额外的系统级配置大大降低了运维复杂度。它的依赖通过Maven管理得非常清晰没有令人头疼的冲突。2.3 功能完备性与设计友好Milo完整实现了OPC UA客户端和服务端的协议栈我们这里只用到客户端部分。它支持包括无安全、签名、签名且加密在内的多种安全策略支持证书管理、会话自动重建等生产级功能。更重要的是它的API设计层次分明OpcUaClient作为核心DataChangeListener处理数据变化UaSubscription管理订阅学起来曲线平缓。而且它与Netty集成作为底层网络通信框架性能和高并发处理能力有保障。2.4 Spring Boot生态的契合度这是最关键的一点。Milo本身没有提供与Spring Boot的自动配置集成。而这正是我们封装starter的价值所在。我们可以利用Spring Boot的ConfigurationProperties来绑定外部配置用ConditionalOnClass等条件注解来智能装配Bean用ApplicationListener来优雅地管理客户端生命周期随Spring应用启动而连接随应用关闭而断开。将Milo的强大功能“Spring化”是我们这个项目的核心目标。注意虽然Milo很优秀但在极端严苛的实时性场景例如要求毫秒级确定性的运动控制任何基于通用TCP/IP和Java虚拟机的方案都可能面临挑战。这时可能需要考虑专门的实时以太网协议或工业总线。但对于绝大多数数据采集秒级、百毫秒级、监控和MES系统集成场景MiloSpring Boot的方案是完全胜任且高效的。3. Starter整体设计与核心思路拆解封装一个Spring Boot Starter绝不是简单地把Milo的OpcUaClient实例丢到Spring容器里就完事了。我们需要考虑配置的便捷性、连接的生命周期、异常处理的统一性以及扩展的灵活性。下面这张图概括了核心的设计思路[应用配置文件] - [OpcUaProperties配置类] - [OpcUaClientConfig配置类] - [OpcUaClient实例] | | | | 读取配置验证参数 组装Endpoint安全策略证书 | | | V V V [业务Service] --- [OpcUaTemplate模板类] --- [OpcUaClient实例] | (提供读写订阅等API) | V [数据监听器/处理器]3.1 配置驱动与自动装配我们希望用户只需在application.yml里写下如下配置就能完成一个OPC UA客户端的基本定义opcua: clients: plc1: endpoint-url: opc.tcp://192.168.1.100:4840 security-policy: Basic256Sha256 identity: anonymous # 或 username username: admin password: ${OPCUA_PLC1_PASSWORD:} # 密码建议从环境变量读取 subscription-publish-interval: 500.0 reconnect-delay: 5s furnace: endpoint-url: opc.tcp://192.168.1.101:4840 security-policy: None identity: anonymous这通过定义一个ConfigurationProperties(prefix “opcua”)的OpcUaProperties类来实现内部用一个Map来存储多个客户端的配置key就是plc1、furnace这样的客户端标识符。3.2 模板模式封装复杂操作直接使用Milo的原生API进行读写代码会夹杂很多CompletableFuture、NodeId的处理不够直观。因此我们引入一个OpcUaTemplate类这是整个starter的门面Facade。它内部持有一个OpcUaClient实例并提供诸如readValue(String nodeId)、writeValue(String nodeId, Object value)、subscribe(String nodeId, DataChangeListener listener)等简化方法。这样业务代码只需注入OpcUaTemplate调用其方法即可无需关心底层连接和协议细节。3.3 连接生命周期管理工业现场网络不稳定是常态客户端必须能处理断线重连。我们不能让Spring容器直接管理一个可能断开的OpcUaClient。我们的策略是延迟连接在OpcUaTemplate被首次调用时才尝试建立连接。或者通过实现SmartLifecycle接口在Spring容器启动完成后自动连接。监听断线注册SessionActivityListener来监听连接状态。一旦检测到连接异常断开启动一个后台任务按照配置的reconnect-delay进行周期性重试直到连接恢复。优雅关闭在Spring应用关闭时通过PreDestroy或DisposableBean接口确保OpcUaClient的disconnect()方法被调用释放所有订阅和会话资源。3.4 异常的统一转换Milo抛出的异常可能是UaException里面包含复杂的StatusCode。我们应该定义一个业务友好的OpcUaClientException在模板类内部捕获Milo的底层异常进行转换和日志记录向上层业务代码屏蔽协议细节只暴露“连接失败”、“节点不存在”、“写入拒绝”等业务语义明确的异常。4. 核心模块实现与代码解析接下来我们深入到代码层面看看这几个核心模块是如何实现的。我会贴出关键代码并解释其设计意图。4.1 配置属性绑定OpcUaPropertiesConfigurationProperties(prefix opcua) Data Validated public class OpcUaProperties { /** * 多个OPC UA客户端配置Key为客户端名称如plc1, furnace */ NestedConfigurationProperty private MapString, ClientConfig clients new HashMap(); Data public static class ClientConfig { /** * 服务器端点URL例如opc.tcp://localhost:4840 */ NotBlank private String endpointUrl; /** * 安全策略None, Basic128Rsa15, Basic256, Basic256Sha256 等 */ private String securityPolicy None; /** * 身份认证方式anonymous, username */ private String identity anonymous; private String username; private String password; /** * 订阅发布间隔毫秒 */ private Double subscriptionPublishInterval 1000.0; /** * 断线重连延迟例如5s, 1m */ private Duration reconnectDelay Duration.ofSeconds(10); // ... 其他配置如超时时间、证书路径等 } }这里使用了Spring Boot的ConfigurationPropertiesData是Lombok注解用于生成getter/setter。Validated配合NotBlank可以在配置注入时就进行校验提前发现问题。MapString, ClientConfig的设计支持多客户端配置非常灵活。4.2 客户端自动配置OpcUaClientConfig这是整个Starter的“大脑”负责根据配置创建和配置OpcUaClient实例。Configuration EnableConfigurationProperties(OpcUaProperties.class) ConditionalOnClass(OpcUaClient.class) AutoConfigureAfter(ClientAutoConfiguration.class) // 确保在其他必要Bean之后配置 public class OpcUaClientConfig { Autowired private OpcUaProperties properties; /** * 为配置的每一个客户端创建一个 OpcUaClient Bean。 * Bean名称为 client名称 “OpcUaClient”例如plc1OpcUaClient。 */ Bean ConditionalOnMissingBean public MapString, OpcUaClient opcUaClients() throws Exception { MapString, OpcUaClient clientMap new ConcurrentHashMap(); for (Map.EntryString, OpcUaClientProperties.ClientConfig entry : properties.getClients().entrySet()) { String clientName entry.getKey(); ClientConfig config entry.getValue(); OpcUaClient client createOpcUaClient(clientName, config); clientMap.put(clientName, client); } return clientMap; } private OpcUaClient createOpcUaClient(String clientName, ClientConfig config) throws Exception { // 1. 解析Endpoint URL EndpointDescription endpoint selectEndpoint(config); // 2. 构建客户端配置 OpcUaClientConfigBuilder builder OpcUaClientConfig.builder() .setEndpoint(endpoint) .setIdentityProvider(getIdentityProvider(config)) // 处理匿名/用户名密码 .setRequestTimeout(uint(5000)) // 请求超时 .setKeepAliveInterval(Duration.ofSeconds(5)) // 保活间隔 .setKeepAliveFailureLimit(5); // 保活失败次数上限 // 3. 配置安全策略和证书略根据securityPolicy配置 // 4. 配置重连策略核心 builder.setSessionActivityListener(new SessionActivityListener() { Override public void onSessionInactive(UaSession session) { log.warn(“OPC UA Client [{}] session inactive, scheduling reconnect...”, clientName); scheduleReconnect(clientName, config); } }); // 5. 创建并连接客户端这里可以先创建在Lifecycle中连接 return OpcUaClient.create(builder.build()); } private void scheduleReconnect(String clientName, ClientConfig config) { // 使用一个调度线程池延迟 config.getReconnectDelay() 后执行重连逻辑 // 重连逻辑需要保证幂等性避免重复连接 } }这个配置类做了几件关键事一是批量创建客户端Bean二是在客户端配置中植入了SessionActivityListener用于监听会话失效三是将重连逻辑封装起来。注意这里OpcUaClient.create()创建了客户端实例但并未立即连接真正的连接触发可以放在下一步的OpcUaTemplate初始化或一个独立的LifecycleBean中。4.3 门面模板类OpcUaTemplate这是给业务开发者使用的核心类。Component public class OpcUaTemplate implements SmartLifecycle, ApplicationContextAware { private final String clientName; private final OpcUaClient client; private volatile boolean isRunning false; private ApplicationContext applicationContext; // 通过构造器注入指定名称的客户端 public OpcUaTemplate(Value(“${opcua.default-client:default}”) String defaultClientName, MapString, OpcUaClient opcUaClients) { this.clientName defaultClientName; this.client opcUaClients.get(defaultClientName); if (this.client null) { throw new IllegalStateException(“No OPC UA client configured with name: ” defaultClientName); } } Override public void start() { if (!isRunning) { try { this.client.connect().get(); // 异步连接阻塞直到完成 this.isRunning true; log.info(“OPC UA Client [{}] connected successfully.”, clientName); } catch (Exception e) { log.error(“Failed to connect OPC UA Client [{}]”, clientName, e); throw new OpcUaClientException(“Connection failed”, e); } } } public DataValue read(String nodeId) { checkState(); try { NodeId nid NodeId.parse(nodeId); ReadResponse response client.read(0.0, TimestampsToReturn.Both, Arrays.asList(new ReadValueId(nid, AttributeId.Value.uid(), null, null))).get(); DataValue dataValue response.getResults()[0]; if (dataValue.getStatusCode().isGood()) { return dataValue; } else { throw new OpcUaClientException(“Read failed with status: ” dataValue.getStatusCode()); } } catch (Exception e) { throw new OpcUaClientException(“Error reading node: ” nodeId, e); } } public void write(String nodeId, Object value) { checkState(); // ... 类似的构建WriteRequest并发送 } public UaSubscription createSubscription() { checkState(); // ... 创建订阅 } private void checkState() { if (!isRunning) { throw new IllegalStateException(“OPC UA Client [” clientName “] is not connected.”); } } Override public void stop() { if (isRunning) { try { client.disconnect().get(); isRunning false; log.info(“OPC UA Client [{}] disconnected.”, clientName); } catch (Exception e) { log.error(“Error disconnecting OPC UA Client [{}]”, clientName, e); } } } // ... 其他方法如批量读写、浏览节点等 }OpcUaTemplate实现了SmartLifecycle这样它就能随着Spring容器的启动和停止而自动连接/断开连接。read和write方法封装了异步操作转同步、异常转换等样板代码让业务调用变得极其简单。4.4 自动装配与Starter入口OpcUaAutoConfiguration最后我们需要一个spring.factories文件对于Spring Boot 2.7也可以是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件来声明我们的自动配置类。Configuration ConditionalOnWebApplication // 或者 ConditionalOnBean 某些特定条件 EnableConfigurationProperties(OpcUaProperties.class) AutoConfigureAfter({OpcUaClientConfig.class}) // 确保客户端Bean先创建 public class OpcUaAutoConfiguration { Bean ConditionalOnMissingBean public OpcUaTemplate opcUaTemplate(OpcUaProperties properties, MapString, OpcUaClient opcUaClients) { // 这里可以提供一个默认的Template或者由用户自己Bean定义 return new OpcUaTemplate(“default”, opcUaClients); } // 可以在这里定义一些默认的监听器或工具类Bean }这样只要用户的项目引入了我们这个starter的依赖并且opcua.clients下有配置Spring Boot就会自动装配好OpcUaClient和OpcUaTemplate。5. 高级特性与生产级考量一个能在生产环境跑起来的starter光有基础读写是远远不够的。下面这些高级特性和细节处理才是区分“玩具”和“工具”的关键。5.1 连接池与多会话管理对于需要高频、并发读取大量节点的场景单个会话Session可能会成为瓶颈。Milo的OpcUaClient支持创建多个会话。我们可以在OpcUaTemplate内部实现一个简单的会话池。当进行读写操作时从池中借用一个会话用完后归还。这需要仔细处理会话的创建、激活、关闭以及异常时的清理工作。池的大小、获取超时时间都可以作为配置项。5.2 订阅Subscription的优雅管理数据变化订阅是OPC UA的核心优势。我们的starter需要提供便捷的订阅API。public SubscriptionHandle subscribe(String nodeId, DataChangeListener listener, Double samplingInterval) { UaSubscription subscription createSubscription(); // 获取或创建订阅 MonitoringParameters parameters new MonitoringParameters( uint(1), // client handle samplingInterval, // 采样间隔 null, // 过滤器 uint(10), // 队列大小 true // 丢弃最旧 ); MonitoredItemCreateRequest request new MonitoredItemCreateRequest( new ReadValueId(NodeId.parse(nodeId), AttributeId.Value.uid(), null, null), MonitoringMode.Reporting, parameters ); // 创建监控项并关联监听器 // 返回一个SubscriptionHandle包含订阅ID和监控项ID用于后续取消订阅 }更重要的是当客户端断线重连后原有的订阅会失效。我们需要在OpcUaTemplate中维护一个“订阅注册表”记录所有活跃的订阅请求节点ID、监听器、参数。在连接成功或恢复后自动重新创建这些订阅实现订阅的“持久化”对业务层透明。5.3 证书与安全配置的简化安全策略为Basic256Sha256时需要交换证书。Milo提供了KeyStoreLoader等工具但配置依然复杂。我们可以进一步封装自动生成客户端证书如果配置中指定了证书路径但文件不存在可以自动生成一个自签名证书。服务器证书信任管理提供一个配置项opcua.clients.xxx.trust-server-cert: true当为true时自动将首次连接的服务端证书导入到客户端的信任库中生产环境慎用最好手动管理信任库。密码保护证书的私钥密码、信任库密码等敏感信息必须支持从环境变量或配置中心如Spring Cloud Config读取绝不能硬编码在配置文件中。5.4 指标监控与健康检查对于微服务架构暴露监控指标至关重要。我们可以利用Micrometer在OpcUaTemplate中收集关键指标opcua.client.connection.status连接状态1已连接0断开opcua.client.session.active_count活跃会话数opcua.operations.read.total读操作总数分成功/失败标签opcua.operations.read.duration读操作耗时直方图opcua.subscriptions.active活跃订阅数同时实现一个HealthIndicator定期对配置的OPC UA服务器进行一个简单的读操作比如读一个已知的“服务器状态”节点根据结果返回UP、DOWN或UNKNOWN状态集成到Spring Boot Actuator的/health端点中。5.5 配置的灵活性与多环境支持我们的OpcUaProperties要支持Spring Boot强大的Profile功能和松散绑定。例如生产环境的服务器地址和密码肯定与测试环境不同。可以通过application-prod.yml来覆盖默认配置。对于密码等机密强烈建议使用${VAULT_TOKEN}或Jasypt进行加密。6. 实战踩坑与问题排查实录理论设计再完美也得经过实战的检验。下面是我在开发和测试过程中遇到的几个典型问题及解决方案这些都是你在实际使用中很可能碰到的。6.1 连接超时与网络问题现象客户端启动时卡在client.connect().get()最终抛出TimeoutException或UaException: BadTimeout。排查网络可达性首先用telnet或nc命令测试服务器IP和端口是否通。防火墙检查服务器和客户端防火墙是否放行了4840端口OPC UA默认端口。端点发现Milo在连接时会先获取服务器端点列表。如果服务器证书是自签名的且客户端未信任可能导致发现阶段失败。可以尝试先用securityPolicy: None进行连接测试排除证书问题。DNS解析如果Endpoint URL用的是主机名确保客户端能正确解析。解决在配置中适当增加requestTimeout和连接超时参数。对于证书问题参考5.3节配置信任。6.2 证书验证失败SecurityPolicy不为None时现象连接时抛出UaException: BadSecurityChecksFailed或CertificateValidationException。排查这是生产环境中最常见的问题。根本原因是客户端不信任服务器的证书或服务器不信任客户端的证书。解决开发/测试环境快速方案在创建OpcUaClient时设置一个CertificateValidator重写validateCertificate方法直接返回VALID警告此方法极度不安全仅用于测试。生产环境标准方案从OPC UA服务器导出其应用程序证书.der或.pem格式。将这个证书导入到客户端信任库JKS或PKCS12文件。为客户端生成一个证书/私钥对并导入到客户端的密钥库。将客户端的证书提供给服务器管理员导入到服务器的信任库。在配置中指定keyStorePath,keyStorePassword,trustStorePath,trustStorePassword。6.3 订阅数据不更新或延迟高现象创建订阅后监听器偶尔收到数据或者数据更新频率远低于设定的publishingInterval。排查服务器端发布间隔我们客户端设置的subscriptionPublishInterval是一个“请求”最终生效的发布间隔是服务器端决定的。服务器可能会限制或调整这个值。需要用专业的OPC UA客户端如UA Expert连接同一服务器查看实际生效的发布间隔。队列溢出如果数据变化太快而queueSize设置太小或者discardOldest策略不合适可能导致数据丢失。查看DataChangeNotification中的queueSize和overflow状态。采样间隔SamplingIntervalSamplingInterval是监控项级别的它必须大于等于服务器的“最小采样间隔”且是服务器“采样间隔基数”的整数倍。设置不合理会被服务器拒绝或调整。解决仔细阅读设备手册了解服务器对订阅参数的限制。使用UA Expert等工具进行参数摸底。适当增加queueSize确保discardOldest策略符合业务需求是丢弃最旧数据还是拒绝新数据。6.4 内存泄漏与资源未释放现象长时间运行后应用内存持续增长或断开重连次数多了之后出现异常。排查监听器未注销在OpcUaTemplate中如果业务组件注册了数据变化监听器但在组件销毁时没有取消订阅会导致监听器对象无法被GC回收订阅也残留在服务器端。订阅未删除断开连接时如果只是调用了disconnect()服务器端的订阅可能不会立即被清理。应该在stop()方法中显式调用subscription.deleteMonitoredItems()和client.deleteSubscriptions()。线程池未关闭Milo内部使用了Netty的EventLoopGroup和调度线程池。如果创建了多个OpcUaClient实例且没有正确关闭会导致线程泄漏。解决在OpcUaTemplate的stop()方法中严格按照删除监控项 - 删除订阅 - 断开会话 - 关闭客户端的顺序执行清理。并确保OpcUaTemplate本身是单例的其生命周期由Spring容器严格管理。6.5 高并发读写时的性能瓶颈现象同时读写数百个节点时响应变慢甚至出现超时。排查单会话瓶颈所有请求共用一个会话服务端按顺序处理。网络往返每个读/写请求一个网络来回。解决使用批量读写OPC UA协议本身支持批量读写。OpcUaClient的read()和write()方法都接受列表参数。务必把多个节点的读写操作合并成一个请求发送能极大提升效率。启用会话池如5.1所述使用多个会话来并行处理请求。调整客户端参数增加OpcUaClientConfigBuilder中的requestTimeout和maxPendingPublishRequests最大待处理发布请求数影响订阅吞吐。把这些坑和解决方案提前考虑到你的Starter设计中比如在OpcUaTemplate的readMultiple、writeMultiple方法中默认使用批量操作在连接管理器中严格实现资源清理就能让这个Starter的稳定性和可靠性提升一个档次。7. 使用示例与集成测试最后我们来看看封装好的Starter在实际项目中如何使用以及如何为它编写集成测试确保其行为符合预期。7.1 在业务项目中使用首先在pom.xml中引入我们打包好的starter假设我们发布的artifactId是opcua-spring-boot-starter。dependency groupIdcom.yourcompany/groupId artifactIdopcua-spring-boot-starter/artifactId version1.0.0/version /dependency然后在application.yml中配置一个客户端opcua: default-client: machine-1 # 指定默认使用的客户端对应OpcUaTemplate的Bean clients: machine-1: endpoint-url: opc.tcp://192.168.10.50:4840 security-policy: Basic256Sha256 identity: username username: opcuser password: ${OPCUA_MACHINE1_PASS:secret} # 从环境变量读取 key-store-path: classpath:cert/client-keystore.pfx key-store-password: ${KEYSTORE_PASS} trust-store-path: classpath:cert/client-truststore.jks trust-store-password: ${TRUSTSTORE_PASS} subscription-publish-interval: 200.0 request-timeout: 10s reconnect-delay: 3s最后在Service中注入OpcUaTemplate并使用Service Slf4j public class DataCollectService { Autowired private OpcUaTemplate opcUaTemplate; // 注入默认客户端对应的Template PostConstruct public void initSubscription() { // 订阅一个温度节点 SubscriptionHandle handle opcUaTemplate.subscribe(“ns2;sMachine1.Temperature”, (item, value) - { log.info(“温度变化: {} {}”, item.getReadValueId().getNodeId(), value.getValue()); // 触发业务逻辑如存入数据库、发送告警等 processTemperature(value.getValue().floatValue()); }, 1000.0 // 采样间隔1秒 ); // 可以将handle保存起来在服务关闭时取消订阅 } public Float getCurrentTemperature() { DataValue dataValue opcUaTemplate.read(“ns2;sMachine1.Temperature”); return dataValue.getValue().floatValue(); } public void setTargetSpeed(Double speed) { opcUaTemplate.write(“ns2;sMachine1.TargetSpeed”, speed); } }使用起来非常简洁几乎感知不到底层OPC UA协议的复杂性。7.2 编写集成测试对于这样一个基础设施组件集成测试必不可少。我们需要模拟一个OPC UA服务器来测试客户端的各种行为。可以使用Milo提供的OpcUaServer来在测试中内嵌一个服务器。SpringBootTest(classes TestApplication.class) TestPropertySource(properties { “opcua.clients.test.endpoint-urlopc.tcp://localhost:12345”, “opcua.clients.test.security-policyNone” }) Slf4j public class OpcUaTemplateIntegrationTest { Autowired private OpcUaTemplate opcUaTemplate; private static OpcUaServer server; BeforeAll static void startServer() throws Exception { // 使用Milo创建一个内存中的测试服务器 server new OpcUaServer(); server.setPort(12345); // 在服务器地址空间添加一些测试节点 server.getAddressSpaceManager().register(...); server.startup().get(); } AfterAll static void stopServer() throws Exception { server.shutdown().get(); } Test void testReadWrite() { // 测试读写功能 opcUaTemplate.write(“ns2;sTestNode”, 42.0); DataValue dv opcUaTemplate.read(“ns2;sTestNode”); assertEquals(42.0, dv.getValue().doubleValue(), 0.001); } Test void testSubscription() throws InterruptedException { AtomicInteger counter new AtomicInteger(0); SubscriptionHandle handle opcUaTemplate.subscribe(“ns2;sDynamicNode”, (item, value) - counter.incrementAndGet(), 100.0 ); // 通过某种方式更新服务器端节点的值... Thread.sleep(500); // 等待发布 assertTrue(counter.get() 0); opcUaTemplate.unsubscribe(handle); } }通过这样的集成测试我们可以确保Starter在真实网络交互场景下的基本功能正常包括连接、读写、订阅、重连等。这为Starter的可靠性提供了坚实基础。整个封装过程从需求分析、技术选型、详细设计、代码实现到测试验证是一个典型的从具体业务问题中抽象出通用解决方案的过程。最终产出的这个Spring Boot Starter不仅解决了当前项目接入OPC UA的痛点更成为了团队乃至公司级的一个可复用的资产。下次再有项目需要接入OPC UA设备只需要引入这个依赖配一下地址和证书几分钟就能搞定数据对接把开发者的精力从协议细节中解放出来聚焦在真正的业务逻辑上。这种通过封装创造价值的做法在软件开发中永远都不会过时。
返回列表